146 files
324.7 KB
104.7k tokens
133 symbols
1 requests
Download .txt
Showing preview only (364K chars total). Download the full file or copy to clipboard to get everything.
Repository: PacktPublishing/Keycloak-Identity-and-Access-Management-for-Modern-Applications
Branch: master
Commit: d46e755e9279
Files: 146
Total size: 324.7 KB

Directory structure:
gitextract__3_pgkb2/

├── .gitignore
├── LICENSE
├── README.md
├── ch13/
│   ├── simple-risk-based-authenticator/
│   │   ├── .mvn/
│   │   │   └── wrapper/
│   │   │       ├── MavenWrapperDownloader.java
│   │   │       ├── maven-wrapper.jar
│   │   │       └── maven-wrapper.properties
│   │   ├── README.md
│   │   ├── mvnw
│   │   ├── mvnw.cmd
│   │   ├── pom.xml
│   │   └── src/
│   │       └── main/
│   │           ├── java/
│   │           │   └── org/
│   │           │       └── keycloak/
│   │           │           └── book/
│   │           │               └── ch13/
│   │           │                   └── authentication/
│   │           │                       ├── MySimpleRiskBasedAuthenticator.java
│   │           │                       └── MySimpleRiskBasedAuthenticatorFactory.java
│   │           └── resources/
│   │               └── META-INF/
│   │                   └── services/
│   │                       └── org.keycloak.authentication.AuthenticatorFactory
│   └── themes/
│       └── mytheme/
│           ├── .mvn/
│           │   └── wrapper/
│           │       ├── MavenWrapperDownloader.java
│           │       ├── maven-wrapper.jar
│           │       └── maven-wrapper.properties
│           ├── README.md
│           ├── mvnw
│           ├── mvnw.cmd
│           ├── pom.xml
│           └── src/
│               └── main/
│                   ├── java/
│                   │   └── org/
│                   │       └── keycloak/
│                   │           └── book/
│                   │               └── ch13/
│                   │                   └── theme/
│                   │                       ├── MyThemeResourceProvider.java
│                   │                       └── MyThemeSelectorProvider.java
│                   └── resources/
│                       ├── META-INF/
│                       │   ├── jboss-deployment-structure.xml
│                       │   ├── keycloak-themes.json
│                       │   └── services/
│                       │       ├── org.keycloak.theme.ThemeResourceProviderFactory
│                       │       └── org.keycloak.theme.ThemeSelectorProviderFactory
│                       └── theme/
│                           └── mytheme/
│                               └── login/
│                                   ├── resources/
│                                   │   └── css/
│                                   │       └── signin.css
│                                   └── theme.properties
├── ch2/
│   ├── backend/
│   │   ├── Dockerfile
│   │   ├── app.js
│   │   ├── keycloak.json
│   │   └── package.json
│   └── frontend/
│       ├── .mvn/
│       │   └── wrapper/
│       │       ├── MavenWrapperDownloader.java
│       │       ├── maven-wrapper.jar
│       │       └── maven-wrapper.properties
│       ├── Dockerfile
│       ├── app.js
│       ├── index.html
│       └── package.json
├── ch4/
│   ├── Dockerfile
│   ├── app.js
│   ├── client.js
│   ├── index.html
│   ├── package.json
│   └── styles.css
├── ch5/
│   ├── backend/
│   │   ├── Dockerfile
│   │   ├── app.js
│   │   ├── keycloak.json
│   │   └── package.json
│   └── frontend/
│       ├── Dockerfile
│       ├── app.js
│       ├── client.js
│       ├── index.html
│       ├── package.json
│       └── styles.css
├── ch6/
│   ├── app.js
│   └── package.json
├── ch7/
│   ├── golang/
│   │   ├── go.mod
│   │   ├── go.sum
│   │   └── main.go
│   ├── keycloak-js-adapter/
│   │   ├── app.js
│   │   ├── index.html
│   │   └── package.json
│   ├── nodejs/
│   │   ├── backend/
│   │   │   ├── app.js
│   │   │   ├── keycloak.json
│   │   │   └── package.json
│   │   └── frontend/
│   │       ├── app.js
│   │       ├── index.html
│   │       ├── keycloak.json
│   │       └── package.json
│   ├── python/
│   │   ├── backend/
│   │   │   ├── app.py
│   │   │   └── oidc-config.json
│   │   └── frontend/
│   │       ├── app.py
│   │       └── oidc-config.json
│   ├── quarkus/
│   │   ├── backend/
│   │   │   ├── .mvn/
│   │   │   │   └── wrapper/
│   │   │   │       ├── MavenWrapperDownloader.java
│   │   │   │       ├── maven-wrapper.jar
│   │   │   │       └── maven-wrapper.properties
│   │   │   ├── mvnw
│   │   │   ├── mvnw.cmd
│   │   │   ├── pom.xml
│   │   │   └── src/
│   │   │       └── main/
│   │   │           ├── java/
│   │   │           │   └── org/
│   │   │           │       └── keycloak/
│   │   │           │           └── GreetingResource.java
│   │   │           └── resources/
│   │   │               ├── META-INF/
│   │   │               │   └── resources/
│   │   │               │       └── index.html
│   │   │               └── application.properties
│   │   └── frontend/
│   │       ├── .mvn/
│   │       │   └── wrapper/
│   │       │       ├── MavenWrapperDownloader.java
│   │       │       ├── maven-wrapper.jar
│   │       │       └── maven-wrapper.properties
│   │       ├── mvnw
│   │       ├── mvnw.cmd
│   │       ├── pom.xml
│   │       └── src/
│   │           └── main/
│   │               ├── java/
│   │               │   └── org/
│   │               │       └── keycloak/
│   │               │           └── GreetingResource.java
│   │               └── resources/
│   │                   ├── META-INF/
│   │                   │   └── resources/
│   │                   │       └── index.html
│   │                   └── application.properties
│   ├── reverse-proxy/
│   │   ├── app/
│   │   │   ├── app.js
│   │   │   └── package.json
│   │   └── secure-proxy.conf
│   └── springboot/
│       ├── backend/
│       │   ├── .mvn/
│       │   │   └── wrapper/
│       │   │       ├── MavenWrapperDownloader.java
│       │   │       ├── maven-wrapper.jar
│       │   │       └── maven-wrapper.properties
│       │   ├── build.gradle
│       │   ├── gradlew
│       │   ├── gradlew.bat
│       │   ├── mvnw
│       │   ├── mvnw.cmd
│       │   ├── pom.xml
│       │   ├── settings.gradle
│       │   └── src/
│       │       └── main/
│       │           ├── java/
│       │           │   └── org/
│       │           │       └── keycloak/
│       │           │           └── springboot/
│       │           │               ├── Application.java
│       │           │               ├── HelloController.java
│       │           │               └── SecurityConfig.java
│       │           └── resources/
│       │               └── application.yaml
│       ├── backend-using-introspection/
│       │   ├── .mvn/
│       │   │   └── wrapper/
│       │   │       ├── maven-wrapper.jar
│       │   │       └── maven-wrapper.properties
│       │   ├── build.gradle
│       │   ├── gradlew
│       │   ├── gradlew.bat
│       │   ├── mvnw
│       │   ├── mvnw.cmd
│       │   ├── pom.xml
│       │   ├── settings.gradle
│       │   └── src/
│       │       └── main/
│       │           ├── java/
│       │           │   └── org/
│       │           │       └── keycloak/
│       │           │           └── springboot/
│       │           │               ├── Application.java
│       │           │               ├── HelloController.java
│       │           │               └── SecurityConfig.java
│       │           └── resources/
│       │               └── application.yaml
│       └── frontend/
│           ├── .mvn/
│           │   └── wrapper/
│           │       ├── MavenWrapperDownloader.java
│           │       ├── maven-wrapper.jar
│           │       └── maven-wrapper.properties
│           ├── build.gradle
│           ├── gradlew
│           ├── gradlew.bat
│           ├── mvnw
│           ├── mvnw.cmd
│           ├── pom.xml
│           ├── settings.gradle
│           └── src/
│               └── main/
│                   ├── java/
│                   │   └── com/
│                   │       └── example/
│                   │           └── springboot/
│                   │               ├── Application.java
│                   │               └── HelloController.java
│                   └── resources/
│                       └── application.yaml
└── ch9/
    ├── configure-caches.cli
    ├── configure-database.cli
    ├── configure-hostname.cli
    ├── configure-https.cli
    ├── configure-proxy.cli
    ├── configure-session-affinity.cli
    ├── haproxy.cfg
    ├── haproxy.crt.pem
    ├── mykeycloak.crt
    ├── mykeycloak.key
    └── mykeycloak.keystore

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
.idea
*.iml
gradle

package-lock.json
node_modules
node
target


================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2020 Packt

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README.md
================================================



# Keycloak - Identity and Access Management for Modern Applications

<a href="https://www.packtpub.com/product/keycloak-identity-and-access-management-for-modern-applications/9781800562493?utm_source=github&utm_medium=repository&utm_campaign=9781800562493"><img src="https://static.packt-cdn.com/products/9781800562493/cover/smaller" alt="Keycloak - Identity and Access Management for Modern Applications" height="256px" align="right"></a>

This is the code repository for [Keycloak - Identity and Access Management for Modern Applications](https://www.packtpub.com/product/keycloak-identity-and-access-management-for-modern-applications/9781800562493?utm_source=github&utm_medium=repository&utm_campaign=9781800562493), published by Packt.

**Harness the power of Keycloak, OpenID Connect, and OAuth 2.0 protocols to secure applications**

## What is this book about?
Implementing authentication and authorization for applications can be a daunting experience, often leaving them exposed to security vulnerabilities. Keycloak is an open-source solution for identity management and access management for modern applications.

This book covers the following exciting features: 
* Understand how to install, configure, and manage Keycloak
* Secure your new and existing applications with Keycloak
* Gain a basic understanding of OAuth 2.0 and OpenID Connect
* Understand how to configure Keycloak to make it ready for production use
* Discover how to leverage additional features and how to customize Keycloak to fit your needs

If you feel this book is for you, get your [copy](https://www.amazon.com/dp/1800562497) today!

<a href="https://www.packtpub.com/?utm_source=github&utm_medium=banner&utm_campaign=GitHubBanner"><img src="https://raw.githubusercontent.com/PacktPublishing/GitHub/master/GitHub.png" alt="https://www.packtpub.com/" border="5" /></a>

## Instructions and Navigations
All of the code is organized into folders. For example, Chapter02.

The code will look like the following:
```
if (test expression)
{
  Statement upon condition is true
}
```

**Following is what you need for this book:**
Developers, sysadmins, security engineers, or anyone who wants to leverage Keycloak and its capabilities for application security will find this book useful. Beginner-level knowledge of app development and authentication and authorization is expected.


With the following software and hardware list you can run all code files present in the book (Chapter 1-14).

### Software and Hardware List

| Chapter  | Software required                   | OS required                        |
| -------- | ------------------------------------| -----------------------------------|
| 1-14        | Keycloak 12                      | Windows, macOS, and Linux (Any) |
| 1-14        | OpenJDK 8+                       | Windows, macOS, and Linux (Any) |
| 1-14       | Node.js 14+                       | Windows, Mac OS, and Linux (Any) |

We also provide a PDF file that has color images of the screenshots/diagrams used in this book. [Click here to download it](http://www.packtpub.com/sites/default/files/downloads/9781800562493_ColorImages.pdf).

## Code in Action

Click on the following link to see the Code in Action:

[YouTube](https://www.youtube.com/playlist?list=PLeLcvrwLe187DykEKXg-9Urd1Z6MQT61d)

## Errata

* Page 5 (Code block 2): `$JAVA_HOME/bin/java-version` _should be_ `$JAVA_HOME/bin/java -version`

* Page 6 (Paragraph 5, line 1): `$ docker run -e KEYCLOAK_USER=admin -e KEYCLOAK_PASSWORD=admin -p 8080 quay.io/keycloak/keycloak` _should be_`$ docker run -e KEYCLOAK_USER=admin -e KEYCLOAK_PASSWORD=admin -p 8080:8080 quay.io/keycloak/keycloak`

### Related products 
* Okta Administration: Up and Running [[Packt]](https://www.packtpub.com/product/okta-administration-up-and-running/9781800566644?utm_source=github&utm_medium=repository&utm_campaign=9781800562493) [[Amazon]](https://www.amazon.com/dp/1800566646)

* Mastering Identity and Access Management with Microsoft Azure - Second Edition [[Packt]](https://www.packtpub.com/product/mastering-identity-and-access-management-with-microsoft-azure-second-edition/9781789132304?utm_source=github&utm_medium=repository&utm_campaign=9781789132304) [[Amazon]](https://www.amazon.com/dp/1789132304)

## Get to Know the Authors
**Stian Thorgersen** started his career at Arjuna Technologies building a cloud federation platform, years before most companies were even ready for a single-vendor public cloud. He later joined Red Hat, looking for ways to make developers' lives easier, which is where the idea of Keycloak started. In 2013, Stian co-founded the Keycloak project with another developer at Red Hat. Today, Stian is the Keycloak project lead and is also the top contributor to the project. He is still employed by Red Hat as a senior principal software engineer focusing on identity and access management, both for Red Hat and for Red Hat's customers. In his spare time, there is nothing Stian likes more than throwing his bike down the mountains of Norway.

**Pedro Igor Silva** is a proud dad of amazing girls. He started his career back in 2000 at an ISP, where he had his first experiences with open source projects such as FreeBSD and Linux, as well as a Java and J2EE software engineer. Since then, he has worked in different IT companies as a system engineer, system architect, and consultant. Today, Pedro Igor is a principal software engineer at Red Hat and one of the core developers of Keycloak. His main area of interest and study is now IT security, specifically in the application security and identity and access management spaces. In his non-working hours, he takes care of his planted aquariums.
### Download a free PDF

 <i>If you have already purchased a print or Kindle version of this book, you can get a DRM-free PDF version at no cost.<br>Simply click on the link to claim your free PDF.</i>
<p align="center"> <a href="https://packt.link/free-ebook/9781800562493">https://packt.link/free-ebook/9781800562493 </a> </p>


================================================
FILE: ch13/simple-risk-based-authenticator/.mvn/wrapper/MavenWrapperDownloader.java
================================================
/*
 * 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();
    }

}


================================================
FILE: ch13/simple-risk-based-authenticator/.mvn/wrapper/maven-wrapper.properties
================================================
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


================================================
FILE: ch13/simple-risk-based-authenticator/README.md
================================================
# Examples for Chapter 13 - Extending Keycloak

## Simple Risk-Based Authenticator

To build this project, execute the following command:

    ./mvnw clean install

Then copy the `target/simple-risk-based-authenticator.jar` to the `$KC_HOME/standalone/deployments` directory.

### What is here

* An example of how to leverage the `Authentication SPI` to create and configure authenticators
* A very simple example of how to use an authenticator to perform risk-based authenticator where the user is forced to use
a second factor depending on the number of failed login attempts

================================================
FILE: ch13/simple-risk-based-authenticator/mvnw
================================================
#!/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 "$@"


================================================
FILE: ch13/simple-risk-based-authenticator/mvnw.cmd
================================================
@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%


================================================
FILE: ch13/simple-risk-based-authenticator/pom.xml
================================================
<?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>org.keycloak.book</groupId>
    <artifactId>simple-risk-based-authenticator</artifactId>
    <version>1.0.0-SNAPSHOT</version>
    <packaging>jar</packaging>
    <properties>
        <keycloak.version>12.0.4</keycloak.version>
        <jboss-jaxrs-api_2.1_spec>2.0.1.Final</jboss-jaxrs-api_2.1_spec>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.keycloak</groupId>
            <artifactId>keycloak-server-spi</artifactId>
            <version>${keycloak.version}</version>
        </dependency>
        <dependency>
            <groupId>org.keycloak</groupId>
            <artifactId>keycloak-server-spi-private</artifactId>
            <version>${keycloak.version}</version>
        </dependency>
        <dependency>
            <groupId>org.keycloak</groupId>
            <artifactId>keycloak-core</artifactId>
            <version>${keycloak.version}</version>
        </dependency>
        <dependency>
            <groupId>org.keycloak</groupId>
            <artifactId>keycloak-services</artifactId>
            <version>${keycloak.version}</version>
        </dependency>
        <dependency>
            <groupId>org.jboss.spec.javax.ws.rs</groupId>
            <artifactId>jboss-jaxrs-api_2.1_spec</artifactId>
            <version>${jboss-jaxrs-api_2.1_spec}</version>
        </dependency>
    </dependencies>
    <build>
        <finalName>${project.artifactId}</finalName>
    </build>
</project>

================================================
FILE: ch13/simple-risk-based-authenticator/src/main/java/org/keycloak/book/ch13/authentication/MySimpleRiskBasedAuthenticator.java
================================================
package org.keycloak.book.ch13.authentication;

import static org.keycloak.book.ch13.authentication.MySimpleRiskBasedAuthenticatorFactory.FAILED_LOGIN_ATTEMPTS_BEFORE_2FA;

import java.util.Collections;
import java.util.Map;

import org.keycloak.authentication.AuthenticationFlowContext;
import org.keycloak.authentication.Authenticator;
import org.keycloak.models.AuthenticatorConfigModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserCredentialManager;
import org.keycloak.models.UserLoginFailureModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionProvider;
import org.keycloak.models.credential.OTPCredentialModel;

/**
 * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
 */
public class MySimpleRiskBasedAuthenticator implements Authenticator {

    private static final String OTP_REQUIRED_USER_ATTRIBUTE = "my.risk.based.auth.2fa.required";

    private enum RiskScore {
        LOW(0),
        MEDIUM(1.0),
        HIGH(2.0);

        private final double score;

        RiskScore(double score) {
            this.score = score;
        }

        public boolean requiresSecondFactor() {
            return score > 0;
        }
    }

    @Override
    public void authenticate(AuthenticationFlowContext context) {
        KeycloakSession session = context.getSession();
        UserModel user = context.getUser();
        RiskScore riskScore = calculateRiskScore(session, context, user);

        if (riskScore.requiresSecondFactor()) {
            forceSecondFactor(session, user);
        } else {
            skipSecondFactor(user);
        }

        context.success();
    }

    private RiskScore calculateRiskScore(KeycloakSession session, AuthenticationFlowContext context, UserModel user) {
        RealmModel realm = session.getContext().getRealm();
        UserSessionProvider sessions = session.sessions();
        UserLoginFailureModel loginFailure = sessions.getUserLoginFailure(realm, user.getId());

        if (loginFailure == null) {
            return RiskScore.LOW;
        }

        int failures = loginFailure.getNumFailures();
        Integer maxFailuresBeforeOtp = getMaxFailuresBeforeOtp(context);

        if (failures >= maxFailuresBeforeOtp) {
            return RiskScore.MEDIUM;
        }

        return RiskScore.LOW;
    }

    private void skipSecondFactor(UserModel user) {
        user.setAttribute(OTP_REQUIRED_USER_ATTRIBUTE, Collections.singletonList("skip"));
    }

    private void forceSecondFactor(KeycloakSession session, UserModel user) {
        UserCredentialManager credentialManager = session.userCredentialManager();
        RealmModel realm = session.getContext().getRealm();

        if (!credentialManager.isConfiguredFor(realm, user, OTPCredentialModel.TYPE)) {
            // if not OTP set, than force registration
            user.addRequiredAction(UserModel.RequiredAction.CONFIGURE_TOTP);
        }

        user.setAttribute(OTP_REQUIRED_USER_ATTRIBUTE, Collections.singletonList("force"));
    }

    private Integer getMaxFailuresBeforeOtp(AuthenticationFlowContext context) {
        Map<String, String> config = Collections.emptyMap();
        AuthenticatorConfigModel configModel = context.getAuthenticatorConfig();

        if (configModel != null) {
            config = configModel.getConfig();
        }

        return Integer.valueOf(config.getOrDefault(FAILED_LOGIN_ATTEMPTS_BEFORE_2FA, "3"));
    }

    @Override
    public void action(AuthenticationFlowContext context) {
        // no-op
    }

    @Override
    public boolean requiresUser() {
        return true;
    }

    @Override
    public boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user) {
        return true;
    }

    @Override
    public void setRequiredActions(KeycloakSession session, RealmModel realm, UserModel user) {
        // no-op
    }

    @Override
    public void close() {
        // nothing to close
    }
}


================================================
FILE: ch13/simple-risk-based-authenticator/src/main/java/org/keycloak/book/ch13/authentication/MySimpleRiskBasedAuthenticatorFactory.java
================================================
package org.keycloak.book.ch13.authentication;

import static org.keycloak.provider.ProviderConfigProperty.STRING_TYPE;

import java.util.Collections;
import java.util.List;

import org.apache.http.auth.AUTH;
import org.keycloak.Config;
import org.keycloak.authentication.Authenticator;
import org.keycloak.authentication.AuthenticatorFactory;
import org.keycloak.models.AuthenticationExecutionModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.provider.ProviderConfigProperty;

/**
 * <p>A very simple demonstration about how to extend the <b>Authentication SPI</b> to create custom authentication execution
 * by using a custom {@link Authenticator} type.
 *
 * <p>This authentication execution should be used in Browser-like flow and added right after the <i>Username Password Form</i>
 * execution.
 *
 * <p>This authenticator is responsible for deciding whether 2FA is mandatory for a user depending on the number of falied login
 * attempts.
 *
 * <p>By setting the {@code failedLoginAttemptsBefore2FA} configuration property for this authenticator, you can define
 * the maximum number of failures until 2FA is mandatory to authenticate users. The default value is {@code 3}.
 */
public class MySimpleRiskBasedAuthenticatorFactory implements AuthenticatorFactory {

    public static final String ID = "myriskbasedauthenticator";

    // the authenticator does not keep any state and rely on runtime information. Can be safely shared across multiple threads
    private static final Authenticator AUTHENTICATOR_INSTANCE = new MySimpleRiskBasedAuthenticator();
    static final String FAILED_LOGIN_ATTEMPTS_BEFORE_2FA = "failedLoginAttemptsBefore2FA";

    @Override
    public Authenticator create(KeycloakSession keycloakSession) {
        // no need to create an instance all the time, so we just use a singleton
        return AUTHENTICATOR_INSTANCE;
    }

    @Override
    public String getDisplayType() {
        return "My Simple Risk-Based Authenticator";
    }

    @Override
    public boolean isConfigurable() {
        return true;
    }

    @Override
    public AuthenticationExecutionModel.Requirement[] getRequirementChoices() {
        return new AuthenticationExecutionModel.Requirement[] { AuthenticationExecutionModel.Requirement.REQUIRED };
    }

    @Override
    public boolean isUserSetupAllowed() {
        return false;
    }

    @Override
    public String getHelpText() {
        return "A help test";
    }

    @Override
    public List<ProviderConfigProperty> getConfigProperties() {
        ProviderConfigProperty name = new ProviderConfigProperty();

        // by configuring this property, you should be able to configure this authenticator in the administration console
        name.setType(STRING_TYPE);
        name.setName(FAILED_LOGIN_ATTEMPTS_BEFORE_2FA);
        name.setLabel("Failed attempts before asking second factor");
        name.setHelpText("The number of failed attempts before asking second factor");

        return Collections.singletonList(name);
    }

    @Override
    public String getReferenceCategory() {
        return null;
    }

    @Override
    public void init(Config.Scope scope) {
        // no supported configuration, nothing to initialize
    }

    @Override
    public void postInit(KeycloakSessionFactory keycloakSessionFactory) {
        // nothing to initialize
    }

    @Override
    public void close() {
        // nothing to close
    }

    @Override
    public String getId() {
        return ID;
    }
}


================================================
FILE: ch13/simple-risk-based-authenticator/src/main/resources/META-INF/services/org.keycloak.authentication.AuthenticatorFactory
================================================
org.keycloak.book.ch13.authentication.MySimpleRiskBasedAuthenticatorFactory

================================================
FILE: ch13/themes/mytheme/.mvn/wrapper/MavenWrapperDownloader.java
================================================
/*
 * 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();
    }

}


================================================
FILE: ch13/themes/mytheme/.mvn/wrapper/maven-wrapper.properties
================================================
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


================================================
FILE: ch13/themes/mytheme/README.md
================================================
# Examples for Chapter 13 - Extending Keycloak

## Themes

To build this project, execute the following command:

    ./mvnw clean install

Then copy the `target/mytheme.jar` to the `$KC_HOME/standalone/deployments` directory.

### What is here

* A custom theme for the login page (only the login page) called `mytheme`.
* An example on how to use a `ThemeSelectorProvider` to dynamically choose a theme.
* An example on how to use a `MyThemeResourceProvider` to load additional templates and resources. 

================================================
FILE: ch13/themes/mytheme/mvnw
================================================
#!/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 "$@"


================================================
FILE: ch13/themes/mytheme/mvnw.cmd
================================================
@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%


================================================
FILE: ch13/themes/mytheme/pom.xml
================================================
<?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>org.keycloak.book</groupId>
    <artifactId>mytheme</artifactId>
    <version>1.0.0-SNAPSHOT</version>
    <packaging>jar</packaging>
    <properties>
        <keycloak.version>12.0.4</keycloak.version>
        <jboss-jaxrs-api_2.1_spec>2.0.1.Final</jboss-jaxrs-api_2.1_spec>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.keycloak</groupId>
            <artifactId>keycloak-server-spi</artifactId>
            <version>${keycloak.version}</version>
        </dependency>
        <dependency>
            <groupId>org.keycloak</groupId>
            <artifactId>keycloak-core</artifactId>
            <version>${keycloak.version}</version>
        </dependency>
        <dependency>
            <groupId>org.keycloak</groupId>
            <artifactId>keycloak-services</artifactId>
            <version>${keycloak.version}</version>
        </dependency>
        <dependency>
            <groupId>org.jboss.spec.javax.ws.rs</groupId>
            <artifactId>jboss-jaxrs-api_2.1_spec</artifactId>
            <version>${jboss-jaxrs-api_2.1_spec}</version>
        </dependency>
    </dependencies>
    <build>
        <finalName>${project.artifactId}</finalName>
    </build>
</project>

================================================
FILE: ch13/themes/mytheme/src/main/java/org/keycloak/book/ch13/theme/MyThemeResourceProvider.java
================================================
package org.keycloak.book.ch13.theme;

import java.io.InputStream;
import java.net.URL;

import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.theme.ThemeResourceProvider;
import org.keycloak.theme.ThemeResourceProviderFactory;

/**
 * <p>A very simple demonstration about how to load custom templates and resources. Helpful if you are implementing other providers,
 * like a authenticator or required action, that requires additional templates and resources.
 *
 * <p>You can use this example as a baseline, but never for production.
 */
public class MyThemeResourceProvider implements ThemeResourceProvider, ThemeResourceProviderFactory {

    private static final String ID = "mythemeselector";
    private final KeycloakSession session;

    public MyThemeResourceProvider() {
        // for reflection when registering the factory
        this(null);
    }

    public MyThemeResourceProvider(KeycloakSession session) {
        this.session = session;
    }

    @Override
    public ThemeResourceProvider create(KeycloakSession session) {
        return new MyThemeResourceProvider(session);
    }

    @Override
    public URL getTemplate(String s) {
        // returns null because we don't have any custom template to load
        return null;
    }

    @Override
    public InputStream getResourceAsStream(String s)  {
        // returns null because we don't have any custom resource to load like css or js
        return null;
    }

    @Override
    public void init(org.keycloak.Config.Scope scope) {
        // first initialization call
    }

    @Override
    public void postInit(KeycloakSessionFactory sessionFactory) {
        // last initialization call, now with access to the session factory
    }

    @Override
    public String getId() {
        return ID;
    }

    @Override
    public void close() {
        // releases any resource you might have created during the initialization phase
    }

    @Override
    public int order() {
        // the order is used to prioritize a provider over others
        // in this case, we are overriding the default theme selector with this implementation
        // another way to configure this provider is changing the server configuration
        return 100;
    }
}


================================================
FILE: ch13/themes/mytheme/src/main/java/org/keycloak/book/ch13/theme/MyThemeSelectorProvider.java
================================================
package org.keycloak.book.ch13.theme;

import javax.ws.rs.core.MultivaluedMap;

import org.keycloak.models.KeycloakContext;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.KeycloakUriInfo;
import org.keycloak.theme.DefaultThemeSelectorProvider;
import org.keycloak.theme.Theme;
import org.keycloak.theme.ThemeSelectorProvider;
import org.keycloak.theme.ThemeSelectorProviderFactory;

/**
 * <p>A very simple demonstration about how to customize theme selection at runtime.
 *
 * <p>You can use this example as a baseline, but never for production.
 */
public class MyThemeSelectorProvider extends DefaultThemeSelectorProvider
        implements ThemeSelectorProviderFactory {

    private static final String ID = "mythemeselector";

    private final KeycloakSession session;

    public MyThemeSelectorProvider() {
        // for reflection when registering the factory
        this(null);
    }

    public MyThemeSelectorProvider(KeycloakSession session) {
        super(session);
        this.session = session;
    }

    @Override
    public String getThemeName(Theme.Type type) {
        // an example on how to use a theme selector to choose a team at runtime
        // not targeted for production
        String theme = getThemeParameter();

        if (theme == null || !Theme.Type.LOGIN.equals(type)) {
            return super.getThemeName(type);
        }

        return theme;
    }

    @Override
    public ThemeSelectorProvider create(KeycloakSession session) {
        return new MyThemeSelectorProvider(session);
    }

    @Override
    public void init(org.keycloak.Config.Scope scope) {
        // first initialization call
    }

    @Override
    public void postInit(KeycloakSessionFactory keycloakSessionFactory) {
        // last initialization call, now with access to the session factory
    }

    @Override
    public String getId() {
        return ID;
    }

    @Override
    public void close() {
        // releases any resource you might have created during the initialization phase
    }

    @Override
    public int order() {
        // the order is used to prioritize a provider over others
        // in this case, we are overriding the default theme selector with this implementation
        // another way to configure this provider is changing the server configuration
        return 100;
    }

    private String getThemeParameter() {
        KeycloakContext context = session.getContext();
        KeycloakUriInfo uri = context.getUri();
        MultivaluedMap<String, String> parameters = uri.getQueryParameters();
        return parameters.getFirst("theme");
    }
}


================================================
FILE: ch13/themes/mytheme/src/main/resources/META-INF/jboss-deployment-structure.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<jboss-deployment-structure>
    <deployment>
        <dependencies>
            <module name="org.keycloak.keycloak-services" />
        </dependencies>
    </deployment>
</jboss-deployment-structure>

================================================
FILE: ch13/themes/mytheme/src/main/resources/META-INF/keycloak-themes.json
================================================
{
  "themes": [{
    "name" : "mytheme",
    "types": [ "login" ]
  }]
}

================================================
FILE: ch13/themes/mytheme/src/main/resources/META-INF/services/org.keycloak.theme.ThemeResourceProviderFactory
================================================
org.keycloak.book.ch13.theme.MyThemeResourceProvider

================================================
FILE: ch13/themes/mytheme/src/main/resources/META-INF/services/org.keycloak.theme.ThemeSelectorProviderFactory
================================================
org.keycloak.book.ch13.theme.MyThemeSelectorProvider

================================================
FILE: ch13/themes/mytheme/src/main/resources/theme/mytheme/login/resources/css/signin.css
================================================
html,
.login-page {
  height: 100%;
}

body {
  display: flex;
  align-items: center;
  padding-top: 40px;
  padding-bottom: 40px;
  background-color: #ffffff;
}

.form-signin {
  width: 100%;
  max-width: 500px;
  height: 100%;
  padding: 15px;
  margin: auto;
  background-color: #ffffff;
}

.form-signin .checkbox {
  font-weight: 400;
}

.form-signin .form-floating:focus-within {
  z-index: 2;
}

.form-signin input[type="text"] {
  margin-bottom: 10px;
  border-bottom-right-radius: 0;
  border-bottom-left-radius: 0;
  font-size: 20px;
}

.form-signin input[type="password"] {
  margin-bottom: 10px;
  border-top-left-radius: 0;
  border-top-right-radius: 0;
  font-size: 20px;
}

#kc-header-wrapper {
  font-size: font-size: 2.90rem !important;
}

.login-card {
    margin: 0 auto;
    box-shadow: var(--pf-global--BoxShadow--lg);
    padding: 0 20px;
    max-width: 100%;
    height: 100%;
    border-color: #0066CC;
    border-color: var(--pf-global--primary-color--100);
    border-radius: 25px;
}

.login-header {
    margin-bottom: 20px !important;
    display: -ms-flexbox !important;
    display: flex !important;
    -ms-flex-direction: column !important;
    flex-direction: column !important;
    color: #000000 !important;
    background-color: #ffffff;
}

.locale-section {
    width: 100%;
}

.locale-dropdown {
    color: #000000 !important;
    margin-top: 10px;
}

.login-signup {

}

.form-label {
    padding: 0px 0px 0px 0px !important;
    position: relative !important;
    color: #000000;
}

.login-info {
    background-color: !important;
    font-size: 20px !important;
    padding: 15px 137px !important;
    background-color: #ffffff !important;
    margin: 10px 20px 10px;
    font-family: inherit;
    color: #000000;
    border-radius: 25px;
}

a {
  color: #000000;
}



================================================
FILE: ch13/themes/mytheme/src/main/resources/theme/mytheme/login/theme.properties
================================================
# Inherit resources and messages from the keycloak
parent=keycloak

# Define the CSS styles
styles=css/login.css css/bootstrap.min.css css/signin.css

meta=viewport==width=device-width,initial-scale=1

# Mapping styles from Keycloak to those defined in the custom css.
kcHtmlClass=login-page
kcLoginClass=form-signin
kcHeaderClass=login-header
kcLabelClass=form-label
kcFormCardClass=login-card
kcSignUpClass=login-signup
kcFormClass=form-signin
kcInputClass=form-control
kcButtonDefaultClass=w-100 btn btn-lg btn-primary
kcButtonClass=w-100 btn btn-lg btn-primary
kcFormGroupClass=form-floating
kcInfoAreaWrapperClass=login-info
kcFormHeaderClass=h3 mb-3 fw-normal
kcLocaleClass=pf-c-dropdown
kcLocaleMainClass=pf-c-dropdown locale-section
kcLocaleListClass=pf-c-dropdown__menu pf-m-align-right
kcLocaleItemClass=pf-c-dropdown__menu-item
kcLocaleDropDownClass=locale-dropdown


================================================
FILE: ch2/backend/Dockerfile
================================================
FROM node
COPY package.json .
RUN npm install
COPY app.js .
COPY keycloak.json .
EXPOSE 3000
CMD [ "npm", "start" ]

================================================
FILE: ch2/backend/app.js
================================================
var express = require('express');
var session = require('express-session');
var Keycloak = require('keycloak-connect');
var cors = require('cors');

var app = express();

app.use(cors());

var memoryStore = new session.MemoryStore();

app.use(session({
  secret: 'some secret',
  resave: false,
  saveUninitialized: true,
  store: memoryStore
}));

var keycloak = new Keycloak({ store: memoryStore });

app.use(keycloak.middleware());

app.get('/secured', keycloak.protect('realm:myrole'), function (req, res) {
  res.setHeader('content-type', 'text/plain');
  res.send('Secret message!');
});

app.get('/public', function (req, res) {
  res.setHeader('content-type', 'text/plain');
  res.send('Public message!');
});

app.get('/', function (req, res) {
  res.send('<html><body><ul><li><a href="/public">Public endpoint</a></li><li><a href="/secured">Secured endpoint</a></li></ul></body></html>');
});

app.listen(3000, function () {
  console.log('Started at port 3000');
});

================================================
FILE: ch2/backend/keycloak.json
================================================
{
  "realm": "myrealm",
  "bearer-only": true,
  "auth-server-url": "${env.KC_URL:http://localhost:8080/auth}",
  "resource": "service-nodejs"
}

================================================
FILE: ch2/backend/package.json
================================================
{
  "name": "keycloak-example-service",
  "version": "0.0.1",
  "scripts": {
    "start": "node app.js"
  },
  "dependencies": {
    "keycloak-connect": "^11.0.0",
    "express": "^4.17.1",
    "express-session": "^1.17.1",
    "cors": "^2.8.5"
  }
}


================================================
FILE: ch2/frontend/.mvn/wrapper/MavenWrapperDownloader.java
================================================
/*
 * 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();
    }

}


================================================
FILE: ch2/frontend/.mvn/wrapper/maven-wrapper.properties
================================================
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


================================================
FILE: ch2/frontend/Dockerfile
================================================
FROM node
COPY package.json .
RUN npm install
COPY app.js .
COPY index.html .
EXPOSE 8000
CMD [ "npm", "start" ]

================================================
FILE: ch2/frontend/app.js
================================================
var express = require('express');
var app = express();
var stringReplace = require('string-replace-middleware');

var KC_URL = process.env.KC_URL || "http://localhost:8080/auth";
var SERVICE_URL = process.env.SERVICE_URL || "http://localhost:3000/secured";

app.use(stringReplace({
   'SERVICE_URL': SERVICE_URL,
   'KC_URL': KC_URL
}));
app.use(express.static('.'))

app.get('/', function(req, res) {
    res.render('index.html');
});

app.get('/client.js', function(req, res) {
    res.render('client.js');
});

app.listen(8000);

================================================
FILE: ch2/frontend/index.html
================================================
<!DOCTYPE html>
<html>
<head>
    <title>Keycloak Example Application</title>
    <script type="text/javascript" src="KC_URL/js/keycloak.js"></script>
    <script type="text/javascript">
        function output(content) {
            if (typeof content === 'object') {
                content = JSON.stringify(content, null, 2)
            }
            document.getElementById('output').textContent = content;
        }

        function profile() {
            if (kc.idTokenParsed.name) {
                document.getElementById('name').textContent = 'Hello ' + kc.idTokenParsed.name;
            } else {
                document.getElementById('name').textContent = 'Hello ' + kc.idTokenParsed.preferred_username;
            }
            if (kc.idTokenParsed.picture) {
                document.getElementById('picture').src = kc.idTokenParsed.picture;
            }
            document.getElementById('user').style.display = 'block';
        }

        function sendRequest() {
            var req = new XMLHttpRequest();
            req.onreadystatechange = function() {
                if (req.readyState === 4) {
                    output(req.status + '\n\n' + req.responseText);
                }
            }
            req.open('GET', 'SERVICE_URL', true);
            req.setRequestHeader('Authorization', 'Bearer ' + kc.token);
            req.send();
        }

        var kc = new Keycloak({ realm: 'myrealm', clientId: 'myclient' });
        window.onload = function() {
            kc.init({'messageReceiveTimeout': 100000}).then(function() {
                if(kc.authenticated) {
                    profile();
                } else {
                    document.getElementById('anonymous').style.display = 'block';
                }
            });
        }
    </script>
</head>
<body>

<div id="anonymous" style="display: none">
    <button onclick="window.kc.login()">Login</button>
</div>

<div id="user" style="display: none">
    <button onclick="window.kc.logout()">Logout</button>
    <button onclick="output(kc.idTokenParsed)">Show ID Token</button>
    <button onclick="output(kc.tokenParsed)">Show Access Token</button>
    <button onclick="window.kc.updateToken(-1).then(function() { output(kc.idTokenParsed); profile() })">Refresh</button>
    <button onclick="sendRequest()">Invoke Service</button>
    <hr/>
    <h2 id="name"></h2>
    <img id="picture" width="50px" height="50px"/>
    <hr/>
    <pre id="output"></pre>
</div>



</body>
</html>


================================================
FILE: ch2/frontend/package.json
================================================
{
  "name": "keycloak-example-app",
  "version": "0.0.1",
  "scripts": {
    "start": "node app.js"
  },
  "dependencies": {
    "express": "^4.17.1",
    "start": "^5.1.0",
    "string-replace-middleware": "^1.0.2"
  }
}


================================================
FILE: ch4/Dockerfile
================================================
FROM node
COPY package.json .
RUN npm install
COPY app.js .
COPY index.html .
COPY styles.css .
COPY client.js .
EXPOSE 8000
CMD [ "npm", "start" ]


================================================
FILE: ch4/app.js
================================================
var express = require('express');
var app = express();
var stringReplace = require('string-replace-middleware');

var KC_URL = process.env.KC_URL || "http://localhost:8080/auth";
var SERVICE_URL = process.env.SERVICE_URL || "http://localhost:3000/secured";

app.use(stringReplace({
   'SERVICE_URL': SERVICE_URL,
   'KC_URL': KC_URL
}));
app.use(express.static('.'))

app.get('/', function(req, res) {
    res.render('index.html');
});


app.listen(8000);

================================================
FILE: ch4/client.js
================================================
/****************************/
/* OpenID Connect functions */
/****************************/

// Load the OpenID Provider Configuration
function loadDiscovery() {
    var issuer = getInput('input-issuer');
    setState('issuer', issuer);

    var req = new XMLHttpRequest();
    req.onreadystatechange = function() {
        if (req.readyState === 4) {
            setState('discovery', JSON.parse(req.responseText));
            setOutput('output-discovery', state.discovery);
        }
    }
    req.open('GET', issuer + '/.well-known/openid-configuration', true);
    req.send();
}

// Create an Authentication Request
function generateAuthenticationRequest() {
    var req = state.discovery['authorization_endpoint'];

    var clientId = getInput('input-clientid');
    var scope = getInput('input-scope');
    var prompt = getInput('input-prompt');
    var maxAge = getInput('input-maxage');
    var loginHint = getInput('input-loginhint');

    var authenticationInput = {
        clientId: clientId,
        scope: scope,
        prompt: prompt,
        maxAge: maxAge,
        loginHint: loginHint
    }
    setState('authenticationInput', authenticationInput);

    req += '?client_id=' + clientId;
    req += '&response_type=code';
    req += '&redirect_uri=' + document.location.href.split('?')[0];
    if ('' !== scope) {
        req += '&scope=' + scope;
    }
    if ('' !== prompt) {
        req += '&prompt=' + prompt;
    }
    if ('' !== maxAge) {
        req += '&max_age=' + maxAge;
    }
    if ('' !== loginHint) {
        req += '&login_hint=' + loginHint;
    }

    setOutput('output-authenticationRequest', req.replace('?', '<br/><br/>').replaceAll('&', '<br/>'));
    document.getElementById('authenticationRequestLink').onclick = function() {
        document.location.href = req;
    }
}

// Create a Token Exchange Request
function loadTokens() {
    var code = getInput('input-code');
    var clientId = getInput('input-clientid');

    var params = 'grant_type=authorization_code';
    params += '&code=' + code;
    params += '&client_id=' + clientId;
    params += '&redirect_uri=' + document.location.href.split('?')[0];

    var req = new XMLHttpRequest();
    req.onreadystatechange = function() {
        if (req.readyState === 4) {
            var response = JSON.parse(req.responseText);
            setOutput('output-response', req.responseText);

            if (response['id_token']) {
                var idToken = response['id_token'].split('.');
                var idTokenHeader = JSON.parse(base64UrlDecode(idToken[0]));
                var idTokenBody = JSON.parse(base64UrlDecode(idToken[1]));
                var idTokenSignature = idToken[2];
                setOutput('output-idtokenHeader', idTokenHeader);
                setOutput('output-idtoken', idTokenBody);
                setOutput('output-idtokenSignature', idTokenSignature);
                setState('refreshToken', response['refresh_token']);
                setState('idToken', response['id_token']);
                setState('accessToken', response['access_token']);
            } else {
                setOutput('output-idtoken', '');
            }
        }
    }
    req.open('POST', state.discovery['token_endpoint'], true);
    req.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');

    setOutput('output-tokenRequest', state.discovery['token_endpoint'] + '<br/><br/>' + params.replaceAll('&', '<br/>'));

    req.send(params);

    window.history.pushState({}, document.title, '/');
}

// Create a Refresh Token Request
function refreshTokens() {
    var code = getInput('input-code');
    var clientId = getInput('input-clientid');

    var params = 'grant_type=refresh_token';
    params += '&refresh_token=' + state.refreshToken;
    params += '&client_id=' + clientId;
    params += '&scope=openid';

    var req = new XMLHttpRequest();
    req.onreadystatechange = function() {
        if (req.readyState === 4) {
            var response = JSON.parse(req.responseText);
            setOutput('output-refreshResponse', req.responseText);

            if (response['id_token']) {
                var idToken = JSON.parse(base64UrlDecode(response['id_token'].split('.')[1]));
                setOutput('output-idtokenRefreshed', idToken);
                setState('refreshToken', response['refresh_token']);
            } else {
                setOutput('output-idtokenRefreshed', '');
            }
        }
    }
    req.open('POST', state.discovery['token_endpoint'], true);
    req.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');

    setOutput('output-refreshRequest', state.discovery['token_endpoint'] + '<br/><br/>' + params.replaceAll('&', '<br/>'));

    req.send(params);

    window.history.pushState({}, document.title, '/');
}

// Create a UserInfo Request
function userInfo() {
    var req = new XMLHttpRequest();
    req.onreadystatechange = function() {
        if (req.readyState === 4) {
            var response = JSON.parse(req.responseText);
            setOutput('output-userInfoResponse', req.responseText);
        }
    }
    req.open('GET', state.discovery['userinfo_endpoint'], true);
    req.setRequestHeader('Authorization', 'Bearer ' + state.accessToken);

    setOutput('output-userInfoRequest', state.discovery['userinfo_endpoint'] + '<br/><br/>' + 'Authorization: Bearer ' + state.accessToken);

    req.send();

    window.history.pushState({}, document.title, '/');
}

/*************************/
/* Application functions */
/*************************/

var steps = ['discovery', 'authentication', 'token', 'refresh', 'userinfo']
var state = loadState();

function reset() {
    localStorage.removeItem('state');
    window.location.reload();
}

function loadState() {
   var s = localStorage.getItem('state');
   if (s) {
       return JSON.parse(s);
   } else {
       return {
           step: 'discovery'
       }
   }
}

function setState(key, value) {
    state[key] = value;
    localStorage.setItem('state', JSON.stringify(state));
}

function step(step) {
    setState('step', step);
    for (i = 0; i < steps.length; i++) {
        document.getElementById('step-' + steps[i]).style.display = steps[i] === step ? 'block' : 'none'
    }
    setState('step', step);

    switch(step) {
        case 'discovery':
            if (state.issuer) {
                setInput('input-issuer', state.issuer);
            }
            break;
        case 'authentication':
            var authenticationInput = state.authenticationInput;
            if (authenticationInput) {
                setInput('input-clientid', authenticationInput.clientId);
                setInput('input-scope', authenticationInput.scope);
                setInput('input-prompt', authenticationInput.prompt);
                setInput('input-maxage', authenticationInput.maxAge);
                setInput('input-loginhint', authenticationInput.loginHint);
                setOutput('output-authenticationResponse', '');
            }
            break;
    }
}

function getInput(id) {
    return document.getElementById(id).value
}

function setInput(id, value) {
    return document.getElementById(id).value = value
}

function setOutput(id, value) {
    if (typeof value === 'object') {
        value = JSON.stringify(value, null, 2)
    } else if (value.startsWith('{')) {
        value = JSON.stringify(JSON.parse(value), null, 2)
    }
    document.getElementById(id).innerHTML = value;
}

function getQueryVariable(key) {
    var query = window.location.search.substring(1);
    var vars = query.split('&');
    for (var i = 0; i < vars.length; i++) {
        var pair = vars[i].split('=');
        if (decodeURIComponent(pair[0]) == key) {
            return decodeURIComponent(pair[1]);
        }
    }
}

function base64UrlDecode(input) {
    input = input
        .replace(/-/g, '+')
        .replace(/_/g, '/');

    var pad = input.length % 4;
    if(pad) {
      if(pad === 1) {
        throw new Error('InvalidLengthError: Input base64url string is the wrong length to determine padding');
      }
      input += new Array(5-pad).join('=');
    }

    return atob(input);
}

function init() {
    step(state.step);
    if (state.discovery) {
        setOutput('output-discovery', state.discovery);
    }

    var code = getQueryVariable('code');
    if (code) {
        setInput('input-code', code);
        setOutput('output-authenticationResponse', 'code=' + code);
    }

    var error = getQueryVariable('error');
    var errorDescription = getQueryVariable('error_description');
    if (error) {
        setOutput('output-authenticationResponse', 'error=' + error + '<br/>error_description=' + errorDescription);
    }
}

================================================
FILE: ch4/index.html
================================================
<!DOCTYPE html>
<html>
<head>
    <title>OpenID Connect Playground</title>
    <script type="text/javascript" src="KC_URL/js/keycloak.js"></script>
    <script type="text/javascript" src="client.js"></script>
    <link rel="stylesheet" href="styles.css">
</head>
<body onload="init()">

<h1>OpenID Connect Playground</h1>

<button onclick="step('discovery')">1 - Discovery</button>
<button onclick="step('authentication')">2 - Authentication</button>
<button onclick="step('token')">3 - Token</button>
<button onclick="step('refresh')">4 - Refresh</button>
<button onclick="step('userinfo')">5 - UserInfo</button>
<button onclick="reset()">Reset</button>

<hr/>

<div id="step-discovery" style="display: none;">
    <h2>Discovery</h2>
    <p>
        <label>Issuer</label><input id="input-issuer" value="http://localhost:8080/auth/realms/myrealm">
    </p>
    <p>
        <button onclick="loadDiscovery()">Load OpenID Provider Configuration</button>
    </p>
    <hr/>
    <h3>OpenID Provider Configuration</h3>
    <pre id="output-discovery"></pre>
</div>

<div id="step-authentication" style="display: none;">
    <h2>Authentication</h2>
    <p>
        <label>client_id</label><input id="input-clientid" value="oidc-playground"><br/>
        <label>scope</label><input id="input-scope" value="openid"><br/>
        <label>prompt</label><input id="input-prompt" value=""><br/>
        <label>max_age</label><input id="input-maxage" value=""><br/>
        <label>login_hint</label><input id="input-loginhint" value="">
    </p>
    <p>
        <button onclick="generateAuthenticationRequest()">Generate Authentication Request</button>
    </p>
    <hr/>
    <h3>Authentication Request</h3>
    <pre id="output-authenticationRequest"></pre>
    <button id="authenticationRequestLink">Send Authentication Request</button>

    <hr/>
    <h3>Authentication Response</h3>
    <pre id="output-authenticationResponse"></pre>
</div>

<div id="step-token" style="display: none;">
    <h2>Token</h2>
    <p>
        <label>Authorization Code</label><input id="input-code">
    </p>
    <p>
        <button onclick="loadTokens()">Send Token Request</button>
    </p>
    <hr/>
    <h3>Token Request</h3>
    <pre id="output-tokenRequest"></pre>

    <h3>Token Response</h3>
    <pre id="output-response"></pre>

    <h3>ID Token</h3>
    Header
    <pre id="output-idtokenHeader"></pre>
    Payload
    <pre id="output-idtoken"></pre>
    Signature
    <pre id="output-idtokenSignature"></pre>
</div>

<div id="step-refresh" style="display: none;">
    <h2>Refresh</h2>
    <p>
        <button onclick="refreshTokens()">Send Refresh Request</button>
    </p>
    <hr/>

    <h3>Refresh Request</h3>
    <pre id="output-refreshRequest"></pre>

    <h3>Refresh Response</h3>
    <pre id="output-refreshResponse"></pre>

    <h3>ID Token</h3>
    <pre id="output-idtokenRefreshed"></pre>
</div>

<div id="step-userinfo" style="display: none;">
    <h2>UserInfo</h2>
    <p>
        <button onclick="userInfo()">Send UserInfo Request</button>
    </p>
    <hr/>

    <h3>UserInfo Request</h3>
    <pre id="output-userInfoRequest"></pre>

    <h3>UserInfo Response</h3>
    <pre id="output-userInfoResponse"></pre>
</div>

</body>
</html>


================================================
FILE: ch4/package.json
================================================
{
  "name": "keycloak-example-app",
  "version": "0.0.1",
  "scripts": {
    "start": "node app.js"
  },
  "dependencies": {
    "express": "^4.17.1",
    "start": "^5.1.0",
    "string-replace-middleware": "^1.0.2"
  }
}


================================================
FILE: ch4/styles.css
================================================
body {
    font-family: sans;
}

label {
    display: inline-block;
    font-size: 14px;
    width: 150px;
}

input {
    width: 400px;
    margin-bottom: 10px;
}

pre {
    border: 1px solid #ccc;
    padding: 10px;
    background-color: #f0f0f0;
}

================================================
FILE: ch5/backend/Dockerfile
================================================
FROM node
COPY package.json .
RUN npm install
COPY app.js .
COPY keycloak.json .
EXPOSE 3000
CMD [ "npm", "start" ]

================================================
FILE: ch5/backend/app.js
================================================
var express = require('express');
var session = require('express-session');
var Keycloak = require('keycloak-connect');
var cors = require('cors');

var app = express();

app.use(cors());

var memoryStore = new session.MemoryStore();

app.use(session({
  secret: 'some secret',
  resave: false,
  saveUninitialized: true,
  store: memoryStore
}));

var keycloak = new Keycloak({ store: memoryStore });

app.use(keycloak.middleware());

app.get('/secured', keycloak.protect('realm:myrole'), function (req, res) {
  res.setHeader('content-type', 'text/plain');
  res.send('Secret message!');
});

app.get('/public', function (req, res) {
  res.setHeader('content-type', 'text/plain');
  res.send('Public message!');
});

app.get('/', function (req, res) {
  res.send('<html><body><ul><li><a href="/public">Public endpoint</a></li><li><a href="/secured">Secured endpoint</a></li></ul></body></html>');
});

app.listen(3000, function () {
  console.log('Started at port 3000');
});

================================================
FILE: ch5/backend/keycloak.json
================================================
{
  "realm": "myrealm",
  "bearer-only": true,
  "auth-server-url": "${env.KC_URL:http://localhost:8080/auth}",
  "resource": "oauth-backend",
  "verify-token-audience": false
}


================================================
FILE: ch5/backend/package.json
================================================
{
  "name": "keycloak-example-service",
  "version": "0.0.1",
  "scripts": {
    "start": "node app.js"
  },
  "dependencies": {
    "keycloak-connect": "^11.0.0",
    "express": "^4.17.1",
    "express-session": "^1.17.1",
    "cors": "^2.8.5"
  }
}


================================================
FILE: ch5/frontend/Dockerfile
================================================
FROM node
COPY package.json .
RUN npm install
COPY app.js .
COPY index.html .
COPY styles.css .
COPY client.js .
EXPOSE 8000
CMD [ "npm", "start" ]


================================================
FILE: ch5/frontend/app.js
================================================
var express = require('express');
var app = express();
var stringReplace = require('string-replace-middleware');

var KC_URL = process.env.KC_URL || "http://localhost:8080/auth";
var SERVICE_URL = process.env.SERVICE_URL || "http://localhost:3000/secured";

app.use(stringReplace({
   'SERVICE_URL': SERVICE_URL,
   'KC_URL': KC_URL
}));
app.use(express.static('.'))

app.get('/', function(req, res) {
    res.render('index.html');
});


app.listen(8000);

================================================
FILE: ch5/frontend/client.js
================================================
/***********************/
/* OAuth 2.0 functions */
/***********************/

// Load the OpenID Provider Configuration
function loadDiscovery() {
    var issuer = getInput('input-issuer');
    setState('issuer', issuer);

    var req = new XMLHttpRequest();
    req.onreadystatechange = function() {
        if (req.readyState === 4) {
            setState('discovery', JSON.parse(req.responseText));
            setOutput('output-discovery', state.discovery);
        }
    }
    req.open('GET', issuer + '/.well-known/openid-configuration', true);
    req.send();
}

// Create an Authorization Request
function generateAuthorizationRequest() {
    var req = state.discovery['authorization_endpoint'];

    var clientId = getInput('input-clientid');
    var scope = getInput('input-scope');

    var authorizationInput = {
        clientId: clientId,
        scope: scope
    }
    setState('authorizationInput', authorizationInput);

    req += '?client_id=' + clientId;
    req += '&response_type=code';
    req += '&redirect_uri=' + document.location.href.split('?')[0];
    if ('' !== scope) {
        req += '&scope=' + scope;
    }

    document.location.href = req;
}

// Create a Token Exchange Request
function loadTokens(code) {
    var clientId = getInput('input-clientid');

    var params = 'grant_type=authorization_code';
    params += '&code=' + code;
    params += '&client_id=' + clientId;
    params += '&redirect_uri=' + document.location.href.split('?')[0];

    var req = new XMLHttpRequest();
    req.onreadystatechange = function() {
        if (req.readyState === 4) {
            var response = JSON.parse(req.responseText);

            if (response['access_token']) {
                var accessToken = response['access_token'].split('.');
                var accessTokenHeader = JSON.parse(base64UrlDecode(accessToken[0]));
                var accessTokenBody = JSON.parse(base64UrlDecode(accessToken[1]));
                var accessTokenSignature = accessToken[2];
                setOutput('output-accessTokenHeader', accessTokenHeader);
                setOutput('output-accessToken', accessTokenBody);
                setOutput('output-accessTokenSignature', accessTokenSignature);
                document.getElementById('output-accessTokenEncoded').innerHTML = response['access_token'];
                setState('refreshToken', response['refresh_token']);
                setState('accessToken', response['access_token']);
            } else {
                setOutput('output-accessToken', '');
            }
        }
    }
    req.open('POST', state.discovery['token_endpoint'], true);
    req.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');

    req.send(params);

    window.history.pushState({}, document.title, '/');
}

// Create a Service Request
function invokeService() {
    var req = new XMLHttpRequest();
    req.onreadystatechange = function() {
        if (req.readyState === 4) {
            if (req.status === 0) {
                setOutput('output-serviceResponse', "Failed to send request");
            } else {
                setOutput('output-serviceResponse', req.responseText);
            }
        }
    }
    console.debug(serviceUrl);
    req.open('GET', serviceUrl, true);
    req.setRequestHeader('Authorization', 'Bearer ' + state.accessToken);

    req.send();

    window.history.pushState({}, document.title, '/');
}

/*************************/
/* Application functions */
/*************************/

var steps = ['discovery', 'authorization', 'invoke']
var state = loadState();

function reset() {
    localStorage.removeItem('state');
    window.location.reload();
}

function loadState() {
   var s = localStorage.getItem('state');
   if (s) {
       return JSON.parse(s);
   } else {
       return {
           step: 'discovery'
       }
   }
}

function setState(key, value) {
    state[key] = value;
    localStorage.setItem('state', JSON.stringify(state));
}

function step(step) {
    setState('step', step);
    for (i = 0; i < steps.length; i++) {
        document.getElementById('step-' + steps[i]).style.display = steps[i] === step ? 'block' : 'none'
    }
    setState('step', step);

    switch(step) {
        case 'discovery':
            if (state.issuer) {
                setInput('input-issuer', state.issuer);
            }
            break;
        case 'authorization':
            var authorizationInput = state.authorizationInput;
            if (authorizationInput) {
                setInput('input-clientid', authorizationInput.clientId);
                setInput('input-scope', authorizationInput.scope);
            }
            break;
    }
}

function getInput(id) {
    return document.getElementById(id).value
}

function setInput(id, value) {
    return document.getElementById(id).value = value
}

function setOutput(id, value) {
    if (typeof value === 'object') {
        value = JSON.stringify(value, null, 2)
    } else if (value.startsWith('{')) {
        value = JSON.stringify(JSON.parse(value), null, 2)
    }
    document.getElementById(id).innerHTML = value;
}

function getQueryVariable(key) {
    var query = window.location.search.substring(1);
    var vars = query.split('&');
    for (var i = 0; i < vars.length; i++) {
        var pair = vars[i].split('=');
        if (decodeURIComponent(pair[0]) == key) {
            return decodeURIComponent(pair[1]);
        }
    }
}

function base64UrlDecode(input) {
    input = input
        .replace(/-/g, '+')
        .replace(/_/g, '/');

    var pad = input.length % 4;
    if(pad) {
      if(pad === 1) {
        throw new Error('InvalidLengthError: Input base64url string is the wrong length to determine padding');
      }
      input += new Array(5-pad).join('=');
    }

    return atob(input);
}

function init() {
    step(state.step);
    if (state.discovery) {
        setOutput('output-discovery', state.discovery);
    }

    var code = getQueryVariable('code');
    if (code) {
        loadTokens(code);
    }

    var error = getQueryVariable('error');
    var errorDescription = getQueryVariable('error_description');
    if (error) {
        setOutput('output-authorizationResponse', 'error=' + error + '<br/>error_description=' + errorDescription);
    }
}

================================================
FILE: ch5/frontend/index.html
================================================
<!DOCTYPE html>
<html>
<head>
    <title>OAuth 2.0 Playground</title>
    <script type="text/javascript" src="KC_URL/js/keycloak.js"></script>
    <script type="text/javascript">
        var serviceUrl = 'SERVICE_URL';
    </script>
    <script type="text/javascript" src="client.js"></script>
    <link rel="stylesheet" href="styles.css">
</head>
<body onload="init()">

<h1>OAuth 2.0 Playground</h1>

<button onclick="step('discovery')">1 - Discovery</button>
<button onclick="step('authorization')">2 - Authorization</button>
<button onclick="step('invoke')">3 - Invoke Service</button>
<button onclick="reset()">Reset</button>

<hr/>

<div id="step-discovery" style="display: none;">
    <h2>Discovery</h2>
    <p>
        <label>Issuer</label><input id="input-issuer" value="http://localhost:8080/auth/realms/myrealm">
    </p>
    <p>
        <button onclick="loadDiscovery()">Load OAuth 2.0 Provider Configuration</button>
    </p>
    <hr/>
    <h3>OAuth 2.0 Provider Configuration</h3>
    <pre id="output-discovery"></pre>
</div>

<div id="step-authorization" style="display: none;">
    <h2>Authorization</h2>
    <p>
        <label>client_id</label><input id="input-clientid" value="oauth-playground"><br/>
        <label>scope</label><input id="input-scope" value=""><br/>
    </p>
    <p>
        <button onclick="generateAuthorizationRequest()">Send Authorization Request</button>
    </p>

    <hr/>
    <h3>Access Token</h3>
    Header
    <pre id="output-accessTokenHeader"></pre>
    Payload
    <pre id="output-accessToken"></pre>
    Signature
    <pre id="output-accessTokenSignature"></pre>
    Encoded
    <pre id="output-accessTokenEncoded"></pre>
</div>

<div id="step-refresh" style="display: none;">
    <h2>Refresh</h2>
    <p>
        <button onclick="refreshTokens()">Send Refresh Request</button>
    </p>
    <hr/>

    <h3>Refresh Request</h3>
    <pre id="output-refreshRequest"></pre>

    <h3>Refresh Response</h3>
    <pre id="output-refreshResponse"></pre>

    <h3>Access Token</h3>
    <pre id="output-accessTokenRefreshed"></pre>
</div>

<div id="step-invoke" style="display: none;">
    <h2>Invoke Service</h2>
    <p>
        <button onclick="invokeService()">Invoke</button>
    </p>
    <hr/>

    <h3>Response</h3>
    <pre id="output-serviceResponse"></pre>
</div>

</body>
</html>


================================================
FILE: ch5/frontend/package.json
================================================
{
  "name": "keycloak-example-app",
  "version": "0.0.1",
  "scripts": {
    "start": "node app.js"
  },
  "dependencies": {
    "express": "^4.17.1",
    "start": "^5.1.0",
    "string-replace-middleware": "^1.0.2"
  }
}


================================================
FILE: ch5/frontend/styles.css
================================================
body {
    font-family: sans;
}

label {
    display: inline-block;
    font-size: 14px;
    width: 150px;
}

input {
    width: 400px;
    margin-bottom: 10px;
}

pre {
    border: 1px solid #ccc;
    padding: 10px;
    background-color: #f0f0f0;
}

================================================
FILE: ch6/app.js
================================================
var express = require('express');
var open = require('open');
var axios = require('axios');
var querystring = require('querystring');

var app = express();
app.use(express.static('callback'));

var server = app.listen(0);
var port = server.address().port;

console.info('Listening on port: ' + port + '\n');

app.get('/callback/', function(req, res) {
    res.send('<html><script>window.close();</script><body>Completed, please close this tab</body></html>');
    var code = req.query.code;
    server.close();

    console.info('Authorization Code: ' + code + '\n');

    axios.post('http://localhost:8080/auth/realms/myrealm/protocol/openid-connect/token', querystring.stringify({
        client_id: 'cli',
        grant_type: 'authorization_code',
        redirect_uri: 'http://localhost:' + port + '/callback',
        code: code
    })).then(res => {
        console.log('Access Token: ' + res.data.access_token + '\n');
    }).catch(error => {
        console.error(error);
    });
});

open('http://localhost:8080/auth/realms/myrealm/protocol/openid-connect/auth?client_id=cli&redirect_uri=http://localhost:' + port + '/callback&response_type=code');

================================================
FILE: ch6/package.json
================================================
{
  "name": "keycloak-example-app",
  "version": "0.0.1",
  "scripts": {
    "start": "node app.js"
  },
  "dependencies": {
    "express": "^4.17.1",
    "open": "^7.3.0",
    "axios": "*"
  }
}


================================================
FILE: ch7/golang/go.mod
================================================
module github.com/abourget/getting-started-with-golang

go 1.12

require (
	github.com/coreos/go-oidc v2.2.1+incompatible
	github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect
	github.com/google/uuid v1.2.0
	github.com/pquerna/cachecontrol v0.0.0-20201205024021-ac21108117ac // indirect
	golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 // indirect
	golang.org/x/net v0.0.0-20210119194325-5f4716e94777
	golang.org/x/oauth2 v0.0.0-20210210192628-66670185b0cd
	google.golang.org/protobuf v1.25.0 // indirect
	gopkg.in/square/go-jose.v2 v2.5.1 // indirect
)


================================================
FILE: ch7/golang/go.sum
================================================
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/coreos/go-oidc v2.2.1+incompatible h1:mh48q/BqXqgjVHpy2ZY7WnWAbenxRjsz9N1i1YxjHAk=
github.com/coreos/go-oidc v2.2.1+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs=
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pquerna/cachecontrol v0.0.0-20201205024021-ac21108117ac h1:jWKYCNlX4J5s8M0nHYkh7Y7c9gRVDEb3mq51j5J0F5M=
github.com/pquerna/cachecontrol v0.0.0-20201205024021-ac21108117ac/go.mod h1:hoLfEwdY11HjRfKFH6KqnPsfxlo3BP6bJehpDv8t6sQ=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20210119194325-5f4716e94777 h1:003p0dJM77cxMSyCPFphvZf/Y5/NXf5fzg6ufd1/Oew=
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20210210192628-66670185b0cd h1:2arJsLyTCJGek+eeptQ3z49Rqndm0f+zvvpwNIXWNIA=
golang.org/x/oauth2 v0.0.0-20210210192628-66670185b0cd/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/square/go-jose.v2 v2.5.1 h1:7odma5RETjNHWJnR32wx8t+Io4djHE1PqxCFx3iiZ2w=
gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=


================================================
FILE: ch7/golang/main.go
================================================
/*
 This is an example about how to use a public client written in Golang to authenticate using Keycloak.
 This example is only for demonstration purposes and lacks important 
*/
package main

import (
	"encoding/json"
	"errors"
	"log"
	"net/http"
	"time"

	oidc "github.com/coreos/go-oidc"
	"github.com/google/uuid"
	"golang.org/x/net/context"
	"golang.org/x/oauth2"
)

var oidcProvider oidc.Provider
var oidcConfig oidc.Config
var oauth2Config oauth2.Config
var idTokenVerifier oidc.IDTokenVerifier

func init() {
	oidcProvider = *createOidcProvider(context.Background())
	oidcConfig, oauth2Config = createConfig(oidcProvider)
	idTokenVerifier = *oidcProvider.Verifier(&oidcConfig)
}

func createOidcProvider(ctx context.Context) *oidc.Provider {
	provider, err := oidc.NewProvider(ctx, "http://localhost:8180/auth/realms/myrealm")

	if err != nil {
		log.Fatal("Failed to fetch discovery document: ", err)
	}

	return provider
}

func createConfig(provider oidc.Provider) (oidc.Config, oauth2.Config) {
	oidcConfig := &oidc.Config{
		ClientID: "mywebapp",
	}

	config := oauth2.Config{
		ClientID:    oidcConfig.ClientID,
		ClientSecret: "CLIENT_SECRET", // change CLIENT_SECRET with the secret generated by Keycloak for the mywebapp client. This option is a string.
		Endpoint:    provider.Endpoint(),
		RedirectURL: "http://localhost:8080/auth/callback",
		Scopes:      []string{oidc.ScopeOpenID, "profile", "email"},
	}

	return *oidcConfig, config
}

func main() {
	http.HandleFunc("/", redirectHandler)
	http.HandleFunc("/auth/callback", callbackHandler)

	log.Printf("To authenticate go to http://%s/", "localhost:8080")
	log.Fatal(http.ListenAndServe("localhost:8080", nil))
}

func redirectHandler(resp http.ResponseWriter, r *http.Request) {
	state := addStateCookie(resp)
	http.Redirect(resp, r, oauth2Config.AuthCodeURL(state), http.StatusFound)
}

func callbackHandler(resp http.ResponseWriter, req *http.Request) {
	err := checkStateAndExpireCookie(req, resp)

	if err != nil {
		redirectHandler(resp, req)
		return
	}

	tokenResponse, err := exchangeCode(req)

	if err != nil {
		http.Error(resp, "Failed to exchange code", http.StatusBadRequest)
		return
	}

	idToken, err := validateIDToken(tokenResponse, req)

	if err != nil {
		http.Error(resp, "Failed to validate id_token", http.StatusUnauthorized)
		return
	}

	handleSuccessfulAuthentication(tokenResponse, *idToken, resp)
}

func addStateCookie(resp http.ResponseWriter) string {
	expire := time.Now().Add(1 * time.Minute)
	value := uuid.New().String()

	cookie := http.Cookie{
		Name:     "p_state",
		Value:    value,
		Expires:  expire,
		HttpOnly: true,
	}

	http.SetCookie(resp, &cookie)

	return value
}

func expireCookie(name string, resp http.ResponseWriter) {
	cookie := &http.Cookie{
		Name:     "p_state",
		Value:    "",
		MaxAge:   -1,
		HttpOnly: true,
	}

	http.SetCookie(resp, cookie)
}

func checkStateAndExpireCookie(req *http.Request, resp http.ResponseWriter) error {
	state, err := req.Cookie("p_state")

	expireCookie("p_state", resp)

	if err != nil {
		return errors.New("state cookie not set")
	}

	if req.URL.Query().Get("state") != state.Value {
		return errors.New("invalid state")
	}

	return nil
}

func exchangeCode(req *http.Request) (*oauth2.Token, error) {
	httpClient := &http.Client{Timeout: 2 * time.Second}
	ctx := context.WithValue(req.Context(), oauth2.HTTPClient, httpClient)

	tokenResponse, err := oauth2Config.Exchange(ctx, req.URL.Query().Get("code"))

	if err != nil {
		return nil, err
	}

	return tokenResponse, nil
}

func validateIDToken(tokenResponse *oauth2.Token, req *http.Request) (*oidc.IDToken, error) {
	rawIDToken, ok := tokenResponse.Extra("id_token").(string)

	if !ok {
		return nil, errors.New("id_token is not in the token response")
	}

	idToken, err := idTokenVerifier.Verify(req.Context(), rawIDToken)

	if err != nil {
		return nil, err
	}

	return idToken, nil
}

func handleSuccessfulAuthentication(tokenResponse *oauth2.Token, idToken oidc.IDToken, resp http.ResponseWriter) {
	payload := struct {
		TokenResponse *oauth2.Token
		IDToken       *json.RawMessage
	}{tokenResponse, new(json.RawMessage)}

	if err := idToken.Claims(&payload.IDToken); err != nil {
		return
	}

	data, err := json.MarshalIndent(&payload, "", "    ")

	if err != nil {
		http.Error(resp, err.Error(), http.StatusInternalServerError)
		return
	}

	resp.Write(data)
}


================================================
FILE: ch7/keycloak-js-adapter/app.js
================================================
var express = require('express');
var app = express();
var stringReplace = require('string-replace-middleware');

var KC_URL = process.env.KC_URL || "http://localhost:8180";

app.use(stringReplace({
   'KC_URL': KC_URL
}));
app.use(express.static('.'))

app.get('/', function(req, res) {
    res.render('index.html');
});

app.listen(8080);


================================================
FILE: ch7/keycloak-js-adapter/index.html
================================================
<!DOCTYPE html>
<html>
<head>
    <title>Keycloak Example Application</title>
    <script type="text/javascript" src="KC_URL/auth/js/keycloak.js"></script>
    <script type="text/javascript">
        function output(content) {
            if (typeof content === 'object') {
                content = JSON.stringify(content, null, 2)
            }
            document.getElementById('output').textContent = content;
        }

        function profile() {
            if (keycloak.idTokenParsed.name) {
                document.getElementById('name').textContent = 'Hello ' + keycloak.idTokenParsed.name;
            } else {
                document.getElementById('name').textContent = 'Hello ' + keycloak.idTokenParsed.preferred_username;
            }
            document.getElementById('user').style.display = 'block';
        }

        keycloak = new Keycloak({ realm: 'myrealm', clientId: 'mybrowserapp' });
        keycloak.init({onLoad: 'login-required'}).success(function () {
            console.log('User is now authenticated.');
            profile();
        }).error(function () {
            window.location.reload();
        });
    </script>
</head>
<body>

<div id="user" style="display: none">
    <button onclick="window.keycloak.logout()">Logout</button>
    <button onclick="output(keycloak.idTokenParsed)">Show ID Token</button>
    <button onclick="output(keycloak.tokenParsed)">Show Access Token</button>
    <button onclick="window.keycloak.updateToken(-1).then(function() { output(keycloak.idTokenParsed); profile() })">Refresh</button>
    <hr/>
    <h2 id="name"></h2>
    <pre id="output"></pre>
</div>

</body>
</html>


================================================
FILE: ch7/keycloak-js-adapter/package.json
================================================
{
  "name": "keycloak-js-app",
  "version": "0.0.1",
  "scripts": {
    "start": "node app.js"
  },
  "dependencies": {
    "express": "^4.17.1",
    "start": "^5.1.0",
    "string-replace-middleware": "^1.0.2"
  }
}

================================================
FILE: ch7/nodejs/backend/app.js
================================================
var express = require('express');
var Keycloak = require('keycloak-connect');

var app = express();

var keycloak = new Keycloak({});

app.use(keycloak.middleware());

app.get('/protected', keycloak.protect(), function (req, res) {
  res.setHeader('content-type', 'text/plain');
  res.send('Access granted to protected resource');
});

app.listen(8080, function () {
  console.log('Started at port 8080');
});

================================================
FILE: ch7/nodejs/backend/keycloak.json
================================================
{
  "realm": "myrealm",
  "bearer-only": true,
  "auth-server-url": "${env.KC_URL:http://localhost:8180/auth}",
  "resource": "mybackend"
}

================================================
FILE: ch7/nodejs/backend/package.json
================================================
{
  "name": "keycloak-example-service",
  "version": "0.0.1",
  "scripts": {
    "start": "node app.js"
  },
  "dependencies": {
    "cors": "^2.8.5",
    "express": "^4.17.1",
    "express-session": "^1.17.1",
    "keycloak-connect": "^11.0.3"
  }
}

================================================
FILE: ch7/nodejs/frontend/app.js
================================================
var express = require('express');
var session = require('express-session');
var Keycloak = require('keycloak-connect');
var cors = require('cors');

var app = express();

app.use(cors());

var memoryStore = new session.MemoryStore();

app.use(session({
    secret: 'some secret',
    resave: false,
    saveUninitialized: true,
    store: memoryStore
}));

var keycloak = new Keycloak({ store: memoryStore });

app.use(keycloak.middleware());

app.get('/', keycloak.protect(), function (req, res) {
    res.setHeader('content-type', 'text/plain');
    res.send('Welcome!');
});

app.listen(8080, function () {
    console.log('Started at port 8080');
});

================================================
FILE: ch7/nodejs/frontend/index.html
================================================
<!DOCTYPE html>
<html>
<head>
    <title>Keycloak Example Application</title>
    </script>
</head>
<body>

</body>
</html>

================================================
FILE: ch7/nodejs/frontend/keycloak.json
================================================
{
  "realm": "myrealm",
  "auth-server-url": "${env.KC_URL:http://localhost:8180/auth}",
  "resource": "mywebapp",
  "credentials" : {
      "secret" : CLIENT_SECRET
  }
}

================================================
FILE: ch7/nodejs/frontend/package.json
================================================
{
  "name": "keycloak-nodejs-frontend",
  "version": "0.0.1",
  "scripts": {
    "start": "node app.js"
  },
  "dependencies": {
    "keycloak-connect": "^11.0.0",
    "express": "^4.17.1",
    "express-session": "^1.17.1",
    "cors": "^2.8.5"
  }
}

================================================
FILE: ch7/python/backend/app.py
================================================
import json

from flask import Flask, g
app = Flask(__name__)
app.secret_key = 'change_me'
app.config['OIDC_CLIENT_SECRETS'] = 'oidc-config.json'
app.config['OIDC_RESOURCE_SERVER_ONLY'] = 'true'
app.config['OIDC_COOKIE_SECURE'] = False
from flask_oidc import OpenIDConnect
oidc = OpenIDConnect(app)

@app.route('/', methods=['POST'])
@oidc.accept_token(True)
def api():
    return json.dumps({'hello': 'Welcome %s' % g.oidc_token_info['preferred_username']})


================================================
FILE: ch7/python/backend/oidc-config.json
================================================

{
  "web": {
    "client_id": "mybackend",
    "client_secret": CLIENT_SECRET,
    "auth_uri": "http://localhost:8180/auth/realms/myrealm/protocol/openid-connect/auth",
    "token_uri": "http://localhost:8180/auth/realms/myrealm/protocol/openid-connect/token",
    "issuer": "http://localhost:8180/auth/realms/myrealm",
    "userinfo_uri": "http://localhost:8180/auth/realms/myrealm/protocol/openid-connect/userinfo",
    "token_introspection_uri": "http://localhost:8180/auth/realms/myrealm/protocol/openid-connect/token/introspect",
    "redirect_uris": [
      "http://localhost:8080/oidc/callback"
    ]
  }
}

================================================
FILE: ch7/python/frontend/app.py
================================================
from flask import Flask, g
app = Flask(__name__)
app.secret_key = 'change_me'
app.config['OIDC_CLIENT_SECRETS'] = 'oidc-config.json'
app.config['OIDC_COOKIE_SECURE'] = False
from flask_oidc import OpenIDConnect
oidc = OpenIDConnect(app)

@app.route('/')
@oidc.require_login
def index():
    if oidc.user_loggedin:
        return 'Welcome %s' % oidc.user_getfield('preferred_username')
    else:
        return 'Not logged in'


================================================
FILE: ch7/python/frontend/oidc-config.json
================================================
{
  "web": {
    "client_id": "mywebapp",
    "client_secret": CLIENT_SECRET,
    "auth_uri": "http://localhost:8180/auth/realms/myrealm/protocol/openid-connect/auth",
    "token_uri": "http://localhost:8180/auth/realms/myrealm/protocol/openid-connect/token",
    "issuer": "http://localhost:8180/auth/realms/myrealm",
    "userinfo_uri": "http://localhost:8180/auth/realms/myrealm/protocol/openid-connect/userinfo",
    "redirect_uris": [
      "http://localhost:8080/oidc/callback"
    ]
  }
}

================================================
FILE: ch7/quarkus/backend/.mvn/wrapper/MavenWrapperDownloader.java
================================================
/*
 * 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();
    }

}


================================================
FILE: ch7/quarkus/backend/.mvn/wrapper/maven-wrapper.properties
================================================
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


================================================
FILE: ch7/quarkus/backend/mvnw
================================================
#!/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 /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 "$@"


================================================
FILE: ch7/quarkus/backend/mvnw.cmd
================================================
@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    https://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%


================================================
FILE: ch7/quarkus/backend/pom.xml
================================================
<?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>org.keycloak</groupId>
  <artifactId>quarkus</artifactId>
  <version>1.0.0-SNAPSHOT</version>
  <properties>
    <compiler-plugin.version>3.8.1</compiler-plugin.version>
    <maven.compiler.parameters>true</maven.compiler.parameters>
    <maven.compiler.source>11</maven.compiler.source>
    <maven.compiler.target>11</maven.compiler.target>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    <quarkus-plugin.version>1.11.3.Final</quarkus-plugin.version>
    <quarkus.platform.artifact-id>quarkus-universe-bom</quarkus.platform.artifact-id>
    <quarkus.platform.group-id>io.quarkus</quarkus.platform.group-id>
    <quarkus.platform.version>1.11.3.Final</quarkus.platform.version>
    <surefire-plugin.version>3.0.0-M5</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-arc</artifactId>
    </dependency>
    <dependency>
      <groupId>io.quarkus</groupId>
      <artifactId>quarkus-resteasy</artifactId>
    </dependency>
    <dependency>
      <groupId>io.quarkus</groupId>
      <artifactId>quarkus-oidc</artifactId>
    </dependency>

    <dependency>
      <groupId>io.quarkus</groupId>
      <artifactId>quarkus-junit5</artifactId>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>io.rest-assured</groupId>
      <artifactId>rest-assured</artifactId>
      <scope>test</scope>
    </dependency>
  </dependencies>
  <build>
    <plugins>
      <plugin>
        <groupId>io.quarkus</groupId>
        <artifactId>quarkus-maven-plugin</artifactId>
        <version>${quarkus-plugin.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>
      </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>
    </plugins>
  </build>
  <profiles>
    <profile>
      <id>native</id>
      <activation>
        <property>
          <name>native</name>
        </property>
      </activation>
      <build>
        <plugins>
          <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>
      <properties>
        <quarkus.package.type>native</quarkus.package.type>
      </properties>
    </profile>
  </profiles>
</project>


================================================
FILE: ch7/quarkus/backend/src/main/java/org/keycloak/GreetingResource.java
================================================
package org.keycloak;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

@Path("/hello")
public class GreetingResource {

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public String hello() {
        return "Hello RESTEasy";
    }
}

================================================
FILE: ch7/quarkus/backend/src/main/resources/META-INF/resources/index.html
================================================
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>quarkus - 1.0.0-SNAPSHOT</title>
    <style>
        h1, h2, h3, h4, h5, h6 {
            margin-bottom: 0.5rem;
            font-weight: 400;
            line-height: 1.5;
        }

        h1 {
            font-size: 2.5rem;
        }

        h2 {
            font-size: 2rem
        }

        h3 {
            font-size: 1.75rem
        }

        h4 {
            font-size: 1.5rem
        }

        h5 {
            font-size: 1.25rem
        }

        h6 {
            font-size: 1rem
        }

        .lead {
            font-weight: 300;
            font-size: 2rem;
        }

        .banner {
            font-size: 2.7rem;
            margin: 0;
            padding: 2rem 1rem;
            background-color: #0d1c2c;
            color: white;
        }

        body {
            margin: 0;
            font-family: -apple-system, system-ui, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
        }

        code {
            font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
            font-size: 87.5%;
            color: #e83e8c;
            word-break: break-word;
        }

        .left-column {
            padding: .75rem;
            max-width: 75%;
            min-width: 55%;
        }

        .right-column {
            padding: .75rem;
            max-width: 25%;
        }

        .container {
            display: flex;
            width: 100%;
        }

        li {
            margin: 0.75rem;
        }

        .right-section {
            margin-left: 1rem;
            padding-left: 0.5rem;
        }

        .right-section h3 {
            padding-top: 0;
            font-weight: 200;
        }

        .right-section ul {
            border-left: 0.3rem solid #71aeef;
            list-style-type: none;
            padding-left: 0;
        }

        .examples {
            display: flex;
            flex-wrap: wrap;
            margin: 20px 0 20px -40px;
        }

        .example {
            display: flex;
            margin-left: 20px;
            margin-bottom: 20px;
            flex-direction: column;
            width: 350px;
            background-color: #205894;
            color: white;
        }

        .example code {
            color: lightgrey;
        }

        .example-header {
            padding: 20px;
            display: flex;
            position: relative;
        }

        .example-header h4 {
            margin: 0;
            font-size: 1.4rem;
            flex-grow: 1;
            line-height: 1.5;
        }

        .example-description {
            padding: 0 20px;
            flex-grow: 1;
        }

        .example-paths {
            display: flex;
            flex-direction: column;
        }

        .example-paths a {
            display: block;
            background-color: transparent;
            font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
            color: white;
            padding: 10px;
            text-decoration: none;
        }

        .example-paths a:before {
            content: '⇨';
            font-weight: bold;
            font-size: 1.5rem;
            margin: 20px;
        }

        .example-paths a:hover {
            background-color: #0d1c2c;
        }

        .guide-link {
            background-color: #71aeef;
            position: absolute;
            color: white;
            text-decoration: none;
            top: 0;
            right: 0;
            padding: 7px;
            font-weight: bold;
        }

        .guide-link:hover {
            background-color: #0d1c2c;
        }
    </style>
</head>
<body>

<div class="banner lead">
    Your new Cloud-Native application is ready!
</div>

<div class="container">
    <div class="left-column">
        <p class="lead"> Congratulations, you have created a new Quarkus cloud application.</p>

        <h2>Why do you see this?</h2>

        <p>This page is served by Quarkus. The source is in
            <code>src/main/resources/META-INF/resources/index.html</code>.</p>

        <h2>What can I do from here?</h2>

        <p>If not already done, run the application in <em>dev mode</em> using: <code>./mvnw compile quarkus:dev</code>.
        </p>
        <ul>
            <li>Play with your example code in <code>src/main/java</code>:
                <div class="examples">
<div class="example">
    <div class="example-header">
        <h4>RESTEasy JAX-RS</h4>
        <a href="https://quarkus.io/guides/rest-json" target="_blank" class="guide-link">Guide</a>
    </div>
    <div class="example-description">
        <p>A Hello World RESTEasy resource</p>

    </div>
    <div class="example-paths">
        <a href="/" class="path-link" target="_blank">GET /</a>
    </div>
</div>

                </div>
            </li>
            <li>Your static assets are located in <code>src/main/resources/META-INF/resources</code>.</li>
            <li>Configure your application in <code>src/main/resources/application.properties</code>.</li>
        </ul>
        <h2>Do you like Quarkus?</h2>
        <p>Go give it a star on <a href="https://github.com/quarkusio/quarkus">GitHub</a>.</p>
    </div>
    <div class="right-column">
        <div class="right-section">
            <h3>Application</h3>
            <ul>
                <li>GroupId: org.keycloak</li>
                <li>ArtifactId: quarkus</li>
                <li>Version: 1.0.0-SNAPSHOT</li>
                <li>Quarkus Version: 1.11.3.Final</li>
            </ul>
        </div>
        <div class="right-section">
            <h3>Next steps</h3>
            <ul>
                <li><a href="https://quarkus.io/guides/maven-tooling.html" target="_blank">Setup your IDE</a></li>
                <li><a href="https://quarkus.io/guides/getting-started.html" target="_blank">Getting started</a></li>
                <li><a href="https://quarkus.io" target="_blank">Quarkus Web Site</a></li>
            </ul>
        </div>
    </div>
</div>
</body>
</html>

================================================
FILE: ch7/quarkus/backend/src/main/resources/application.properties
================================================
quarkus.oidc.auth-server-u
Download .txt
gitextract__3_pgkb2/

├── .gitignore
├── LICENSE
├── README.md
├── ch13/
│   ├── simple-risk-based-authenticator/
│   │   ├── .mvn/
│   │   │   └── wrapper/
│   │   │       ├── MavenWrapperDownloader.java
│   │   │       ├── maven-wrapper.jar
│   │   │       └── maven-wrapper.properties
│   │   ├── README.md
│   │   ├── mvnw
│   │   ├── mvnw.cmd
│   │   ├── pom.xml
│   │   └── src/
│   │       └── main/
│   │           ├── java/
│   │           │   └── org/
│   │           │       └── keycloak/
│   │           │           └── book/
│   │           │               └── ch13/
│   │           │                   └── authentication/
│   │           │                       ├── MySimpleRiskBasedAuthenticator.java
│   │           │                       └── MySimpleRiskBasedAuthenticatorFactory.java
│   │           └── resources/
│   │               └── META-INF/
│   │                   └── services/
│   │                       └── org.keycloak.authentication.AuthenticatorFactory
│   └── themes/
│       └── mytheme/
│           ├── .mvn/
│           │   └── wrapper/
│           │       ├── MavenWrapperDownloader.java
│           │       ├── maven-wrapper.jar
│           │       └── maven-wrapper.properties
│           ├── README.md
│           ├── mvnw
│           ├── mvnw.cmd
│           ├── pom.xml
│           └── src/
│               └── main/
│                   ├── java/
│                   │   └── org/
│                   │       └── keycloak/
│                   │           └── book/
│                   │               └── ch13/
│                   │                   └── theme/
│                   │                       ├── MyThemeResourceProvider.java
│                   │                       └── MyThemeSelectorProvider.java
│                   └── resources/
│                       ├── META-INF/
│                       │   ├── jboss-deployment-structure.xml
│                       │   ├── keycloak-themes.json
│                       │   └── services/
│                       │       ├── org.keycloak.theme.ThemeResourceProviderFactory
│                       │       └── org.keycloak.theme.ThemeSelectorProviderFactory
│                       └── theme/
│                           └── mytheme/
│                               └── login/
│                                   ├── resources/
│                                   │   └── css/
│                                   │       └── signin.css
│                                   └── theme.properties
├── ch2/
│   ├── backend/
│   │   ├── Dockerfile
│   │   ├── app.js
│   │   ├── keycloak.json
│   │   └── package.json
│   └── frontend/
│       ├── .mvn/
│       │   └── wrapper/
│       │       ├── MavenWrapperDownloader.java
│       │       ├── maven-wrapper.jar
│       │       └── maven-wrapper.properties
│       ├── Dockerfile
│       ├── app.js
│       ├── index.html
│       └── package.json
├── ch4/
│   ├── Dockerfile
│   ├── app.js
│   ├── client.js
│   ├── index.html
│   ├── package.json
│   └── styles.css
├── ch5/
│   ├── backend/
│   │   ├── Dockerfile
│   │   ├── app.js
│   │   ├── keycloak.json
│   │   └── package.json
│   └── frontend/
│       ├── Dockerfile
│       ├── app.js
│       ├── client.js
│       ├── index.html
│       ├── package.json
│       └── styles.css
├── ch6/
│   ├── app.js
│   └── package.json
├── ch7/
│   ├── golang/
│   │   ├── go.mod
│   │   ├── go.sum
│   │   └── main.go
│   ├── keycloak-js-adapter/
│   │   ├── app.js
│   │   ├── index.html
│   │   └── package.json
│   ├── nodejs/
│   │   ├── backend/
│   │   │   ├── app.js
│   │   │   ├── keycloak.json
│   │   │   └── package.json
│   │   └── frontend/
│   │       ├── app.js
│   │       ├── index.html
│   │       ├── keycloak.json
│   │       └── package.json
│   ├── python/
│   │   ├── backend/
│   │   │   ├── app.py
│   │   │   └── oidc-config.json
│   │   └── frontend/
│   │       ├── app.py
│   │       └── oidc-config.json
│   ├── quarkus/
│   │   ├── backend/
│   │   │   ├── .mvn/
│   │   │   │   └── wrapper/
│   │   │   │       ├── MavenWrapperDownloader.java
│   │   │   │       ├── maven-wrapper.jar
│   │   │   │       └── maven-wrapper.properties
│   │   │   ├── mvnw
│   │   │   ├── mvnw.cmd
│   │   │   ├── pom.xml
│   │   │   └── src/
│   │   │       └── main/
│   │   │           ├── java/
│   │   │           │   └── org/
│   │   │           │       └── keycloak/
│   │   │           │           └── GreetingResource.java
│   │   │           └── resources/
│   │   │               ├── META-INF/
│   │   │               │   └── resources/
│   │   │               │       └── index.html
│   │   │               └── application.properties
│   │   └── frontend/
│   │       ├── .mvn/
│   │       │   └── wrapper/
│   │       │       ├── MavenWrapperDownloader.java
│   │       │       ├── maven-wrapper.jar
│   │       │       └── maven-wrapper.properties
│   │       ├── mvnw
│   │       ├── mvnw.cmd
│   │       ├── pom.xml
│   │       └── src/
│   │           └── main/
│   │               ├── java/
│   │               │   └── org/
│   │               │       └── keycloak/
│   │               │           └── GreetingResource.java
│   │               └── resources/
│   │                   ├── META-INF/
│   │                   │   └── resources/
│   │                   │       └── index.html
│   │                   └── application.properties
│   ├── reverse-proxy/
│   │   ├── app/
│   │   │   ├── app.js
│   │   │   └── package.json
│   │   └── secure-proxy.conf
│   └── springboot/
│       ├── backend/
│       │   ├── .mvn/
│       │   │   └── wrapper/
│       │   │       ├── MavenWrapperDownloader.java
│       │   │       ├── maven-wrapper.jar
│       │   │       └── maven-wrapper.properties
│       │   ├── build.gradle
│       │   ├── gradlew
│       │   ├── gradlew.bat
│       │   ├── mvnw
│       │   ├── mvnw.cmd
│       │   ├── pom.xml
│       │   ├── settings.gradle
│       │   └── src/
│       │       └── main/
│       │           ├── java/
│       │           │   └── org/
│       │           │       └── keycloak/
│       │           │           └── springboot/
│       │           │               ├── Application.java
│       │           │               ├── HelloController.java
│       │           │               └── SecurityConfig.java
│       │           └── resources/
│       │               └── application.yaml
│       ├── backend-using-introspection/
│       │   ├── .mvn/
│       │   │   └── wrapper/
│       │   │       ├── maven-wrapper.jar
│       │   │       └── maven-wrapper.properties
│       │   ├── build.gradle
│       │   ├── gradlew
│       │   ├── gradlew.bat
│       │   ├── mvnw
│       │   ├── mvnw.cmd
│       │   ├── pom.xml
│       │   ├── settings.gradle
│       │   └── src/
│       │       └── main/
│       │           ├── java/
│       │           │   └── org/
│       │           │       └── keycloak/
│       │           │           └── springboot/
│       │           │               ├── Application.java
│       │           │               ├── HelloController.java
│       │           │               └── SecurityConfig.java
│       │           └── resources/
│       │               └── application.yaml
│       └── frontend/
│           ├── .mvn/
│           │   └── wrapper/
│           │       ├── MavenWrapperDownloader.java
│           │       ├── maven-wrapper.jar
│           │       └── maven-wrapper.properties
│           ├── build.gradle
│           ├── gradlew
│           ├── gradlew.bat
│           ├── mvnw
│           ├── mvnw.cmd
│           ├── pom.xml
│           ├── settings.gradle
│           └── src/
│               └── main/
│                   ├── java/
│                   │   └── com/
│                   │       └── example/
│                   │           └── springboot/
│                   │               ├── Application.java
│                   │               └── HelloController.java
│                   └── resources/
│                       └── application.yaml
└── ch9/
    ├── configure-caches.cli
    ├── configure-database.cli
    ├── configure-hostname.cli
    ├── configure-https.cli
    ├── configure-proxy.cli
    ├── configure-session-affinity.cli
    ├── haproxy.cfg
    ├── haproxy.crt.pem
    ├── mykeycloak.crt
    ├── mykeycloak.key
    └── mykeycloak.keystore
Download .txt
SYMBOL INDEX (133 symbols across 26 files)

FILE: ch13/simple-risk-based-authenticator/.mvn/wrapper/MavenWrapperDownloader.java
  class MavenWrapperDownloader (line 21) | public class MavenWrapperDownloader {
    method main (line 48) | public static void main(String args[]) {
    method downloadFileFromURL (line 97) | private static void downloadFileFromURL(String urlString, File destina...

FILE: ch13/simple-risk-based-authenticator/src/main/java/org/keycloak/book/ch13/authentication/MySimpleRiskBasedAuthenticator.java
  class MySimpleRiskBasedAuthenticator (line 22) | public class MySimpleRiskBasedAuthenticator implements Authenticator {
    type RiskScore (line 26) | private enum RiskScore {
      method RiskScore (line 33) | RiskScore(double score) {
      method requiresSecondFactor (line 37) | public boolean requiresSecondFactor() {
    method authenticate (line 42) | @Override
    method calculateRiskScore (line 57) | private RiskScore calculateRiskScore(KeycloakSession session, Authenti...
    method skipSecondFactor (line 76) | private void skipSecondFactor(UserModel user) {
    method forceSecondFactor (line 80) | private void forceSecondFactor(KeycloakSession session, UserModel user) {
    method getMaxFailuresBeforeOtp (line 92) | private Integer getMaxFailuresBeforeOtp(AuthenticationFlowContext cont...
    method action (line 103) | @Override
    method requiresUser (line 108) | @Override
    method configuredFor (line 113) | @Override
    method setRequiredActions (line 118) | @Override
    method close (line 123) | @Override

FILE: ch13/simple-risk-based-authenticator/src/main/java/org/keycloak/book/ch13/authentication/MySimpleRiskBasedAuthenticatorFactory.java
  class MySimpleRiskBasedAuthenticatorFactory (line 30) | public class MySimpleRiskBasedAuthenticatorFactory implements Authentica...
    method create (line 38) | @Override
    method getDisplayType (line 44) | @Override
    method isConfigurable (line 49) | @Override
    method getRequirementChoices (line 54) | @Override
    method isUserSetupAllowed (line 59) | @Override
    method getHelpText (line 64) | @Override
    method getConfigProperties (line 69) | @Override
    method getReferenceCategory (line 82) | @Override
    method init (line 87) | @Override
    method postInit (line 92) | @Override
    method close (line 97) | @Override
    method getId (line 102) | @Override

FILE: ch13/themes/mytheme/.mvn/wrapper/MavenWrapperDownloader.java
  class MavenWrapperDownloader (line 21) | public class MavenWrapperDownloader {
    method main (line 48) | public static void main(String args[]) {
    method downloadFileFromURL (line 97) | private static void downloadFileFromURL(String urlString, File destina...

FILE: ch13/themes/mytheme/src/main/java/org/keycloak/book/ch13/theme/MyThemeResourceProvider.java
  class MyThemeResourceProvider (line 17) | public class MyThemeResourceProvider implements ThemeResourceProvider, T...
    method MyThemeResourceProvider (line 22) | public MyThemeResourceProvider() {
    method MyThemeResourceProvider (line 27) | public MyThemeResourceProvider(KeycloakSession session) {
    method create (line 31) | @Override
    method getTemplate (line 36) | @Override
    method getResourceAsStream (line 42) | @Override
    method init (line 48) | @Override
    method postInit (line 53) | @Override
    method getId (line 58) | @Override
    method close (line 63) | @Override
    method order (line 68) | @Override

FILE: ch13/themes/mytheme/src/main/java/org/keycloak/book/ch13/theme/MyThemeSelectorProvider.java
  class MyThemeSelectorProvider (line 19) | public class MyThemeSelectorProvider extends DefaultThemeSelectorProvider
    method MyThemeSelectorProvider (line 26) | public MyThemeSelectorProvider() {
    method MyThemeSelectorProvider (line 31) | public MyThemeSelectorProvider(KeycloakSession session) {
    method getThemeName (line 36) | @Override
    method create (line 49) | @Override
    method init (line 54) | @Override
    method postInit (line 59) | @Override
    method getId (line 64) | @Override
    method close (line 69) | @Override
    method order (line 74) | @Override
    method getThemeParameter (line 82) | private String getThemeParameter() {

FILE: ch2/frontend/.mvn/wrapper/MavenWrapperDownloader.java
  class MavenWrapperDownloader (line 21) | public class MavenWrapperDownloader {
    method main (line 48) | public static void main(String args[]) {
    method downloadFileFromURL (line 97) | private static void downloadFileFromURL(String urlString, File destina...

FILE: ch4/client.js
  function loadDiscovery (line 6) | function loadDiscovery() {
  function generateAuthenticationRequest (line 22) | function generateAuthenticationRequest() {
  function loadTokens (line 63) | function loadTokens() {
  function refreshTokens (line 105) | function refreshTokens() {
  function userInfo (line 140) | function userInfo() {
  function reset (line 165) | function reset() {
  function loadState (line 170) | function loadState() {
  function setState (line 181) | function setState(key, value) {
  function step (line 186) | function step(step) {
  function getInput (line 213) | function getInput(id) {
  function setInput (line 217) | function setInput(id, value) {
  function setOutput (line 221) | function setOutput(id, value) {
  function getQueryVariable (line 230) | function getQueryVariable(key) {
  function base64UrlDecode (line 241) | function base64UrlDecode(input) {
  function init (line 257) | function init() {

FILE: ch5/frontend/client.js
  function loadDiscovery (line 6) | function loadDiscovery() {
  function generateAuthorizationRequest (line 22) | function generateAuthorizationRequest() {
  function loadTokens (line 45) | function loadTokens(code) {
  function invokeService (line 83) | function invokeService() {
  function reset (line 110) | function reset() {
  function loadState (line 115) | function loadState() {
  function setState (line 126) | function setState(key, value) {
  function step (line 131) | function step(step) {
  function getInput (line 154) | function getInput(id) {
  function setInput (line 158) | function setInput(id, value) {
  function setOutput (line 162) | function setOutput(id, value) {
  function getQueryVariable (line 171) | function getQueryVariable(key) {
  function base64UrlDecode (line 182) | function base64UrlDecode(input) {
  function init (line 198) | function init() {

FILE: ch7/golang/main.go
  function init (line 25) | func init() {
  function createOidcProvider (line 31) | func createOidcProvider(ctx context.Context) *oidc.Provider {
  function createConfig (line 41) | func createConfig(provider oidc.Provider) (oidc.Config, oauth2.Config) {
  function main (line 57) | func main() {
  function redirectHandler (line 65) | func redirectHandler(resp http.ResponseWriter, r *http.Request) {
  function callbackHandler (line 70) | func callbackHandler(resp http.ResponseWriter, req *http.Request) {
  function addStateCookie (line 95) | func addStateCookie(resp http.ResponseWriter) string {
  function expireCookie (line 111) | func expireCookie(name string, resp http.ResponseWriter) {
  function checkStateAndExpireCookie (line 122) | func checkStateAndExpireCookie(req *http.Request, resp http.ResponseWrit...
  function exchangeCode (line 138) | func exchangeCode(req *http.Request) (*oauth2.Token, error) {
  function validateIDToken (line 151) | func validateIDToken(tokenResponse *oauth2.Token, req *http.Request) (*o...
  function handleSuccessfulAuthentication (line 167) | func handleSuccessfulAuthentication(tokenResponse *oauth2.Token, idToken...

FILE: ch7/python/backend/app.py
  function api (line 14) | def api():

FILE: ch7/python/frontend/app.py
  function index (line 11) | def index():

FILE: ch7/quarkus/backend/.mvn/wrapper/MavenWrapperDownloader.java
  class MavenWrapperDownloader (line 21) | public class MavenWrapperDownloader {
    method main (line 48) | public static void main(String args[]) {
    method downloadFileFromURL (line 97) | private static void downloadFileFromURL(String urlString, File destina...

FILE: ch7/quarkus/backend/src/main/java/org/keycloak/GreetingResource.java
  class GreetingResource (line 8) | @Path("/hello")
    method hello (line 11) | @GET

FILE: ch7/quarkus/frontend/.mvn/wrapper/MavenWrapperDownloader.java
  class MavenWrapperDownloader (line 21) | public class MavenWrapperDownloader {
    method main (line 48) | public static void main(String args[]) {
    method downloadFileFromURL (line 97) | private static void downloadFileFromURL(String urlString, File destina...

FILE: ch7/quarkus/frontend/src/main/java/org/keycloak/GreetingResource.java
  class GreetingResource (line 8) | @Path("/hello")
    method hello (line 11) | @GET

FILE: ch7/springboot/backend-using-introspection/src/main/java/org/keycloak/springboot/Application.java
  class Application (line 9) | @SpringBootApplication
    method main (line 12) | public static void main(String[] args) {

FILE: ch7/springboot/backend-using-introspection/src/main/java/org/keycloak/springboot/HelloController.java
  class HelloController (line 6) | @RestController
    method index (line 9) | @RequestMapping("/")

FILE: ch7/springboot/backend-using-introspection/src/main/java/org/keycloak/springboot/SecurityConfig.java
  class SecurityConfig (line 7) | @EnableWebSecurity
    method configure (line 10) | @Override

FILE: ch7/springboot/backend/.mvn/wrapper/MavenWrapperDownloader.java
  class MavenWrapperDownloader (line 21) | public class MavenWrapperDownloader {
    method main (line 48) | public static void main(String args[]) {
    method downloadFileFromURL (line 97) | private static void downloadFileFromURL(String urlString, File destina...

FILE: ch7/springboot/backend/src/main/java/org/keycloak/springboot/Application.java
  class Application (line 9) | @SpringBootApplication
    method main (line 12) | public static void main(String[] args) {

FILE: ch7/springboot/backend/src/main/java/org/keycloak/springboot/HelloController.java
  class HelloController (line 6) | @RestController
    method index (line 9) | @RequestMapping("/")

FILE: ch7/springboot/backend/src/main/java/org/keycloak/springboot/SecurityConfig.java
  class SecurityConfig (line 7) | @EnableWebSecurity
    method configure (line 10) | @Override

FILE: ch7/springboot/frontend/.mvn/wrapper/MavenWrapperDownloader.java
  class MavenWrapperDownloader (line 21) | public class MavenWrapperDownloader {
    method main (line 48) | public static void main(String args[]) {
    method downloadFileFromURL (line 97) | private static void downloadFileFromURL(String urlString, File destina...

FILE: ch7/springboot/frontend/src/main/java/com/example/springboot/Application.java
  class Application (line 9) | @SpringBootApplication
    method main (line 12) | public static void main(String[] args) {

FILE: ch7/springboot/frontend/src/main/java/com/example/springboot/HelloController.java
  class HelloController (line 6) | @RestController
    method index (line 9) | @RequestMapping("/")
Condensed preview — 146 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (358K chars).
[
  {
    "path": ".gitignore",
    "chars": 63,
    "preview": ".idea\n*.iml\ngradle\n\npackage-lock.json\nnode_modules\nnode\ntarget\n"
  },
  {
    "path": "LICENSE",
    "chars": 1062,
    "preview": "MIT License\n\nCopyright (c) 2020 Packt\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof t"
  },
  {
    "path": "README.md",
    "chars": 6026,
    "preview": "\n\n\n# Keycloak - Identity and Access Management for Modern Applications\n\n<a href=\"https://www.packtpub.com/product/keyclo"
  },
  {
    "path": "ch13/simple-risk-based-authenticator/.mvn/wrapper/MavenWrapperDownloader.java",
    "chars": 4941,
    "preview": "/*\n * Copyright 2007-present the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \""
  },
  {
    "path": "ch13/simple-risk-based-authenticator/.mvn/wrapper/maven-wrapper.properties",
    "chars": 218,
    "preview": "distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip\nwrap"
  },
  {
    "path": "ch13/simple-risk-based-authenticator/README.md",
    "chars": 578,
    "preview": "# Examples for Chapter 13 - Extending Keycloak\n\n## Simple Risk-Based Authenticator\n\nTo build this project, execute the f"
  },
  {
    "path": "ch13/simple-risk-based-authenticator/mvnw",
    "chars": 10069,
    "preview": "#!/bin/sh\n# ----------------------------------------------------------------------------\n# Licensed to the Apache Softwa"
  },
  {
    "path": "ch13/simple-risk-based-authenticator/mvnw.cmd",
    "chars": 6607,
    "preview": "@REM ----------------------------------------------------------------------------\n@REM Licensed to the Apache Software F"
  },
  {
    "path": "ch13/simple-risk-based-authenticator/pom.xml",
    "chars": 1927,
    "preview": "<?xml version=\"1.0\"?>\n<project xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-"
  },
  {
    "path": "ch13/simple-risk-based-authenticator/src/main/java/org/keycloak/book/ch13/authentication/MySimpleRiskBasedAuthenticator.java",
    "chars": 4016,
    "preview": "package org.keycloak.book.ch13.authentication;\n\nimport static org.keycloak.book.ch13.authentication.MySimpleRiskBasedAut"
  },
  {
    "path": "ch13/simple-risk-based-authenticator/src/main/java/org/keycloak/book/ch13/authentication/MySimpleRiskBasedAuthenticatorFactory.java",
    "chars": 3573,
    "preview": "package org.keycloak.book.ch13.authentication;\n\nimport static org.keycloak.provider.ProviderConfigProperty.STRING_TYPE;\n"
  },
  {
    "path": "ch13/simple-risk-based-authenticator/src/main/resources/META-INF/services/org.keycloak.authentication.AuthenticatorFactory",
    "chars": 75,
    "preview": "org.keycloak.book.ch13.authentication.MySimpleRiskBasedAuthenticatorFactory"
  },
  {
    "path": "ch13/themes/mytheme/.mvn/wrapper/MavenWrapperDownloader.java",
    "chars": 4941,
    "preview": "/*\n * Copyright 2007-present the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \""
  },
  {
    "path": "ch13/themes/mytheme/.mvn/wrapper/maven-wrapper.properties",
    "chars": 218,
    "preview": "distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip\nwrap"
  },
  {
    "path": "ch13/themes/mytheme/README.md",
    "chars": 505,
    "preview": "# Examples for Chapter 13 - Extending Keycloak\n\n## Themes\n\nTo build this project, execute the following command:\n\n    ./"
  },
  {
    "path": "ch13/themes/mytheme/mvnw",
    "chars": 10069,
    "preview": "#!/bin/sh\n# ----------------------------------------------------------------------------\n# Licensed to the Apache Softwa"
  },
  {
    "path": "ch13/themes/mytheme/mvnw.cmd",
    "chars": 6607,
    "preview": "@REM ----------------------------------------------------------------------------\n@REM Licensed to the Apache Software F"
  },
  {
    "path": "ch13/themes/mytheme/pom.xml",
    "chars": 1700,
    "preview": "<?xml version=\"1.0\"?>\n<project xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-"
  },
  {
    "path": "ch13/themes/mytheme/src/main/java/org/keycloak/book/ch13/theme/MyThemeResourceProvider.java",
    "chars": 2303,
    "preview": "package org.keycloak.book.ch13.theme;\n\nimport java.io.InputStream;\nimport java.net.URL;\n\nimport org.keycloak.models.Keyc"
  },
  {
    "path": "ch13/themes/mytheme/src/main/java/org/keycloak/book/ch13/theme/MyThemeSelectorProvider.java",
    "chars": 2694,
    "preview": "package org.keycloak.book.ch13.theme;\n\nimport javax.ws.rs.core.MultivaluedMap;\n\nimport org.keycloak.models.KeycloakConte"
  },
  {
    "path": "ch13/themes/mytheme/src/main/resources/META-INF/jboss-deployment-structure.xml",
    "chars": 240,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<jboss-deployment-structure>\n    <deployment>\n        <dependencies>\n            "
  },
  {
    "path": "ch13/themes/mytheme/src/main/resources/META-INF/keycloak-themes.json",
    "chars": 72,
    "preview": "{\n  \"themes\": [{\n    \"name\" : \"mytheme\",\n    \"types\": [ \"login\" ]\n  }]\n}"
  },
  {
    "path": "ch13/themes/mytheme/src/main/resources/META-INF/services/org.keycloak.theme.ThemeResourceProviderFactory",
    "chars": 52,
    "preview": "org.keycloak.book.ch13.theme.MyThemeResourceProvider"
  },
  {
    "path": "ch13/themes/mytheme/src/main/resources/META-INF/services/org.keycloak.theme.ThemeSelectorProviderFactory",
    "chars": 52,
    "preview": "org.keycloak.book.ch13.theme.MyThemeSelectorProvider"
  },
  {
    "path": "ch13/themes/mytheme/src/main/resources/theme/mytheme/login/resources/css/signin.css",
    "chars": 1807,
    "preview": "html,\n.login-page {\n  height: 100%;\n}\n\nbody {\n  display: flex;\n  align-items: center;\n  padding-top: 40px;\n  padding-bot"
  },
  {
    "path": "ch13/themes/mytheme/src/main/resources/theme/mytheme/login/theme.properties",
    "chars": 877,
    "preview": "# Inherit resources and messages from the keycloak\nparent=keycloak\n\n# Define the CSS styles\nstyles=css/login.css css/boo"
  },
  {
    "path": "ch2/backend/Dockerfile",
    "chars": 115,
    "preview": "FROM node\nCOPY package.json .\nRUN npm install\nCOPY app.js .\nCOPY keycloak.json .\nEXPOSE 3000\nCMD [ \"npm\", \"start\" ]"
  },
  {
    "path": "ch2/backend/app.js",
    "chars": 977,
    "preview": "var express = require('express');\nvar session = require('express-session');\nvar Keycloak = require('keycloak-connect');\n"
  },
  {
    "path": "ch2/backend/keycloak.json",
    "chars": 144,
    "preview": "{\n  \"realm\": \"myrealm\",\n  \"bearer-only\": true,\n  \"auth-server-url\": \"${env.KC_URL:http://localhost:8080/auth}\",\n  \"resou"
  },
  {
    "path": "ch2/backend/package.json",
    "chars": 251,
    "preview": "{\n  \"name\": \"keycloak-example-service\",\n  \"version\": \"0.0.1\",\n  \"scripts\": {\n    \"start\": \"node app.js\"\n  },\n  \"dependen"
  },
  {
    "path": "ch2/frontend/.mvn/wrapper/MavenWrapperDownloader.java",
    "chars": 4941,
    "preview": "/*\n * Copyright 2007-present the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \""
  },
  {
    "path": "ch2/frontend/.mvn/wrapper/maven-wrapper.properties",
    "chars": 218,
    "preview": "distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip\nwrap"
  },
  {
    "path": "ch2/frontend/Dockerfile",
    "chars": 112,
    "preview": "FROM node\nCOPY package.json .\nRUN npm install\nCOPY app.js .\nCOPY index.html .\nEXPOSE 8000\nCMD [ \"npm\", \"start\" ]"
  },
  {
    "path": "ch2/frontend/app.js",
    "chars": 531,
    "preview": "var express = require('express');\nvar app = express();\nvar stringReplace = require('string-replace-middleware');\n\nvar KC"
  },
  {
    "path": "ch2/frontend/index.html",
    "chars": 2493,
    "preview": "<!DOCTYPE html>\n<html>\n<head>\n    <title>Keycloak Example Application</title>\n    <script type=\"text/javascript\" src=\"KC"
  },
  {
    "path": "ch2/frontend/package.json",
    "chars": 222,
    "preview": "{\n  \"name\": \"keycloak-example-app\",\n  \"version\": \"0.0.1\",\n  \"scripts\": {\n    \"start\": \"node app.js\"\n  },\n  \"dependencies"
  },
  {
    "path": "ch4/Dockerfile",
    "chars": 148,
    "preview": "FROM node\nCOPY package.json .\nRUN npm install\nCOPY app.js .\nCOPY index.html .\nCOPY styles.css .\nCOPY client.js .\nEXPOSE "
  },
  {
    "path": "ch4/app.js",
    "chars": 455,
    "preview": "var express = require('express');\nvar app = express();\nvar stringReplace = require('string-replace-middleware');\n\nvar KC"
  },
  {
    "path": "ch4/client.js",
    "chars": 8727,
    "preview": "/****************************/\n/* OpenID Connect functions */\n/****************************/\n\n// Load the OpenID Provide"
  },
  {
    "path": "ch4/index.html",
    "chars": 3226,
    "preview": "<!DOCTYPE html>\n<html>\n<head>\n    <title>OpenID Connect Playground</title>\n    <script type=\"text/javascript\" src=\"KC_UR"
  },
  {
    "path": "ch4/package.json",
    "chars": 222,
    "preview": "{\n  \"name\": \"keycloak-example-app\",\n  \"version\": \"0.0.1\",\n  \"scripts\": {\n    \"start\": \"node app.js\"\n  },\n  \"dependencies"
  },
  {
    "path": "ch4/styles.css",
    "chars": 249,
    "preview": "body {\n    font-family: sans;\n}\n\nlabel {\n    display: inline-block;\n    font-size: 14px;\n    width: 150px;\n}\n\ninput {\n  "
  },
  {
    "path": "ch5/backend/Dockerfile",
    "chars": 115,
    "preview": "FROM node\nCOPY package.json .\nRUN npm install\nCOPY app.js .\nCOPY keycloak.json .\nEXPOSE 3000\nCMD [ \"npm\", \"start\" ]"
  },
  {
    "path": "ch5/backend/app.js",
    "chars": 977,
    "preview": "var express = require('express');\nvar session = require('express-session');\nvar Keycloak = require('keycloak-connect');\n"
  },
  {
    "path": "ch5/backend/keycloak.json",
    "chars": 178,
    "preview": "{\n  \"realm\": \"myrealm\",\n  \"bearer-only\": true,\n  \"auth-server-url\": \"${env.KC_URL:http://localhost:8080/auth}\",\n  \"resou"
  },
  {
    "path": "ch5/backend/package.json",
    "chars": 251,
    "preview": "{\n  \"name\": \"keycloak-example-service\",\n  \"version\": \"0.0.1\",\n  \"scripts\": {\n    \"start\": \"node app.js\"\n  },\n  \"dependen"
  },
  {
    "path": "ch5/frontend/Dockerfile",
    "chars": 148,
    "preview": "FROM node\nCOPY package.json .\nRUN npm install\nCOPY app.js .\nCOPY index.html .\nCOPY styles.css .\nCOPY client.js .\nEXPOSE "
  },
  {
    "path": "ch5/frontend/app.js",
    "chars": 455,
    "preview": "var express = require('express');\nvar app = express();\nvar stringReplace = require('string-replace-middleware');\n\nvar KC"
  },
  {
    "path": "ch5/frontend/client.js",
    "chars": 6272,
    "preview": "/***********************/\n/* OAuth 2.0 functions */\n/***********************/\n\n// Load the OpenID Provider Configuration"
  },
  {
    "path": "ch5/frontend/index.html",
    "chars": 2330,
    "preview": "<!DOCTYPE html>\n<html>\n<head>\n    <title>OAuth 2.0 Playground</title>\n    <script type=\"text/javascript\" src=\"KC_URL/js/"
  },
  {
    "path": "ch5/frontend/package.json",
    "chars": 222,
    "preview": "{\n  \"name\": \"keycloak-example-app\",\n  \"version\": \"0.0.1\",\n  \"scripts\": {\n    \"start\": \"node app.js\"\n  },\n  \"dependencies"
  },
  {
    "path": "ch5/frontend/styles.css",
    "chars": 249,
    "preview": "body {\n    font-family: sans;\n}\n\nlabel {\n    display: inline-block;\n    font-size: 14px;\n    width: 150px;\n}\n\ninput {\n  "
  },
  {
    "path": "ch6/app.js",
    "chars": 1157,
    "preview": "var express = require('express');\nvar open = require('open');\nvar axios = require('axios');\nvar querystring = require('q"
  },
  {
    "path": "ch6/package.json",
    "chars": 196,
    "preview": "{\n  \"name\": \"keycloak-example-app\",\n  \"version\": \"0.0.1\",\n  \"scripts\": {\n    \"start\": \"node app.js\"\n  },\n  \"dependencies"
  },
  {
    "path": "ch7/golang/go.mod",
    "chars": 584,
    "preview": "module github.com/abourget/getting-started-with-golang\n\ngo 1.12\n\nrequire (\n\tgithub.com/coreos/go-oidc v2.2.1+incompatibl"
  },
  {
    "path": "ch7/golang/go.sum",
    "chars": 37559,
    "preview": "cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ncloud.google.com/go v0.34.0/go.mod h1"
  },
  {
    "path": "ch7/golang/main.go",
    "chars": 4392,
    "preview": "/*\n This is an example about how to use a public client written in Golang to authenticate using Keycloak.\n This example "
  },
  {
    "path": "ch7/keycloak-js-adapter/app.js",
    "chars": 341,
    "preview": "var express = require('express');\nvar app = express();\nvar stringReplace = require('string-replace-middleware');\n\nvar KC"
  },
  {
    "path": "ch7/keycloak-js-adapter/index.html",
    "chars": 1653,
    "preview": "<!DOCTYPE html>\n<html>\n<head>\n    <title>Keycloak Example Application</title>\n    <script type=\"text/javascript\" src=\"KC"
  },
  {
    "path": "ch7/keycloak-js-adapter/package.json",
    "chars": 216,
    "preview": "{\n  \"name\": \"keycloak-js-app\",\n  \"version\": \"0.0.1\",\n  \"scripts\": {\n    \"start\": \"node app.js\"\n  },\n  \"dependencies\": {\n"
  },
  {
    "path": "ch7/nodejs/backend/app.js",
    "chars": 409,
    "preview": "var express = require('express');\nvar Keycloak = require('keycloak-connect');\n\nvar app = express();\n\nvar keycloak = new "
  },
  {
    "path": "ch7/nodejs/backend/keycloak.json",
    "chars": 139,
    "preview": "{\n  \"realm\": \"myrealm\",\n  \"bearer-only\": true,\n  \"auth-server-url\": \"${env.KC_URL:http://localhost:8180/auth}\",\n  \"resou"
  },
  {
    "path": "ch7/nodejs/backend/package.json",
    "chars": 250,
    "preview": "{\n  \"name\": \"keycloak-example-service\",\n  \"version\": \"0.0.1\",\n  \"scripts\": {\n    \"start\": \"node app.js\"\n  },\n  \"dependen"
  },
  {
    "path": "ch7/nodejs/frontend/app.js",
    "chars": 654,
    "preview": "var express = require('express');\nvar session = require('express-session');\nvar Keycloak = require('keycloak-connect');\n"
  },
  {
    "path": "ch7/nodejs/frontend/index.html",
    "chars": 123,
    "preview": "<!DOCTYPE html>\n<html>\n<head>\n    <title>Keycloak Example Application</title>\n    </script>\n</head>\n<body>\n\n</body>\n</ht"
  },
  {
    "path": "ch7/nodejs/frontend/keycloak.json",
    "chars": 171,
    "preview": "{\n  \"realm\": \"myrealm\",\n  \"auth-server-url\": \"${env.KC_URL:http://localhost:8180/auth}\",\n  \"resource\": \"mywebapp\",\n  \"cr"
  },
  {
    "path": "ch7/nodejs/frontend/package.json",
    "chars": 250,
    "preview": "{\n  \"name\": \"keycloak-nodejs-frontend\",\n  \"version\": \"0.0.1\",\n  \"scripts\": {\n    \"start\": \"node app.js\"\n  },\n  \"dependen"
  },
  {
    "path": "ch7/python/backend/app.py",
    "chars": 459,
    "preview": "import json\n\nfrom flask import Flask, g\napp = Flask(__name__)\napp.secret_key = 'change_me'\napp.config['OIDC_CLIENT_SECRE"
  },
  {
    "path": "ch7/python/backend/oidc-config.json",
    "chars": 614,
    "preview": "\n{\n  \"web\": {\n    \"client_id\": \"mybackend\",\n    \"client_secret\": CLIENT_SECRET,\n    \"auth_uri\": \"http://localhost:8180/a"
  },
  {
    "path": "ch7/python/frontend/app.py",
    "chars": 426,
    "preview": "from flask import Flask, g\napp = Flask(__name__)\napp.secret_key = 'change_me'\napp.config['OIDC_CLIENT_SECRETS'] = 'oidc-"
  },
  {
    "path": "ch7/python/frontend/oidc-config.json",
    "chars": 495,
    "preview": "{\n  \"web\": {\n    \"client_id\": \"mywebapp\",\n    \"client_secret\": CLIENT_SECRET,\n    \"auth_uri\": \"http://localhost:8180/aut"
  },
  {
    "path": "ch7/quarkus/backend/.mvn/wrapper/MavenWrapperDownloader.java",
    "chars": 4941,
    "preview": "/*\n * Copyright 2007-present the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \""
  },
  {
    "path": "ch7/quarkus/backend/.mvn/wrapper/maven-wrapper.properties",
    "chars": 218,
    "preview": "distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip\nwrap"
  },
  {
    "path": "ch7/quarkus/backend/mvnw",
    "chars": 10070,
    "preview": "#!/bin/sh\n# ----------------------------------------------------------------------------\n# Licensed to the Apache Softwa"
  },
  {
    "path": "ch7/quarkus/backend/mvnw.cmd",
    "chars": 6608,
    "preview": "@REM ----------------------------------------------------------------------------\n@REM Licensed to the Apache Software F"
  },
  {
    "path": "ch7/quarkus/backend/pom.xml",
    "chars": 4440,
    "preview": "<?xml version=\"1.0\"?>\n<project xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-"
  },
  {
    "path": "ch7/quarkus/backend/src/main/java/org/keycloak/GreetingResource.java",
    "chars": 299,
    "preview": "package org.keycloak;\n\nimport javax.ws.rs.GET;\nimport javax.ws.rs.Path;\nimport javax.ws.rs.Produces;\nimport javax.ws.rs."
  },
  {
    "path": "ch7/quarkus/backend/src/main/resources/META-INF/resources/index.html",
    "chars": 6201,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <title>quarkus - 1.0.0-SNAPSHOT</title>\n    <styl"
  },
  {
    "path": "ch7/quarkus/backend/src/main/resources/application.properties",
    "chars": 394,
    "preview": "quarkus.oidc.auth-server-url=http://localhost:8180/auth/realms/myrealm\nquarkus.oidc.client-id=mybackend\n# change CLIENT_"
  },
  {
    "path": "ch7/quarkus/frontend/.mvn/wrapper/MavenWrapperDownloader.java",
    "chars": 4941,
    "preview": "/*\n * Copyright 2007-present the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \""
  },
  {
    "path": "ch7/quarkus/frontend/.mvn/wrapper/maven-wrapper.properties",
    "chars": 218,
    "preview": "distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip\nwrap"
  },
  {
    "path": "ch7/quarkus/frontend/mvnw",
    "chars": 10070,
    "preview": "#!/bin/sh\n# ----------------------------------------------------------------------------\n# Licensed to the Apache Softwa"
  },
  {
    "path": "ch7/quarkus/frontend/mvnw.cmd",
    "chars": 6608,
    "preview": "@REM ----------------------------------------------------------------------------\n@REM Licensed to the Apache Software F"
  },
  {
    "path": "ch7/quarkus/frontend/pom.xml",
    "chars": 4440,
    "preview": "<?xml version=\"1.0\"?>\n<project xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-"
  },
  {
    "path": "ch7/quarkus/frontend/src/main/java/org/keycloak/GreetingResource.java",
    "chars": 299,
    "preview": "package org.keycloak;\n\nimport javax.ws.rs.GET;\nimport javax.ws.rs.Path;\nimport javax.ws.rs.Produces;\nimport javax.ws.rs."
  },
  {
    "path": "ch7/quarkus/frontend/src/main/resources/META-INF/resources/index.html",
    "chars": 6201,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <title>quarkus - 1.0.0-SNAPSHOT</title>\n    <styl"
  },
  {
    "path": "ch7/quarkus/frontend/src/main/resources/application.properties",
    "chars": 393,
    "preview": "quarkus.oidc.auth-server-url=http://localhost:8180/auth/realms/myrealm\nquarkus.oidc.client-id=mywebapp\n# change CLIENT_S"
  },
  {
    "path": "ch7/reverse-proxy/app/app.js",
    "chars": 560,
    "preview": "var express = require('express');\nvar app = express();\n\napp.get('/', function(req, res) {\n    var response = '<html><bod"
  },
  {
    "path": "ch7/reverse-proxy/app/package.json",
    "chars": 155,
    "preview": "{\n  \"name\": \"keycloak-example-app\",\n  \"version\": \"0.0.1\",\n  \"scripts\": {\n    \"start\": \"node app.js\"\n  },\n  \"dependencies"
  },
  {
    "path": "ch7/reverse-proxy/secure-proxy.conf",
    "chars": 637,
    "preview": "LoadModule auth_openidc_module modules/mod_auth_openidc.so\n\nServerName localhost\n\n<VirtualHost *:80>\n    ProxyPass / htt"
  },
  {
    "path": "ch7/springboot/backend/.mvn/wrapper/MavenWrapperDownloader.java",
    "chars": 4941,
    "preview": "/*\n * Copyright 2007-present the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \""
  },
  {
    "path": "ch7/springboot/backend/.mvn/wrapper/maven-wrapper.properties",
    "chars": 218,
    "preview": "distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip\nwrap"
  },
  {
    "path": "ch7/springboot/backend/build.gradle",
    "chars": 431,
    "preview": "plugins {\n\tid 'org.springframework.boot' version '2.4.2'\n\tid 'io.spring.dependency-management' version '1.0.11.RELEASE'\n"
  },
  {
    "path": "ch7/springboot/backend/gradlew",
    "chars": 5960,
    "preview": "#!/usr/bin/env sh\n\n#\n# Copyright 2015 the original author or authors.\n#\n# Licensed under the Apache License, Version 2.0"
  },
  {
    "path": "ch7/springboot/backend/gradlew.bat",
    "chars": 2942,
    "preview": "@rem\r\n@rem Copyright 2015 the original author or authors.\r\n@rem\r\n@rem Licensed under the Apache License, Version 2.0 (th"
  },
  {
    "path": "ch7/springboot/backend/mvnw",
    "chars": 9895,
    "preview": "#!/bin/sh\n# ----------------------------------------------------------------------------\n# Licensed to the Apache Softwa"
  },
  {
    "path": "ch7/springboot/backend/mvnw.cmd",
    "chars": 6301,
    "preview": "@REM ----------------------------------------------------------------------------\n@REM Licensed to the Apache Software F"
  },
  {
    "path": "ch7/springboot/backend/pom.xml",
    "chars": 1575,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2"
  },
  {
    "path": "ch7/springboot/backend/settings.gradle",
    "chars": 32,
    "preview": "rootProject.name = 'spring-boot'"
  },
  {
    "path": "ch7/springboot/backend/src/main/java/org/keycloak/springboot/Application.java",
    "chars": 641,
    "preview": "package org.keycloak.springboot;\n\nimport java.util.Arrays;\n\nimport org.springframework.boot.SpringApplication;\nimport or"
  },
  {
    "path": "ch7/springboot/backend/src/main/java/org/keycloak/springboot/HelloController.java",
    "chars": 301,
    "preview": "package org.keycloak.springboot;\n\nimport org.springframework.web.bind.annotation.RestController;\nimport org.springframew"
  },
  {
    "path": "ch7/springboot/backend/src/main/java/org/keycloak/springboot/SecurityConfig.java",
    "chars": 651,
    "preview": "package org.keycloak.springboot;\n\nimport org.springframework.security.config.annotation.web.builders.HttpSecurity;\nimpor"
  },
  {
    "path": "ch7/springboot/backend/src/main/resources/application.yaml",
    "chars": 130,
    "preview": "spring:\n  security:\n    oauth2:\n      resourceserver:\n        jwt:\n          issuer-uri: http://localhost:8180/auth/real"
  },
  {
    "path": "ch7/springboot/backend-using-introspection/.mvn/wrapper/maven-wrapper.properties",
    "chars": 221,
    "preview": "distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip\nwrap"
  },
  {
    "path": "ch7/springboot/backend-using-introspection/build.gradle",
    "chars": 431,
    "preview": "plugins {\n\tid 'org.springframework.boot' version '2.4.2'\n\tid 'io.spring.dependency-management' version '1.0.11.RELEASE'\n"
  },
  {
    "path": "ch7/springboot/backend-using-introspection/gradlew",
    "chars": 5960,
    "preview": "#!/usr/bin/env sh\n\n#\n# Copyright 2015 the original author or authors.\n#\n# Licensed under the Apache License, Version 2.0"
  },
  {
    "path": "ch7/springboot/backend-using-introspection/gradlew.bat",
    "chars": 2942,
    "preview": "@rem\r\n@rem Copyright 2015 the original author or authors.\r\n@rem\r\n@rem Licensed under the Apache License, Version 2.0 (th"
  },
  {
    "path": "ch7/springboot/backend-using-introspection/mvnw",
    "chars": 9895,
    "preview": "#!/bin/sh\n# ----------------------------------------------------------------------------\n# Licensed to the Apache Softwa"
  },
  {
    "path": "ch7/springboot/backend-using-introspection/mvnw.cmd",
    "chars": 6301,
    "preview": "@REM ----------------------------------------------------------------------------\n@REM Licensed to the Apache Software F"
  },
  {
    "path": "ch7/springboot/backend-using-introspection/pom.xml",
    "chars": 1779,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2"
  },
  {
    "path": "ch7/springboot/backend-using-introspection/settings.gradle",
    "chars": 32,
    "preview": "rootProject.name = 'spring-boot'"
  },
  {
    "path": "ch7/springboot/backend-using-introspection/src/main/java/org/keycloak/springboot/Application.java",
    "chars": 641,
    "preview": "package org.keycloak.springboot;\n\nimport java.util.Arrays;\n\nimport org.springframework.boot.SpringApplication;\nimport or"
  },
  {
    "path": "ch7/springboot/backend-using-introspection/src/main/java/org/keycloak/springboot/HelloController.java",
    "chars": 301,
    "preview": "package org.keycloak.springboot;\n\nimport org.springframework.web.bind.annotation.RestController;\nimport org.springframew"
  },
  {
    "path": "ch7/springboot/backend-using-introspection/src/main/java/org/keycloak/springboot/SecurityConfig.java",
    "chars": 659,
    "preview": "package org.keycloak.springboot;\n\nimport org.springframework.security.config.annotation.web.builders.HttpSecurity;\nimpor"
  },
  {
    "path": "ch7/springboot/backend-using-introspection/src/main/resources/application.yaml",
    "chars": 320,
    "preview": "spring:\n  security:\n    oauth2:\n      resourceserver:\n        opaquetoken:\n          client-id: mybackend\n          clie"
  },
  {
    "path": "ch7/springboot/frontend/.mvn/wrapper/MavenWrapperDownloader.java",
    "chars": 4941,
    "preview": "/*\n * Copyright 2007-present the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \""
  },
  {
    "path": "ch7/springboot/frontend/.mvn/wrapper/maven-wrapper.properties",
    "chars": 218,
    "preview": "distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip\nwrap"
  },
  {
    "path": "ch7/springboot/frontend/build.gradle",
    "chars": 431,
    "preview": "plugins {\n\tid 'org.springframework.boot' version '2.4.2'\n\tid 'io.spring.dependency-management' version '1.0.11.RELEASE'\n"
  },
  {
    "path": "ch7/springboot/frontend/gradlew",
    "chars": 5960,
    "preview": "#!/usr/bin/env sh\n\n#\n# Copyright 2015 the original author or authors.\n#\n# Licensed under the Apache License, Version 2.0"
  },
  {
    "path": "ch7/springboot/frontend/gradlew.bat",
    "chars": 2942,
    "preview": "@rem\r\n@rem Copyright 2015 the original author or authors.\r\n@rem\r\n@rem Licensed under the Apache License, Version 2.0 (th"
  },
  {
    "path": "ch7/springboot/frontend/mvnw",
    "chars": 9895,
    "preview": "#!/bin/sh\n# ----------------------------------------------------------------------------\n# Licensed to the Apache Softwa"
  },
  {
    "path": "ch7/springboot/frontend/mvnw.cmd",
    "chars": 6301,
    "preview": "@REM ----------------------------------------------------------------------------\n@REM Licensed to the Apache Software F"
  },
  {
    "path": "ch7/springboot/frontend/pom.xml",
    "chars": 1547,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2"
  },
  {
    "path": "ch7/springboot/frontend/settings.gradle",
    "chars": 32,
    "preview": "rootProject.name = 'spring-boot'"
  },
  {
    "path": "ch7/springboot/frontend/src/main/java/com/example/springboot/Application.java",
    "chars": 640,
    "preview": "package com.example.springboot;\n\nimport java.util.Arrays;\n\nimport org.springframework.boot.SpringApplication;\nimport org"
  },
  {
    "path": "ch7/springboot/frontend/src/main/java/com/example/springboot/HelloController.java",
    "chars": 300,
    "preview": "package com.example.springboot;\n\nimport org.springframework.web.bind.annotation.RestController;\nimport org.springframewo"
  },
  {
    "path": "ch7/springboot/frontend/src/main/resources/application.yaml",
    "chars": 768,
    "preview": "spring:\n  security:\n    oauth2:\n      client:\n        registration:\n          myfrontend:\n            provider: keycloak"
  },
  {
    "path": "ch9/configure-caches.cli",
    "chars": 660,
    "preview": "embed-server --server-config=standalone-ha.xml --std-out=discard\n/subsystem=infinispan/cache-container=keycloak/distribu"
  },
  {
    "path": "ch9/configure-database.cli",
    "chars": 740,
    "preview": "embed-server --server-config=standalone-ha.xml --std-out=discard\nmodule add --name=org.postgres --resources=<PATH_TO_JDB"
  },
  {
    "path": "ch9/configure-hostname.cli",
    "chars": 348,
    "preview": "embed-server --server-config=standalone-ha.xml --std-out=discard\n/subsystem=keycloak-server/spi=hostname/provider=defaul"
  },
  {
    "path": "ch9/configure-https.cli",
    "chars": 695,
    "preview": "embed-server --server-config=standalone-ha.xml --std-out=discard\n/subsystem=elytron/key-store=kcKeyStore:add(path=${jbos"
  },
  {
    "path": "ch9/configure-proxy.cli",
    "chars": 207,
    "preview": "embed-server --server-config=standalone-ha.xml --std-out=discard\n/subsystem=undertow/server=default-server/https-listene"
  },
  {
    "path": "ch9/configure-session-affinity.cli",
    "chars": 268,
    "preview": "embed-server --server-config=standalone-ha.xml --std-out=discard\n/subsystem=keycloak-server/spi=stickySessionEncoder:add"
  },
  {
    "path": "ch9/haproxy.cfg",
    "chars": 2581,
    "preview": "#---------------------------------------------------------------------\n# Global settings\n#------------------------------"
  },
  {
    "path": "ch9/haproxy.crt.pem",
    "chars": 3091,
    "preview": "-----BEGIN CERTIFICATE-----\nMIID0zCCArugAwIBAgIUEhaz7pf/ievSFyNu2CCKPUEPUj8wDQYJKoZIhvcNAQEL\nBQAweTELMAkGA1UEBhMCWFgxCzA"
  },
  {
    "path": "ch9/mykeycloak.crt",
    "chars": 1387,
    "preview": "-----BEGIN CERTIFICATE-----\nMIID0zCCArugAwIBAgIUEhaz7pf/ievSFyNu2CCKPUEPUj8wDQYJKoZIhvcNAQEL\nBQAweTELMAkGA1UEBhMCWFgxCzA"
  },
  {
    "path": "ch9/mykeycloak.key",
    "chars": 1704,
    "preview": "-----BEGIN PRIVATE KEY-----\nMIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDWLVUprRKsn6br\n7MDsL5/ddoE1wpj710h/GE/Op3N"
  }
]

// ... and 9 more files (download for full content)

About this extraction

This page contains the full source code of the PacktPublishing/Keycloak-Identity-and-Access-Management-for-Modern-Applications GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 146 files (324.7 KB), approximately 104.7k tokens, and a symbol index with 133 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!