Merge branch 'stable-2.15'

* stable-2.15:
  Fix typo in README.md
  SAML providers: put proper links to SAML providers config
  Documentation: move build instructions into README
  SamlWebFilter: Fix AnonymousHttpRequest.getHeaderNames method

Change-Id: I99f0c1df34a88a83f89241b42b53c7f57d2ee09f
diff --git a/README.md b/README.md
index c5d1a3b..13a0625 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
-# Gerrit SAML Plugin
+# Gerrit SAML Authentication Filter
 
-This plugin allows you to authenticate to Gerrit using a SAML identity
+This filter allows you to authenticate to Gerrit using a SAML identity
 provider.
 
 ## Installation
@@ -14,35 +14,65 @@
 If any of these attributes is not found in the assertion, their value is
 taken from the NameId field of the SAML assertion.
 
-### Setting Gerrit in your IdP (Okta, Onelogin, ...)
+### Setting Gerrit in your IdP
 
-- Create a new SAML 2.0 application.
-- Set the following parameters:
-  - Single sign on URL: http://gerrit.site.com/plugins/saml/callback
-  - Check "Use this for Recipient URL and Destination URL".
-  - Audience URI (SP Entity Id): http://gerrit.site.com/plugins/saml/callback
-  - We need to set up the attributes in the assertion to send the right
-    information. Here is how to do it with Okta:
-    - Application username: "Okta username prefix"
-    - Add attribute statement: Name: "DisplayName" with Value
-      "user.displayName"
-    - Add attribute statement: Name: "EmailAddress" with Value
-      "user.email"
-    - **IMPORTANT**: If you are not using Okta, you need to set up an attribute
-      "UserName" with the value of the username (not email, without @). If you
-      do not do so, the name will be taken from the NameId provided by
-      the assertion.  This is why in Okta we set the application username to
-      "Okta username prefix".
-- Obtain your IdP metadata (either URL or a local XML file)
-
-If you are using Active Directory Federation Services (ADFS), follow the below steps to configure Gerrit.
-You can then [go here](doc/Setup_ADFS.md) for more details on how to make the saml plugin work with ADFS.
+- [Okta](okta/README.md)
+- [Keycloak](keycloak/README.md)
+- [ADFS](adfs/README.md)
 
 ### Download the plugin
 
 Download Gerrit SAML plugin for the appropriate version of gerrit from the [Gerrit-CI](https://gerrit-ci.gerritforge.com/search/?q=saml)
 into $gerrit_site/lib/.
 
+### Building the SAML filter
+
+This authentication filter is built with Bazel.
+
+## Build in Gerrit tree
+
+Clone or link this filter to the plugins directory of Gerrit's
+source tree. Put the external dependency Bazel build file into
+the Gerrit /plugins directory, replacing the existing empty one.
+
+```
+  cd gerrit/plugins
+  rm external_plugin_deps.bzl
+  ln -s @PLUGIN@/external_plugin_deps.bzl .
+```
+
+Then issue
+
+```
+  bazel build plugins/@PLUGIN@
+```
+
+The output is created in
+
+```
+  bazel-genfiles/plugins/@PLUGIN@/@PLUGIN@.jar
+```
+
+The @PLUGIN@.jar should be deployed to `gerrit_site/lib` directory:
+
+```
+ cp bazel-genfiles/plugins/@PLUGIN@/@PLUGIN@.jar `$gerrit_site/lib`
+```
+
+__NOTE__: Even though the project is built as a Gerrit plugin, it must be loaded
+as a Servlet filter by Gerrit and thus needs to be located with the libraries and
+cannot be dynamically loaded like other plugins.
+
+This project can be imported into the Eclipse IDE.
+Add the plugin name to the `CUSTOM_PLUGINS` set in
+Gerrit core in `tools/bzl/plugins.bzl`, and execute:
+
+```
+  ./tools/eclipse/project.py
+```
+
+How to build the Gerrit Plugin API is described in the [Gerrit documentation](../../../Documentation/dev-bazel.html#_extension_and_plugin_api_jar_files).
+
 ### Configure Gerrit to use the SAML filter:
 In `$site_path/etc/gerrit.config` file, the `[httpd]` section should contain
 
diff --git a/doc/Setup_ADFS.md b/adfs/README.md
similarity index 97%
rename from doc/Setup_ADFS.md
rename to adfs/README.md
index d262569..60ddb1f 100644
--- a/doc/Setup_ADFS.md
+++ b/adfs/README.md
@@ -1,4 +1,5 @@
-# Setting-up gerrit-saml-plugin for Active Directory Federation Services (ADFS)
+# ADFS as Gerrit SAML authentication provider
+
 Note: replace `fs.hc.sct` with the name of your ADFS, replace gerrit.hc.sct with the name of your Gerrit host.
 
 ## Setup on the Gerrit machine
diff --git a/adfs/images/0.png b/adfs/images/0.png
new file mode 100644
index 0000000..2330f30
--- /dev/null
+++ b/adfs/images/0.png
Binary files differ
diff --git a/adfs/images/1.png b/adfs/images/1.png
new file mode 100644
index 0000000..a965a46
--- /dev/null
+++ b/adfs/images/1.png
Binary files differ
diff --git a/adfs/images/10.png b/adfs/images/10.png
new file mode 100644
index 0000000..3e0e911
--- /dev/null
+++ b/adfs/images/10.png
Binary files differ
diff --git a/adfs/images/11.png b/adfs/images/11.png
new file mode 100644
index 0000000..1544a2a
--- /dev/null
+++ b/adfs/images/11.png
Binary files differ
diff --git a/adfs/images/12.png b/adfs/images/12.png
new file mode 100644
index 0000000..838ebea
--- /dev/null
+++ b/adfs/images/12.png
Binary files differ
diff --git a/adfs/images/13.png b/adfs/images/13.png
new file mode 100644
index 0000000..acb2acb
--- /dev/null
+++ b/adfs/images/13.png
Binary files differ
diff --git a/adfs/images/14.png b/adfs/images/14.png
new file mode 100644
index 0000000..4a85a4b
--- /dev/null
+++ b/adfs/images/14.png
Binary files differ
diff --git a/adfs/images/15.png b/adfs/images/15.png
new file mode 100644
index 0000000..bb86e86
--- /dev/null
+++ b/adfs/images/15.png
Binary files differ
diff --git a/adfs/images/2.png b/adfs/images/2.png
new file mode 100644
index 0000000..0039e21
--- /dev/null
+++ b/adfs/images/2.png
Binary files differ
diff --git a/adfs/images/3.png b/adfs/images/3.png
new file mode 100644
index 0000000..f5d528a
--- /dev/null
+++ b/adfs/images/3.png
Binary files differ
diff --git a/adfs/images/4.png b/adfs/images/4.png
new file mode 100644
index 0000000..f9da22c
--- /dev/null
+++ b/adfs/images/4.png
Binary files differ
diff --git a/adfs/images/5.png b/adfs/images/5.png
new file mode 100644
index 0000000..c6e0e13
--- /dev/null
+++ b/adfs/images/5.png
Binary files differ
diff --git a/adfs/images/6.png b/adfs/images/6.png
new file mode 100644
index 0000000..03a3c5d
--- /dev/null
+++ b/adfs/images/6.png
Binary files differ
diff --git a/adfs/images/7.png b/adfs/images/7.png
new file mode 100644
index 0000000..fe8b665
--- /dev/null
+++ b/adfs/images/7.png
Binary files differ
diff --git a/adfs/images/8.png b/adfs/images/8.png
new file mode 100644
index 0000000..2943fe7
--- /dev/null
+++ b/adfs/images/8.png
Binary files differ
diff --git a/adfs/images/9.png b/adfs/images/9.png
new file mode 100644
index 0000000..2fc2f20
--- /dev/null
+++ b/adfs/images/9.png
Binary files differ
diff --git a/keycloak/README.md b/keycloak/README.md
new file mode 100644
index 0000000..bf9939d
--- /dev/null
+++ b/keycloak/README.md
@@ -0,0 +1,85 @@
+# Keycloak as Gerrit SAML provider
+
+[Keycloak](https://www.keycloak.org/) is open source Identity and Access
+Management tool and supports the SAML authentication protocol.
+
+## Objective
+
+This document provides a step-by-step tutorial how to set-up Keycloak as
+SAML provider for Gerrit Code Review for development and guidance only.
+For production HTTPS protocol and other more secure credentials and keys
+would need to be put in place.
+
+## Prerequisites
+
+- [Docker](https://www.docker.com/get-started)
+- [Docker-compose](https://docs.docker.com/compose/)
+- [Gerrit Code Review v2.15 or later](https://www.gerritcodereview.com)
+
+## Steps
+
+1. Install Keycloak official Docker image from this repository and start it:
+
+```bash
+  $ git clone https://github.com/jboss-dockerfiles/keycloak
+  $ cd keycloak/docker-compose-examples
+  $ docker-compose -f keycloak-postgres.yml up
+```
+
+2. Login to Keycloak using user=admin and password=Pa55w0rd credentials and import
+the Gerrit client [keycloak json file](keycloak-gerrit-client-export.json).
+
+3. Create test user (e.g., fullname="John Doe", username "jdoe", email: "john@doe.org", password "secret", Temporary=OFF)
+
+4. Add the following configuration settings to $GERRIT_SITE/etc/gerrit.config:
+
+```
+[auth]
+    type = HTTP
+    logoutUrl = http://localhost:8080/auth/realms/master/protocol/openid-connect/logout
+    httpHeader = X-SAML-UserName
+    httpDisplaynameHeader = X-SAML-DisplayName
+    httpEmailHeader = X-SAML-EmailHeader
+    httpExternalIdHeader = X-SAML-ExternalId
+
+[httpd]
+    listenUrl = http://*:8081/
+    filterClass = com.googlesource.gerrit.plugins.saml.SamlWebFilter
+
+[saml]
+    serviceProviderEntityId = SAML2Client
+    keystorePath = etc/samlKeystore.jks
+    keystorePassword = pac4j-demo-password
+    privateKeyPassword = pac4j-demo-password
+    metadataPath = http://localhost:8080/auth/realms/master/protocol/saml/descriptor
+    userNameAttr = UserName
+    displayNameAttr = DisplayName
+    emailAddressAttr = EmailAddress
+    computedDisplayName = true
+    firstNameAttr = firstName
+    lastNameAttr = lastName
+```
+
+5. Generate keystore in `$GERRIT_SITE/etc` local keystore:
+
+```
+keytool -genkeypair -alias pac4j -keypass pac4j-demo-password \
+  -keystore samlKeystore.jks \
+  -storepass pac4j-demo-password -keyalg RSA -keysize 2048 -validity 365
+```
+
+6. Install the saml.jar filter into the `$GERRIT_SITE/lib` directory
+
+7. Start gerrit using: `$GERRIT_SITE/bin/gerrit.sh start`
+
+8. Enter gerrit URL in browser: http://localhost:8081 and hit "Sign In" button
+
+9. Keycloak Login Dialog should appear
+
+10. Enter user: "jdoe" and password: "secret"
+
+11. You are redirected to gerrit and the first user/admin John Doe is created
+in gerrit with the right user name and email address.
+
+12. Congrats, you have Gerrit / Keycloak SAML integration up and running.
+
diff --git a/keycloak/keycloak-gerrit-client-export.json b/keycloak/keycloak-gerrit-client-export.json
new file mode 100644
index 0000000..e6b6df6
--- /dev/null
+++ b/keycloak/keycloak-gerrit-client-export.json
@@ -0,0 +1,1692 @@
+{
+  "id": "master",
+  "realm": "master",
+  "displayName": "Keycloak",
+  "displayNameHtml": "<div class=\"kc-logo-text\"><span>Keycloak</span></div>",
+  "notBefore": 0,
+  "revokeRefreshToken": false,
+  "refreshTokenMaxReuse": 0,
+  "accessTokenLifespan": 60,
+  "accessTokenLifespanForImplicitFlow": 900,
+  "ssoSessionIdleTimeout": 1800,
+  "ssoSessionMaxLifespan": 36000,
+  "ssoSessionIdleTimeoutRememberMe": 0,
+  "ssoSessionMaxLifespanRememberMe": 0,
+  "offlineSessionIdleTimeout": 2592000,
+  "offlineSessionMaxLifespanEnabled": false,
+  "offlineSessionMaxLifespan": 5184000,
+  "accessCodeLifespan": 60,
+  "accessCodeLifespanUserAction": 300,
+  "accessCodeLifespanLogin": 1800,
+  "actionTokenGeneratedByAdminLifespan": 43200,
+  "actionTokenGeneratedByUserLifespan": 300,
+  "enabled": true,
+  "sslRequired": "external",
+  "registrationAllowed": false,
+  "registrationEmailAsUsername": false,
+  "rememberMe": false,
+  "verifyEmail": false,
+  "loginWithEmailAllowed": true,
+  "duplicateEmailsAllowed": false,
+  "resetPasswordAllowed": false,
+  "editUsernameAllowed": false,
+  "bruteForceProtected": false,
+  "permanentLockout": false,
+  "maxFailureWaitSeconds": 900,
+  "minimumQuickLoginWaitSeconds": 60,
+  "waitIncrementSeconds": 60,
+  "quickLoginCheckMilliSeconds": 1000,
+  "maxDeltaTimeSeconds": 43200,
+  "failureFactor": 30,
+  "defaultRoles": [
+    "offline_access",
+    "uma_authorization"
+  ],
+  "requiredCredentials": [
+    "password"
+  ],
+  "otpPolicyType": "totp",
+  "otpPolicyAlgorithm": "HmacSHA1",
+  "otpPolicyInitialCounter": 0,
+  "otpPolicyDigits": 6,
+  "otpPolicyLookAheadWindow": 1,
+  "otpPolicyPeriod": 30,
+  "otpSupportedApplications": [
+    "FreeOTP",
+    "Google Authenticator"
+  ],
+  "scopeMappings": [
+    {
+      "clientScope": "offline_access",
+      "roles": [
+        "offline_access"
+      ]
+    }
+  ],
+  "clients": [
+    {
+      "id": "3896eefc-4131-4911-96cd-6e1d2aea787f",
+      "clientId": "SAML2Client",
+      "name": "Gerrit",
+      "description": "Gerrit SSO",
+      "rootUrl": "http://localhost:8081/plugins/saml/callback",
+      "adminUrl": "http://localhost:8081/",
+      "baseUrl": "http://localhost:8081/",
+      "surrogateAuthRequired": false,
+      "enabled": true,
+      "clientAuthenticatorType": "client-secret",
+      "secret": "**********",
+      "redirectUris": [
+        "http://localhost:8081/plugins/saml/callback?client_name=SAML2Client"
+      ],
+      "webOrigins": [],
+      "notBefore": 0,
+      "bearerOnly": false,
+      "consentRequired": false,
+      "standardFlowEnabled": true,
+      "implicitFlowEnabled": false,
+      "directAccessGrantsEnabled": false,
+      "serviceAccountsEnabled": false,
+      "publicClient": false,
+      "frontchannelLogout": true,
+      "protocol": "saml",
+      "attributes": {
+        "saml.assertion.signature": "true",
+        "saml.force.post.binding": "false",
+        "saml.multivalued.roles": "false",
+        "saml_single_logout_service_url_post": "http://localhost:8081/plugins/saml/callback?client_name=SAML2Client&logoutendpoint=true",
+        "saml.encrypt": "false",
+        "login_theme": "keycloak",
+        "saml_assertion_consumer_url_post": "http://localhost:8081/",
+        "saml.server.signature": "true",
+        "saml.server.signature.keyinfo.ext": "false",
+        "exclude.session.state.from.auth.response": "false",
+        "saml.signing.certificate": "MIICmzCCAYMCBgFn6ouxezANBgkqhkiG9w0BAQsFADARMQ8wDQYDVQQDDAZnZXJyaXQwHhcNMTgxMjI2MTI0MzAyWhcNMjgxMjI2MTI0NDQyWjARMQ8wDQYDVQQDDAZnZXJyaXQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCBtzB/DRx30RzqRPtJhbe0fWFzZf7wzO9ZjhoS4uYwWBO/FLfI2Qma3/9Q060kKuTlHFHZvrMemUP9Xlc/2+qv+M5pNKRabfwnufdR1zofPnJdazQ6RP41FK/cLLN1rd8naJQ+ZjKE6Di0k55YTUbnbiVgcfjtl/2oQI/HxdpQE5dDWbOkCtBQa6mmznxtjTsOjrE9xKZUNIBNkTGGuWU+NfFkx8oOIpNlChdbcsbZiXceYU8LS6mOjiycZ7XmQe4IEVHVdBbUnsmRK++rqoIY5tNAVWsT+YoiCdGJ+iNYbm7B1Jq0EDYOymiGvcKeMi5wmUdGJ7yxIZxTeenqfq39AgMBAAEwDQYJKoZIhvcNAQELBQADggEBAFGgLTbNO8CfqN/MblAKLAKWvycV5FtFlJ2z75NsL2Oz7vc/pwWfLPCQoG5bsPUVwQnp3TmIQeZtoWwkh6Ews5hmS3cRbbswgYpT60FtHuEZAdrqsbXzHv4xfQd5hT5uvhMRj98OReKKap+Se+qNYzKvB6fxYLp0SWm9VOwHEeh+kMA0ZkAeW2m5YHN1T98fsFMLEiOJdGJS6hKpoMm6SofKEgVRdoz72xwEW96dqbDoJlo2/IFqw/tMsDXfyrhQbVlHPBMgRb++hQGyh4hps82TH2mzcvks1geVqXrx+1MyjSJd12+BgOHX40k25FPoQdu15AGYwET5nq/7HZ3F+/M=",
+        "saml_single_logout_service_url_redirect": "",
+        "saml.signature.algorithm": "RSA_SHA256",
+        "saml_force_name_id_format": "true",
+        "saml.client.signature": "false",
+        "tls.client.certificate.bound.access.tokens": "false",
+        "saml.authnstatement": "true",
+        "display.on.consent.screen": "false",
+        "saml.signing.private.key": "MIIEowIBAAKCAQEAgbcwfw0cd9Ec6kT7SYW3tH1hc2X+8MzvWY4aEuLmMFgTvxS3yNkJmt//UNOtJCrk5RxR2b6zHplD/V5XP9vqr/jOaTSkWm38J7n3Udc6Hz5yXWs0OkT+NRSv3Cyzda3fJ2iUPmYyhOg4tJOeWE1G524lYHH47Zf9qECPx8XaUBOXQ1mzpArQUGupps58bY07Do6xPcSmVDSATZExhrllPjXxZMfKDiKTZQoXW3LG2Yl3HmFPC0upjo4snGe15kHuCBFR1XQW1J7JkSvvq6qCGObTQFVrE/mKIgnRifojWG5uwdSatBA2Dspohr3CnjIucJlHRie8sSGcU3np6n6t/QIDAQABAoIBABip6TmvF4Ocqh/NH+5501UpJddYRGIqxTPE9iYzKEt248JIQS2aPt5IyvXmWZxv33fEq7d4L/yYbboGLxbATN5Ks4yDauCa2v/+twzDnJSGPh6PHK4boi4bkdiOU00D01Nd6hn3OpHUybtj+g6WGu40Hj05Tnh2ls9f/zaf4wOofQxo3YctgtHtM5QI0/QwVmuo4p6XoFIpyItHyt3o4MigodNQLkcAQHFSiNdoMLX+BG8dobB/QSFeJhrrXjOSQyk09Kpwl7dDRLtKXx5pthJJitBkuon3aJ60vY7UN/tAcxvzThHiOiiXasJ2ahRvw2ikhAJEONxUxV9em542t80CgYEA+ydmunyUglhhswzHK2HFhdq+T8SgXh3VOy6j4qe1e9vWL1zAmG24cDtQ0lWIZ/KtTLl2LwEtTjx3JVLw3QJ+79WTRJ7GA9ZINJc2ahwaRT9FZgDZA1UvA4CdsWy3Nxm+uAE97Dtke4/C4gqglgVCPRU2m9DsKKqESFYoe0sfBt8CgYEAhDfuk/5/E7Jk3EzlzzT8TwliV0DDgCiF9ebF5sfDvPpkTzYmQ9MwUTDFU1007D3O5+ko0rV6YaKtwDP3JIOGC0tzeVl/NbyOa8DirKPdTyeqPPM8VGDil27q5cxniUt5+9A20oW7EvZ7Q3jyVCCX2lbYw68UhXLedX56r1cScqMCgYAe0qAV5PIo6QXcfoX2+gHOwqC2k3AG/OxNXhT43RI1yC4KE/0C9/w5sd3iYmLiNvMzxlMNw7w2rM8Ggp5S0VqYvoJbWoz8rZCg+6nO7fH/a5ttwE7hzNN/P4qa2rfFiGBSnrxlwIg9bdBCA4Hfx3dwnajdCI/jEcI4SnKfZwHAywKBgBqLuETSZeUofgOVFNbDRpz/v1TRVe8XM74pNTaeiPgaNaWJs6kOb7b9WcDhB80eo7oAIAgeE5IivUvHoykblwARh/+nLlk1oEqWEWykAbzws8dE9qniQdwxksvMfEUeeEFMjpU58FNgVWM4lz64xIQ7x42SS0Z3x55DZJJrovKNAoGBAPg56n+3drjj7FU11UnrkuSn2fqcdGbR5kKex/EQkBLOjB5mPxmI4h9Ycd2K9mBqTw+mYvmD962mJPPmDy4eyUtdcHe/mVCVT0jXPHCUumA2rg4VlF0Da1eiMzd7CwCNa6uq9flBzkEqx0ljO3d5nllFyLwwsYTyrmaxMbcziGTj",
+        "saml_name_id_format": "username",
+        "saml.onetimeuse.condition": "false",
+        "saml_signature_canonicalization_method": "http://www.w3.org/2001/10/xml-exc-c14n#"
+      },
+      "authenticationFlowBindingOverrides": {},
+      "fullScopeAllowed": true,
+      "nodeReRegistrationTimeout": -1,
+      "protocolMappers": [
+        {
+          "id": "771ab333-d236-449a-a298-9b3027fb760e",
+          "name": "lastName",
+          "protocol": "saml",
+          "protocolMapper": "saml-user-property-mapper",
+          "consentRequired": false,
+          "config": {
+            "attribute.nameformat": "Unspecified",
+            "user.attribute": "lastName",
+            "friendly.name": "lastName",
+            "attribute.name": "lastName"
+          }
+        },
+        {
+          "id": "537a1547-d97d-4dd6-8b65-f7204e19bcb1",
+          "name": "UserName",
+          "protocol": "saml",
+          "protocolMapper": "saml-user-property-mapper",
+          "consentRequired": false,
+          "config": {
+            "user.attribute": "username",
+            "friendly.name": "username",
+            "attribute.name": "UserName"
+          }
+        },
+        {
+          "id": "a5786003-1c28-48d9-b1d2-f760b1541057",
+          "name": "firstName",
+          "protocol": "saml",
+          "protocolMapper": "saml-user-property-mapper",
+          "consentRequired": false,
+          "config": {
+            "attribute.nameformat": "Unspecified",
+            "user.attribute": "firstName",
+            "friendly.name": "firstName",
+            "attribute.name": "firstName"
+          }
+        },
+        {
+          "id": "a3e8549e-3f92-43b8-89be-5368aaf2a6f9",
+          "name": "EmailAddress",
+          "protocol": "saml",
+          "protocolMapper": "saml-user-property-mapper",
+          "consentRequired": false,
+          "config": {
+            "user.attribute": "email",
+            "friendly.name": "email",
+            "attribute.name": "EmailAddress"
+          }
+        }
+      ],
+      "defaultClientScopes": [
+        "web-origins",
+        "role_list",
+        "roles",
+        "profile",
+        "email"
+      ],
+      "optionalClientScopes": [
+        "address",
+        "phone",
+        "offline_access"
+      ]
+    },
+    {
+      "id": "61635f19-0db9-4553-a386-5ff668d4da74",
+      "clientId": "security-admin-console",
+      "name": "${client_security-admin-console}",
+      "baseUrl": "/auth/admin/master/console/index.html",
+      "surrogateAuthRequired": false,
+      "enabled": true,
+      "clientAuthenticatorType": "client-secret",
+      "secret": "**********",
+      "redirectUris": [
+        "/auth/admin/master/console/*"
+      ],
+      "webOrigins": [],
+      "notBefore": 0,
+      "bearerOnly": false,
+      "consentRequired": false,
+      "standardFlowEnabled": true,
+      "implicitFlowEnabled": false,
+      "directAccessGrantsEnabled": false,
+      "serviceAccountsEnabled": false,
+      "publicClient": true,
+      "frontchannelLogout": false,
+      "protocol": "openid-connect",
+      "attributes": {},
+      "authenticationFlowBindingOverrides": {},
+      "fullScopeAllowed": false,
+      "nodeReRegistrationTimeout": 0,
+      "protocolMappers": [
+        {
+          "id": "0abb343c-01b6-4736-84df-0b2a2de919e9",
+          "name": "locale",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-usermodel-attribute-mapper",
+          "consentRequired": false,
+          "config": {
+            "userinfo.token.claim": "true",
+            "user.attribute": "locale",
+            "id.token.claim": "true",
+            "access.token.claim": "true",
+            "claim.name": "locale",
+            "jsonType.label": "String"
+          }
+        }
+      ],
+      "defaultClientScopes": [
+        "web-origins",
+        "role_list",
+        "roles",
+        "profile",
+        "email"
+      ],
+      "optionalClientScopes": [
+        "address",
+        "phone",
+        "offline_access"
+      ]
+    },
+    {
+      "id": "a8d57542-328a-4c13-a4ae-ab3a60f1c503",
+      "clientId": "gerrit-oauth",
+      "name": "Gerrit OAuth2 Client",
+      "description": "Gerrit code review client applications",
+      "rootUrl": "http://localhost:8081",
+      "adminUrl": "http://localhost:8081",
+      "surrogateAuthRequired": false,
+      "enabled": true,
+      "clientAuthenticatorType": "client-secret",
+      "secret": "**********",
+      "redirectUris": [
+        "http://localhost:8081/*"
+      ],
+      "webOrigins": [
+        "http://localhost:8081"
+      ],
+      "notBefore": 0,
+      "bearerOnly": false,
+      "consentRequired": false,
+      "standardFlowEnabled": true,
+      "implicitFlowEnabled": false,
+      "directAccessGrantsEnabled": true,
+      "serviceAccountsEnabled": false,
+      "publicClient": false,
+      "frontchannelLogout": false,
+      "protocol": "openid-connect",
+      "attributes": {
+        "saml.assertion.signature": "false",
+        "saml.force.post.binding": "false",
+        "saml.multivalued.roles": "false",
+        "saml.encrypt": "false",
+        "login_theme": "keycloak",
+        "saml.server.signature": "false",
+        "saml.server.signature.keyinfo.ext": "false",
+        "exclude.session.state.from.auth.response": "false",
+        "saml_force_name_id_format": "false",
+        "saml.client.signature": "false",
+        "tls.client.certificate.bound.access.tokens": "false",
+        "saml.authnstatement": "false",
+        "display.on.consent.screen": "false",
+        "saml.onetimeuse.condition": "false"
+      },
+      "authenticationFlowBindingOverrides": {},
+      "fullScopeAllowed": true,
+      "nodeReRegistrationTimeout": -1,
+      "protocolMappers": [
+        {
+          "id": "f2f032f8-03f8-4597-8e84-370560f36686",
+          "name": "family name",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-usermodel-property-mapper",
+          "consentRequired": false,
+          "config": {
+            "userinfo.token.claim": "true",
+            "user.attribute": "lastName",
+            "id.token.claim": "true",
+            "access.token.claim": "true",
+            "claim.name": "family_name",
+            "jsonType.label": "String"
+          }
+        },
+        {
+          "id": "412be101-dfbd-4f77-aa54-6bcca51c1125",
+          "name": "full name",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-full-name-mapper",
+          "consentRequired": false,
+          "config": {
+            "id.token.claim": "true",
+            "access.token.claim": "true",
+            "userinfo.token.claim": "true"
+          }
+        },
+        {
+          "id": "082f8ac6-123c-4e47-9838-5b5dcb9b08ab",
+          "name": "username",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-usermodel-property-mapper",
+          "consentRequired": false,
+          "config": {
+            "userinfo.token.claim": "true",
+            "user.attribute": "username",
+            "id.token.claim": "true",
+            "access.token.claim": "true",
+            "claim.name": "preferred_username",
+            "jsonType.label": "String"
+          }
+        },
+        {
+          "id": "d31c17bc-71c2-47de-b013-fbb5c43c736e",
+          "name": "email",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-usermodel-property-mapper",
+          "consentRequired": false,
+          "config": {
+            "userinfo.token.claim": "true",
+            "user.attribute": "email",
+            "id.token.claim": "true",
+            "access.token.claim": "true",
+            "claim.name": "email",
+            "jsonType.label": "String"
+          }
+        },
+        {
+          "id": "f69ca206-9484-4179-8b51-f8f5f6ea5f56",
+          "name": "role list",
+          "protocol": "saml",
+          "protocolMapper": "saml-role-list-mapper",
+          "consentRequired": false,
+          "config": {
+            "single": "false",
+            "attribute.nameformat": "Basic",
+            "attribute.name": "Role"
+          }
+        },
+        {
+          "id": "0d4c84a3-66d0-4620-bd11-7ecfcad90577",
+          "name": "given name",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-usermodel-property-mapper",
+          "consentRequired": false,
+          "config": {
+            "userinfo.token.claim": "true",
+            "user.attribute": "firstName",
+            "id.token.claim": "true",
+            "access.token.claim": "true",
+            "claim.name": "given_name",
+            "jsonType.label": "String"
+          }
+        }
+      ],
+      "defaultClientScopes": [
+        "web-origins",
+        "role_list",
+        "roles",
+        "profile",
+        "email"
+      ],
+      "optionalClientScopes": [
+        "address",
+        "phone",
+        "offline_access"
+      ]
+    },
+    {
+      "id": "e255464f-5c00-4db4-be47-921fef057716",
+      "clientId": "junk",
+      "surrogateAuthRequired": false,
+      "enabled": true,
+      "clientAuthenticatorType": "client-secret",
+      "secret": "**********",
+      "redirectUris": [],
+      "webOrigins": [],
+      "notBefore": 0,
+      "bearerOnly": false,
+      "consentRequired": false,
+      "standardFlowEnabled": true,
+      "implicitFlowEnabled": false,
+      "directAccessGrantsEnabled": true,
+      "serviceAccountsEnabled": false,
+      "publicClient": true,
+      "frontchannelLogout": false,
+      "protocol": "openid-connect",
+      "attributes": {},
+      "authenticationFlowBindingOverrides": {},
+      "fullScopeAllowed": true,
+      "nodeReRegistrationTimeout": -1,
+      "protocolMappers": [
+        {
+          "id": "86f4b092-94f7-4b02-a47b-405699d8ab01",
+          "name": "full name",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-full-name-mapper",
+          "consentRequired": false,
+          "config": {
+            "id.token.claim": "true",
+            "access.token.claim": "true",
+            "userinfo.token.claim": "true"
+          }
+        }
+      ],
+      "defaultClientScopes": [
+        "web-origins",
+        "role_list",
+        "roles",
+        "profile",
+        "email"
+      ],
+      "optionalClientScopes": [
+        "address",
+        "phone",
+        "offline_access"
+      ]
+    },
+    {
+      "id": "00ccb200-7aff-4d0d-ae5f-9323453f99c7",
+      "clientId": "account",
+      "name": "${client_account}",
+      "baseUrl": "/auth/realms/master/account",
+      "surrogateAuthRequired": false,
+      "enabled": true,
+      "clientAuthenticatorType": "client-secret",
+      "secret": "**********",
+      "defaultRoles": [
+        "manage-account",
+        "view-profile"
+      ],
+      "redirectUris": [
+        "/auth/realms/master/account/*"
+      ],
+      "webOrigins": [],
+      "notBefore": 0,
+      "bearerOnly": false,
+      "consentRequired": false,
+      "standardFlowEnabled": true,
+      "implicitFlowEnabled": false,
+      "directAccessGrantsEnabled": false,
+      "serviceAccountsEnabled": false,
+      "publicClient": false,
+      "frontchannelLogout": false,
+      "protocol": "openid-connect",
+      "attributes": {},
+      "authenticationFlowBindingOverrides": {},
+      "fullScopeAllowed": false,
+      "nodeReRegistrationTimeout": 0,
+      "defaultClientScopes": [
+        "web-origins",
+        "role_list",
+        "roles",
+        "profile",
+        "email"
+      ],
+      "optionalClientScopes": [
+        "address",
+        "phone",
+        "offline_access"
+      ]
+    },
+    {
+      "id": "3ea8dde2-4f98-495a-9587-adb926ed34d7",
+      "clientId": "admin-cli",
+      "name": "${client_admin-cli}",
+      "surrogateAuthRequired": false,
+      "enabled": true,
+      "clientAuthenticatorType": "client-secret",
+      "secret": "**********",
+      "redirectUris": [],
+      "webOrigins": [],
+      "notBefore": 0,
+      "bearerOnly": false,
+      "consentRequired": false,
+      "standardFlowEnabled": false,
+      "implicitFlowEnabled": false,
+      "directAccessGrantsEnabled": true,
+      "serviceAccountsEnabled": false,
+      "publicClient": true,
+      "frontchannelLogout": false,
+      "protocol": "openid-connect",
+      "attributes": {},
+      "authenticationFlowBindingOverrides": {},
+      "fullScopeAllowed": false,
+      "nodeReRegistrationTimeout": 0,
+      "defaultClientScopes": [
+        "web-origins",
+        "role_list",
+        "roles",
+        "profile",
+        "email"
+      ],
+      "optionalClientScopes": [
+        "address",
+        "phone",
+        "offline_access"
+      ]
+    },
+    {
+      "id": "cf52d0fd-6efb-4899-9552-1a625d0b1990",
+      "clientId": "broker",
+      "name": "${client_broker}",
+      "surrogateAuthRequired": false,
+      "enabled": true,
+      "clientAuthenticatorType": "client-secret",
+      "secret": "**********",
+      "redirectUris": [],
+      "webOrigins": [],
+      "notBefore": 0,
+      "bearerOnly": false,
+      "consentRequired": false,
+      "standardFlowEnabled": true,
+      "implicitFlowEnabled": false,
+      "directAccessGrantsEnabled": false,
+      "serviceAccountsEnabled": false,
+      "publicClient": false,
+      "frontchannelLogout": false,
+      "protocol": "openid-connect",
+      "attributes": {},
+      "authenticationFlowBindingOverrides": {},
+      "fullScopeAllowed": false,
+      "nodeReRegistrationTimeout": 0,
+      "defaultClientScopes": [
+        "web-origins",
+        "role_list",
+        "roles",
+        "profile",
+        "email"
+      ],
+      "optionalClientScopes": [
+        "address",
+        "phone",
+        "offline_access"
+      ]
+    },
+    {
+      "id": "02ba9bac-1b26-4a8a-ba3b-9ff0b6bc19dd",
+      "clientId": "master-realm",
+      "name": "master Realm",
+      "surrogateAuthRequired": false,
+      "enabled": true,
+      "clientAuthenticatorType": "client-secret",
+      "secret": "**********",
+      "redirectUris": [],
+      "webOrigins": [],
+      "notBefore": 0,
+      "bearerOnly": true,
+      "consentRequired": false,
+      "standardFlowEnabled": true,
+      "implicitFlowEnabled": false,
+      "directAccessGrantsEnabled": false,
+      "serviceAccountsEnabled": false,
+      "publicClient": false,
+      "frontchannelLogout": false,
+      "attributes": {},
+      "authenticationFlowBindingOverrides": {},
+      "fullScopeAllowed": true,
+      "nodeReRegistrationTimeout": 0,
+      "defaultClientScopes": [
+        "web-origins",
+        "role_list",
+        "roles",
+        "profile",
+        "email"
+      ],
+      "optionalClientScopes": [
+        "address",
+        "phone",
+        "offline_access"
+      ]
+    }
+  ],
+  "clientScopes": [
+    {
+      "id": "1775cde0-555d-4ecb-aeeb-0af23c925352",
+      "name": "role_list",
+      "description": "SAML role list",
+      "protocol": "saml",
+      "attributes": {
+        "consent.screen.text": "${samlRoleListScopeConsentText}",
+        "display.on.consent.screen": "true"
+      },
+      "protocolMappers": [
+        {
+          "id": "7025caf9-5a67-450f-89e1-a7cd9cd08fb8",
+          "name": "role list",
+          "protocol": "saml",
+          "protocolMapper": "saml-role-list-mapper",
+          "consentRequired": false,
+          "config": {
+            "single": "false",
+            "attribute.nameformat": "Basic",
+            "attribute.name": "Role"
+          }
+        }
+      ]
+    },
+    {
+      "id": "23e21e4a-067a-42e8-9937-48d502fd9012",
+      "name": "email",
+      "description": "OpenID Connect built-in scope: email",
+      "protocol": "openid-connect",
+      "attributes": {
+        "include.in.token.scope": "true",
+        "display.on.consent.screen": "true",
+        "consent.screen.text": "${emailScopeConsentText}"
+      },
+      "protocolMappers": [
+        {
+          "id": "4d088fd7-6e36-4bbe-b1c3-e7a9424dc7a9",
+          "name": "email verified",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-usermodel-property-mapper",
+          "consentRequired": false,
+          "config": {
+            "userinfo.token.claim": "true",
+            "user.attribute": "emailVerified",
+            "id.token.claim": "true",
+            "access.token.claim": "true",
+            "claim.name": "email_verified",
+            "jsonType.label": "boolean"
+          }
+        },
+        {
+          "id": "67c834c0-835c-47bd-bc0a-7b12e8a83e0d",
+          "name": "email",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-usermodel-property-mapper",
+          "consentRequired": false,
+          "config": {
+            "userinfo.token.claim": "true",
+            "user.attribute": "email",
+            "id.token.claim": "true",
+            "access.token.claim": "true",
+            "claim.name": "email",
+            "jsonType.label": "String"
+          }
+        }
+      ]
+    },
+    {
+      "id": "34dcdfaa-1bfb-4051-b7ee-b80e51f83879",
+      "name": "address",
+      "description": "OpenID Connect built-in scope: address",
+      "protocol": "openid-connect",
+      "attributes": {
+        "include.in.token.scope": "true",
+        "display.on.consent.screen": "true",
+        "consent.screen.text": "${addressScopeConsentText}"
+      },
+      "protocolMappers": [
+        {
+          "id": "77725e59-1d89-44cf-9297-e90de6027803",
+          "name": "address",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-address-mapper",
+          "consentRequired": false,
+          "config": {
+            "user.attribute.formatted": "formatted",
+            "user.attribute.country": "country",
+            "user.attribute.postal_code": "postal_code",
+            "userinfo.token.claim": "true",
+            "user.attribute.street": "street",
+            "id.token.claim": "true",
+            "user.attribute.region": "region",
+            "access.token.claim": "true",
+            "user.attribute.locality": "locality"
+          }
+        }
+      ]
+    },
+    {
+      "id": "4fdfab14-497a-41d4-a3b7-23aad8add16a",
+      "name": "offline_access",
+      "description": "OpenID Connect built-in scope: offline_access",
+      "protocol": "openid-connect",
+      "attributes": {
+        "consent.screen.text": "${offlineAccessScopeConsentText}",
+        "display.on.consent.screen": "true"
+      }
+    },
+    {
+      "id": "71d9deb8-a7b0-4ec7-bb3f-051f7ee107c6",
+      "name": "roles",
+      "description": "OpenID Connect scope for add user roles to the access token",
+      "protocol": "openid-connect",
+      "attributes": {
+        "include.in.token.scope": "false",
+        "display.on.consent.screen": "true",
+        "consent.screen.text": "${rolesScopeConsentText}"
+      },
+      "protocolMappers": [
+        {
+          "id": "4336df7c-c071-4700-9aec-5b63c03cb7a4",
+          "name": "realm roles",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-usermodel-realm-role-mapper",
+          "consentRequired": false,
+          "config": {
+            "user.attribute": "foo",
+            "access.token.claim": "true",
+            "claim.name": "realm_access.roles",
+            "jsonType.label": "String",
+            "multivalued": "true"
+          }
+        },
+        {
+          "id": "9bb08455-4dd0-4b2d-a764-889bb6b90929",
+          "name": "client roles",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-usermodel-client-role-mapper",
+          "consentRequired": false,
+          "config": {
+            "user.attribute": "foo",
+            "access.token.claim": "true",
+            "claim.name": "resource_access.${client_id}.roles",
+            "jsonType.label": "String",
+            "multivalued": "true"
+          }
+        },
+        {
+          "id": "45f2232a-17aa-4feb-ac51-f7901ea83e33",
+          "name": "audience resolve",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-audience-resolve-mapper",
+          "consentRequired": false,
+          "config": {}
+        }
+      ]
+    },
+    {
+      "id": "96a5bbb9-2e17-4e24-a625-8629662d5aba",
+      "name": "phone",
+      "description": "OpenID Connect built-in scope: phone",
+      "protocol": "openid-connect",
+      "attributes": {
+        "include.in.token.scope": "true",
+        "display.on.consent.screen": "true",
+        "consent.screen.text": "${phoneScopeConsentText}"
+      },
+      "protocolMappers": [
+        {
+          "id": "853ba347-192a-4ece-ad5d-ca93b0e01701",
+          "name": "phone number",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-usermodel-attribute-mapper",
+          "consentRequired": false,
+          "config": {
+            "userinfo.token.claim": "true",
+            "user.attribute": "phoneNumber",
+            "id.token.claim": "true",
+            "access.token.claim": "true",
+            "claim.name": "phone_number",
+            "jsonType.label": "String"
+          }
+        },
+        {
+          "id": "b2705f58-a4c2-4eb4-9227-6b0154e49388",
+          "name": "phone number verified",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-usermodel-attribute-mapper",
+          "consentRequired": false,
+          "config": {
+            "userinfo.token.claim": "true",
+            "user.attribute": "phoneNumberVerified",
+            "id.token.claim": "true",
+            "access.token.claim": "true",
+            "claim.name": "phone_number_verified",
+            "jsonType.label": "boolean"
+          }
+        }
+      ]
+    },
+    {
+      "id": "b04c6ff4-37b8-43dd-a5c9-f10bdc6c1aa0",
+      "name": "web-origins",
+      "description": "OpenID Connect scope for add allowed web origins to the access token",
+      "protocol": "openid-connect",
+      "attributes": {
+        "include.in.token.scope": "false",
+        "display.on.consent.screen": "false",
+        "consent.screen.text": ""
+      },
+      "protocolMappers": [
+        {
+          "id": "6e7bcd9c-6e8e-4454-ac6a-eb3de841b1c9",
+          "name": "allowed web origins",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-allowed-origins-mapper",
+          "consentRequired": false,
+          "config": {}
+        }
+      ]
+    },
+    {
+      "id": "b55212bf-3a39-450a-98da-d5e44be43536",
+      "name": "profile",
+      "description": "OpenID Connect built-in scope: profile",
+      "protocol": "openid-connect",
+      "attributes": {
+        "include.in.token.scope": "true",
+        "display.on.consent.screen": "true",
+        "consent.screen.text": "${profileScopeConsentText}"
+      },
+      "protocolMappers": [
+        {
+          "id": "f0818a18-e35d-4298-a8a0-a2b6aa28488e",
+          "name": "website",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-usermodel-attribute-mapper",
+          "consentRequired": false,
+          "config": {
+            "userinfo.token.claim": "true",
+            "user.attribute": "website",
+            "id.token.claim": "true",
+            "access.token.claim": "true",
+            "claim.name": "website",
+            "jsonType.label": "String"
+          }
+        },
+        {
+          "id": "9834dcd1-223b-43f1-a6b9-b92ad07bf255",
+          "name": "picture",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-usermodel-attribute-mapper",
+          "consentRequired": false,
+          "config": {
+            "userinfo.token.claim": "true",
+            "user.attribute": "picture",
+            "id.token.claim": "true",
+            "access.token.claim": "true",
+            "claim.name": "picture",
+            "jsonType.label": "String"
+          }
+        },
+        {
+          "id": "d5127eb9-14db-48a0-b53b-e2b8c9ae08c3",
+          "name": "birthdate",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-usermodel-attribute-mapper",
+          "consentRequired": false,
+          "config": {
+            "userinfo.token.claim": "true",
+            "user.attribute": "birthdate",
+            "id.token.claim": "true",
+            "access.token.claim": "true",
+            "claim.name": "birthdate",
+            "jsonType.label": "String"
+          }
+        },
+        {
+          "id": "a322d700-98c0-470a-b9dd-58b785f2df46",
+          "name": "locale",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-usermodel-attribute-mapper",
+          "consentRequired": false,
+          "config": {
+            "userinfo.token.claim": "true",
+            "user.attribute": "locale",
+            "id.token.claim": "true",
+            "access.token.claim": "true",
+            "claim.name": "locale",
+            "jsonType.label": "String"
+          }
+        },
+        {
+          "id": "f6835607-6c5a-4bc0-b2f6-f5df5fac837e",
+          "name": "gender",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-usermodel-attribute-mapper",
+          "consentRequired": false,
+          "config": {
+            "userinfo.token.claim": "true",
+            "user.attribute": "gender",
+            "id.token.claim": "true",
+            "access.token.claim": "true",
+            "claim.name": "gender",
+            "jsonType.label": "String"
+          }
+        },
+        {
+          "id": "4924843e-fef9-4a14-a0c0-dce2dcaf054a",
+          "name": "family name",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-usermodel-property-mapper",
+          "consentRequired": false,
+          "config": {
+            "userinfo.token.claim": "true",
+            "user.attribute": "lastName",
+            "id.token.claim": "true",
+            "access.token.claim": "true",
+            "claim.name": "family_name",
+            "jsonType.label": "String"
+          }
+        },
+        {
+          "id": "acfa2cb8-aacb-435f-b7c8-45a17e66a796",
+          "name": "updated at",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-usermodel-attribute-mapper",
+          "consentRequired": false,
+          "config": {
+            "userinfo.token.claim": "true",
+            "user.attribute": "updatedAt",
+            "id.token.claim": "true",
+            "access.token.claim": "true",
+            "claim.name": "updated_at",
+            "jsonType.label": "String"
+          }
+        },
+        {
+          "id": "00c5a3a2-50ea-4dfb-9cec-f2afa2f2e555",
+          "name": "profile",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-usermodel-attribute-mapper",
+          "consentRequired": false,
+          "config": {
+            "userinfo.token.claim": "true",
+            "user.attribute": "profile",
+            "id.token.claim": "true",
+            "access.token.claim": "true",
+            "claim.name": "profile",
+            "jsonType.label": "String"
+          }
+        },
+        {
+          "id": "51c4633d-8c5f-40e9-94dc-df5104189fa5",
+          "name": "middle name",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-usermodel-attribute-mapper",
+          "consentRequired": false,
+          "config": {
+            "userinfo.token.claim": "true",
+            "user.attribute": "middleName",
+            "id.token.claim": "true",
+            "access.token.claim": "true",
+            "claim.name": "middle_name",
+            "jsonType.label": "String"
+          }
+        },
+        {
+          "id": "a23a3da8-75f3-4e39-b873-7f07139357ff",
+          "name": "nickname",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-usermodel-attribute-mapper",
+          "consentRequired": false,
+          "config": {
+            "userinfo.token.claim": "true",
+            "user.attribute": "nickname",
+            "id.token.claim": "true",
+            "access.token.claim": "true",
+            "claim.name": "nickname",
+            "jsonType.label": "String"
+          }
+        },
+        {
+          "id": "dead5484-aeaa-4ac4-9fb3-799bc760748f",
+          "name": "given name",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-usermodel-property-mapper",
+          "consentRequired": false,
+          "config": {
+            "userinfo.token.claim": "true",
+            "user.attribute": "firstName",
+            "id.token.claim": "true",
+            "access.token.claim": "true",
+            "claim.name": "given_name",
+            "jsonType.label": "String"
+          }
+        },
+        {
+          "id": "e7acd2fa-9f3b-467c-9449-259f2d3f95d7",
+          "name": "zoneinfo",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-usermodel-attribute-mapper",
+          "consentRequired": false,
+          "config": {
+            "userinfo.token.claim": "true",
+            "user.attribute": "zoneinfo",
+            "id.token.claim": "true",
+            "access.token.claim": "true",
+            "claim.name": "zoneinfo",
+            "jsonType.label": "String"
+          }
+        },
+        {
+          "id": "bf0264da-ca81-48f9-9f8e-a6c49852aeb6",
+          "name": "full name",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-full-name-mapper",
+          "consentRequired": false,
+          "config": {
+            "id.token.claim": "true",
+            "access.token.claim": "true",
+            "userinfo.token.claim": "true"
+          }
+        },
+        {
+          "id": "468ff5d7-7706-4e23-b5b7-1291473666d0",
+          "name": "username",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-usermodel-property-mapper",
+          "consentRequired": false,
+          "config": {
+            "userinfo.token.claim": "true",
+            "user.attribute": "username",
+            "id.token.claim": "true",
+            "access.token.claim": "true",
+            "claim.name": "preferred_username",
+            "jsonType.label": "String"
+          }
+        }
+      ]
+    }
+  ],
+  "defaultDefaultClientScopes": [
+    "role_list",
+    "email",
+    "roles",
+    "web-origins",
+    "profile"
+  ],
+  "defaultOptionalClientScopes": [
+    "address",
+    "offline_access",
+    "phone"
+  ],
+  "browserSecurityHeaders": {
+    "contentSecurityPolicyReportOnly": "",
+    "xContentTypeOptions": "nosniff",
+    "xRobotsTag": "none",
+    "xFrameOptions": "SAMEORIGIN",
+    "xXSSProtection": "1; mode=block",
+    "contentSecurityPolicy": "frame-src 'self'; frame-ancestors 'self'; object-src 'none';",
+    "strictTransportSecurity": "max-age=31536000; includeSubDomains"
+  },
+  "smtpServer": {},
+  "eventsEnabled": false,
+  "eventsListeners": [
+    "jboss-logging"
+  ],
+  "enabledEventTypes": [],
+  "adminEventsEnabled": false,
+  "adminEventsDetailsEnabled": false,
+  "components": {
+    "org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy": [
+      {
+        "id": "0df6509e-2901-4f55-b0e7-b973a2d6da64",
+        "name": "Full Scope Disabled",
+        "providerId": "scope",
+        "subType": "anonymous",
+        "subComponents": {},
+        "config": {}
+      },
+      {
+        "id": "064f52ed-0285-41fb-b4b7-8f3dfd40f798",
+        "name": "Allowed Protocol Mapper Types",
+        "providerId": "allowed-protocol-mappers",
+        "subType": "anonymous",
+        "subComponents": {},
+        "config": {
+          "allowed-protocol-mapper-types": [
+            "saml-role-list-mapper",
+            "oidc-address-mapper",
+            "oidc-full-name-mapper",
+            "saml-user-property-mapper",
+            "oidc-usermodel-property-mapper",
+            "saml-user-attribute-mapper",
+            "oidc-sha256-pairwise-sub-mapper",
+            "oidc-usermodel-attribute-mapper"
+          ]
+        }
+      },
+      {
+        "id": "4c351585-dc77-4c11-9520-2e3ae3c2b256",
+        "name": "Max Clients Limit",
+        "providerId": "max-clients",
+        "subType": "anonymous",
+        "subComponents": {},
+        "config": {
+          "max-clients": [
+            "200"
+          ]
+        }
+      },
+      {
+        "id": "e0de7b20-41af-4f25-a954-abf124c4d895",
+        "name": "Allowed Protocol Mapper Types",
+        "providerId": "allowed-protocol-mappers",
+        "subType": "authenticated",
+        "subComponents": {},
+        "config": {
+          "allowed-protocol-mapper-types": [
+            "oidc-address-mapper",
+            "oidc-usermodel-property-mapper",
+            "oidc-sha256-pairwise-sub-mapper",
+            "saml-user-property-mapper",
+            "saml-role-list-mapper",
+            "saml-user-attribute-mapper",
+            "oidc-usermodel-attribute-mapper",
+            "oidc-full-name-mapper"
+          ]
+        }
+      },
+      {
+        "id": "ce3c7aa2-b373-4fa7-84b6-9b618615837b",
+        "name": "Trusted Hosts",
+        "providerId": "trusted-hosts",
+        "subType": "anonymous",
+        "subComponents": {},
+        "config": {
+          "host-sending-registration-request-must-match": [
+            "true"
+          ],
+          "client-uris-must-match": [
+            "true"
+          ]
+        }
+      },
+      {
+        "id": "4cd26343-0905-4771-8b07-cf796d967c34",
+        "name": "Allowed Client Scopes",
+        "providerId": "allowed-client-templates",
+        "subType": "anonymous",
+        "subComponents": {},
+        "config": {
+          "allow-default-scopes": [
+            "true"
+          ]
+        }
+      },
+      {
+        "id": "273dc8c5-031d-46fc-92eb-7530afd4ba88",
+        "name": "Consent Required",
+        "providerId": "consent-required",
+        "subType": "anonymous",
+        "subComponents": {},
+        "config": {}
+      },
+      {
+        "id": "99d0fa48-4f67-4b84-a067-219223523697",
+        "name": "Allowed Client Scopes",
+        "providerId": "allowed-client-templates",
+        "subType": "authenticated",
+        "subComponents": {},
+        "config": {
+          "allow-default-scopes": [
+            "true"
+          ]
+        }
+      }
+    ],
+    "org.keycloak.keys.KeyProvider": [
+      {
+        "id": "dfa01a4f-bc52-4373-a9bc-a3b42bb1e00c",
+        "name": "aes-generated",
+        "providerId": "aes-generated",
+        "subComponents": {},
+        "config": {
+          "priority": [
+            "100"
+          ]
+        }
+      },
+      {
+        "id": "930505a0-0ecf-49af-8b25-282d572d9b3a",
+        "name": "rsa-generated",
+        "providerId": "rsa-generated",
+        "subComponents": {},
+        "config": {
+          "priority": [
+            "100"
+          ]
+        }
+      },
+      {
+        "id": "1c7e1d88-541e-403b-9170-2f15a47a1a75",
+        "name": "hmac-generated",
+        "providerId": "hmac-generated",
+        "subComponents": {},
+        "config": {
+          "priority": [
+            "100"
+          ],
+          "algorithm": [
+            "HS256"
+          ]
+        }
+      }
+    ]
+  },
+  "internationalizationEnabled": false,
+  "supportedLocales": [],
+  "authenticationFlows": [
+    {
+      "id": "425f6f8d-8aec-49ef-9ba0-31261f5ae5ca",
+      "alias": "Handle Existing Account",
+      "description": "Handle what to do if there is existing account with same email/username like authenticated identity provider",
+      "providerId": "basic-flow",
+      "topLevel": false,
+      "builtIn": true,
+      "authenticationExecutions": [
+        {
+          "authenticator": "idp-confirm-link",
+          "requirement": "REQUIRED",
+          "priority": 10,
+          "userSetupAllowed": false,
+          "autheticatorFlow": false
+        },
+        {
+          "authenticator": "idp-email-verification",
+          "requirement": "ALTERNATIVE",
+          "priority": 20,
+          "userSetupAllowed": false,
+          "autheticatorFlow": false
+        },
+        {
+          "requirement": "ALTERNATIVE",
+          "priority": 30,
+          "flowAlias": "Verify Existing Account by Re-authentication",
+          "userSetupAllowed": false,
+          "autheticatorFlow": true
+        }
+      ]
+    },
+    {
+      "id": "e60dd309-3ebd-45a7-bcc9-cc84762c74a7",
+      "alias": "Verify Existing Account by Re-authentication",
+      "description": "Reauthentication of existing account",
+      "providerId": "basic-flow",
+      "topLevel": false,
+      "builtIn": true,
+      "authenticationExecutions": [
+        {
+          "authenticator": "idp-username-password-form",
+          "requirement": "REQUIRED",
+          "priority": 10,
+          "userSetupAllowed": false,
+          "autheticatorFlow": false
+        },
+        {
+          "authenticator": "auth-otp-form",
+          "requirement": "OPTIONAL",
+          "priority": 20,
+          "userSetupAllowed": false,
+          "autheticatorFlow": false
+        }
+      ]
+    },
+    {
+      "id": "032f0dbc-ccd1-4a6c-8450-c9f228ad2c85",
+      "alias": "browser",
+      "description": "browser based authentication",
+      "providerId": "basic-flow",
+      "topLevel": true,
+      "builtIn": true,
+      "authenticationExecutions": [
+        {
+          "authenticator": "auth-cookie",
+          "requirement": "ALTERNATIVE",
+          "priority": 10,
+          "userSetupAllowed": false,
+          "autheticatorFlow": false
+        },
+        {
+          "authenticator": "auth-spnego",
+          "requirement": "DISABLED",
+          "priority": 20,
+          "userSetupAllowed": false,
+          "autheticatorFlow": false
+        },
+        {
+          "authenticator": "identity-provider-redirector",
+          "requirement": "ALTERNATIVE",
+          "priority": 25,
+          "userSetupAllowed": false,
+          "autheticatorFlow": false
+        },
+        {
+          "requirement": "ALTERNATIVE",
+          "priority": 30,
+          "flowAlias": "forms",
+          "userSetupAllowed": false,
+          "autheticatorFlow": true
+        }
+      ]
+    },
+    {
+      "id": "cab51fee-abdf-4140-aecb-551809650d79",
+      "alias": "clients",
+      "description": "Base authentication for clients",
+      "providerId": "client-flow",
+      "topLevel": true,
+      "builtIn": true,
+      "authenticationExecutions": [
+        {
+          "authenticator": "client-secret",
+          "requirement": "ALTERNATIVE",
+          "priority": 10,
+          "userSetupAllowed": false,
+          "autheticatorFlow": false
+        },
+        {
+          "authenticator": "client-jwt",
+          "requirement": "ALTERNATIVE",
+          "priority": 20,
+          "userSetupAllowed": false,
+          "autheticatorFlow": false
+        },
+        {
+          "authenticator": "client-secret-jwt",
+          "requirement": "ALTERNATIVE",
+          "priority": 30,
+          "userSetupAllowed": false,
+          "autheticatorFlow": false
+        },
+        {
+          "authenticator": "client-x509",
+          "requirement": "ALTERNATIVE",
+          "priority": 40,
+          "userSetupAllowed": false,
+          "autheticatorFlow": false
+        }
+      ]
+    },
+    {
+      "id": "526d355a-24bd-4efb-9ce3-571f76f98ceb",
+      "alias": "direct grant",
+      "description": "OpenID Connect Resource Owner Grant",
+      "providerId": "basic-flow",
+      "topLevel": true,
+      "builtIn": true,
+      "authenticationExecutions": [
+        {
+          "authenticator": "direct-grant-validate-username",
+          "requirement": "REQUIRED",
+          "priority": 10,
+          "userSetupAllowed": false,
+          "autheticatorFlow": false
+        },
+        {
+          "authenticator": "direct-grant-validate-password",
+          "requirement": "REQUIRED",
+          "priority": 20,
+          "userSetupAllowed": false,
+          "autheticatorFlow": false
+        },
+        {
+          "authenticator": "direct-grant-validate-otp",
+          "requirement": "OPTIONAL",
+          "priority": 30,
+          "userSetupAllowed": false,
+          "autheticatorFlow": false
+        }
+      ]
+    },
+    {
+      "id": "ced266f5-cd87-45c5-9d49-0ca5bc7fc6f0",
+      "alias": "docker auth",
+      "description": "Used by Docker clients to authenticate against the IDP",
+      "providerId": "basic-flow",
+      "topLevel": true,
+      "builtIn": true,
+      "authenticationExecutions": [
+        {
+          "authenticator": "docker-http-basic-authenticator",
+          "requirement": "REQUIRED",
+          "priority": 10,
+          "userSetupAllowed": false,
+          "autheticatorFlow": false
+        }
+      ]
+    },
+    {
+      "id": "bcafdcc7-b574-472c-92fa-0805fea218af",
+      "alias": "first broker login",
+      "description": "Actions taken after first broker login with identity provider account, which is not yet linked to any Keycloak account",
+      "providerId": "basic-flow",
+      "topLevel": true,
+      "builtIn": true,
+      "authenticationExecutions": [
+        {
+          "authenticatorConfig": "review profile config",
+          "authenticator": "idp-review-profile",
+          "requirement": "REQUIRED",
+          "priority": 10,
+          "userSetupAllowed": false,
+          "autheticatorFlow": false
+        },
+        {
+          "authenticatorConfig": "create unique user config",
+          "authenticator": "idp-create-user-if-unique",
+          "requirement": "ALTERNATIVE",
+          "priority": 20,
+          "userSetupAllowed": false,
+          "autheticatorFlow": false
+        },
+        {
+          "requirement": "ALTERNATIVE",
+          "priority": 30,
+          "flowAlias": "Handle Existing Account",
+          "userSetupAllowed": false,
+          "autheticatorFlow": true
+        }
+      ]
+    },
+    {
+      "id": "5ad9445d-718a-4488-9efa-f1d2dccee114",
+      "alias": "forms",
+      "description": "Username, password, otp and other auth forms.",
+      "providerId": "basic-flow",
+      "topLevel": false,
+      "builtIn": true,
+      "authenticationExecutions": [
+        {
+          "authenticator": "auth-username-password-form",
+          "requirement": "REQUIRED",
+          "priority": 10,
+          "userSetupAllowed": false,
+          "autheticatorFlow": false
+        },
+        {
+          "authenticator": "auth-otp-form",
+          "requirement": "OPTIONAL",
+          "priority": 20,
+          "userSetupAllowed": false,
+          "autheticatorFlow": false
+        }
+      ]
+    },
+    {
+      "id": "ef1a1c69-426b-48e5-88df-ce1248357f13",
+      "alias": "http challenge",
+      "description": "An authentication flow based on challenge-response HTTP Authentication Schemes",
+      "providerId": "basic-flow",
+      "topLevel": true,
+      "builtIn": true,
+      "authenticationExecutions": [
+        {
+          "authenticator": "no-cookie-redirect",
+          "requirement": "REQUIRED",
+          "priority": 10,
+          "userSetupAllowed": false,
+          "autheticatorFlow": false
+        },
+        {
+          "authenticator": "basic-auth",
+          "requirement": "REQUIRED",
+          "priority": 20,
+          "userSetupAllowed": false,
+          "autheticatorFlow": false
+        },
+        {
+          "authenticator": "basic-auth-otp",
+          "requirement": "DISABLED",
+          "priority": 30,
+          "userSetupAllowed": false,
+          "autheticatorFlow": false
+        },
+        {
+          "authenticator": "auth-spnego",
+          "requirement": "DISABLED",
+          "priority": 40,
+          "userSetupAllowed": false,
+          "autheticatorFlow": false
+        }
+      ]
+    },
+    {
+      "id": "c5eef7d9-3262-4f44-a14b-e1292d05f4df",
+      "alias": "registration",
+      "description": "registration flow",
+      "providerId": "basic-flow",
+      "topLevel": true,
+      "builtIn": true,
+      "authenticationExecutions": [
+        {
+          "authenticator": "registration-page-form",
+          "requirement": "REQUIRED",
+          "priority": 10,
+          "flowAlias": "registration form",
+          "userSetupAllowed": false,
+          "autheticatorFlow": true
+        }
+      ]
+    },
+    {
+      "id": "89aa8bf0-d261-4287-b137-9d75dbf819d6",
+      "alias": "registration form",
+      "description": "registration form",
+      "providerId": "form-flow",
+      "topLevel": false,
+      "builtIn": true,
+      "authenticationExecutions": [
+        {
+          "authenticator": "registration-user-creation",
+          "requirement": "REQUIRED",
+          "priority": 20,
+          "userSetupAllowed": false,
+          "autheticatorFlow": false
+        },
+        {
+          "authenticator": "registration-profile-action",
+          "requirement": "REQUIRED",
+          "priority": 40,
+          "userSetupAllowed": false,
+          "autheticatorFlow": false
+        },
+        {
+          "authenticator": "registration-password-action",
+          "requirement": "REQUIRED",
+          "priority": 50,
+          "userSetupAllowed": false,
+          "autheticatorFlow": false
+        },
+        {
+          "authenticator": "registration-recaptcha-action",
+          "requirement": "DISABLED",
+          "priority": 60,
+          "userSetupAllowed": false,
+          "autheticatorFlow": false
+        }
+      ]
+    },
+    {
+      "id": "a66eb1c7-37bf-48c3-9220-291efe8846c5",
+      "alias": "reset credentials",
+      "description": "Reset credentials for a user if they forgot their password or something",
+      "providerId": "basic-flow",
+      "topLevel": true,
+      "builtIn": true,
+      "authenticationExecutions": [
+        {
+          "authenticator": "reset-credentials-choose-user",
+          "requirement": "REQUIRED",
+          "priority": 10,
+          "userSetupAllowed": false,
+          "autheticatorFlow": false
+        },
+        {
+          "authenticator": "reset-credential-email",
+          "requirement": "REQUIRED",
+          "priority": 20,
+          "userSetupAllowed": false,
+          "autheticatorFlow": false
+        },
+        {
+          "authenticator": "reset-password",
+          "requirement": "REQUIRED",
+          "priority": 30,
+          "userSetupAllowed": false,
+          "autheticatorFlow": false
+        },
+        {
+          "authenticator": "reset-otp",
+          "requirement": "OPTIONAL",
+          "priority": 40,
+          "userSetupAllowed": false,
+          "autheticatorFlow": false
+        }
+      ]
+    },
+    {
+      "id": "fde82557-2e53-4405-bdd9-d1177df51af9",
+      "alias": "saml ecp",
+      "description": "SAML ECP Profile Authentication Flow",
+      "providerId": "basic-flow",
+      "topLevel": true,
+      "builtIn": true,
+      "authenticationExecutions": [
+        {
+          "authenticator": "http-basic-authenticator",
+          "requirement": "REQUIRED",
+          "priority": 10,
+          "userSetupAllowed": false,
+          "autheticatorFlow": false
+        }
+      ]
+    }
+  ],
+  "authenticatorConfig": [
+    {
+      "id": "25ffee1d-c9df-438b-a378-b86138545aba",
+      "alias": "create unique user config",
+      "config": {
+        "require.password.update.after.registration": "false"
+      }
+    },
+    {
+      "id": "0097c3d6-ab28-42b2-a3ce-967cf0d43d6b",
+      "alias": "review profile config",
+      "config": {
+        "update.profile.on.first.login": "missing"
+      }
+    }
+  ],
+  "requiredActions": [
+    {
+      "alias": "CONFIGURE_TOTP",
+      "name": "Configure OTP",
+      "providerId": "CONFIGURE_TOTP",
+      "enabled": true,
+      "defaultAction": false,
+      "priority": 10,
+      "config": {}
+    },
+    {
+      "alias": "terms_and_conditions",
+      "name": "Terms and Conditions",
+      "providerId": "terms_and_conditions",
+      "enabled": false,
+      "defaultAction": false,
+      "priority": 20,
+      "config": {}
+    },
+    {
+      "alias": "UPDATE_PASSWORD",
+      "name": "Update Password",
+      "providerId": "UPDATE_PASSWORD",
+      "enabled": true,
+      "defaultAction": false,
+      "priority": 30,
+      "config": {}
+    },
+    {
+      "alias": "UPDATE_PROFILE",
+      "name": "Update Profile",
+      "providerId": "UPDATE_PROFILE",
+      "enabled": true,
+      "defaultAction": false,
+      "priority": 40,
+      "config": {}
+    },
+    {
+      "alias": "VERIFY_EMAIL",
+      "name": "Verify Email",
+      "providerId": "VERIFY_EMAIL",
+      "enabled": true,
+      "defaultAction": false,
+      "priority": 50,
+      "config": {}
+    }
+  ],
+  "browserFlow": "browser",
+  "registrationFlow": "registration",
+  "directGrantFlow": "direct grant",
+  "resetCredentialsFlow": "reset credentials",
+  "clientAuthenticationFlow": "clients",
+  "dockerAuthenticationFlow": "docker auth",
+  "attributes": {
+    "_browser_header.xXSSProtection": "1; mode=block",
+    "_browser_header.strictTransportSecurity": "max-age=31536000; includeSubDomains",
+    "_browser_header.xFrameOptions": "SAMEORIGIN",
+    "permanentLockout": "false",
+    "quickLoginCheckMilliSeconds": "1000",
+    "displayName": "Keycloak",
+    "_browser_header.xRobotsTag": "none",
+    "maxFailureWaitSeconds": "900",
+    "displayNameHtml": "<div class=\"kc-logo-text\"><span>Keycloak</span></div>",
+    "minimumQuickLoginWaitSeconds": "60",
+    "failureFactor": "30",
+    "maxDeltaTimeSeconds": "43200",
+    "_browser_header.xContentTypeOptions": "nosniff",
+    "offlineSessionMaxLifespan": "5184000",
+    "_browser_header.contentSecurityPolicyReportOnly": "",
+    "bruteForceProtected": "false",
+    "_browser_header.contentSecurityPolicy": "frame-src 'self'; frame-ancestors 'self'; object-src 'none';",
+    "offlineSessionMaxLifespanEnabled": "false",
+    "waitIncrementSeconds": "60"
+  },
+  "keycloakVersion": "4.8.3.Final",
+  "userManagedAccessAllowed": false
+}
\ No newline at end of file
diff --git a/okta/README.md b/okta/README.md
new file mode 100644
index 0000000..4d67ba9
--- /dev/null
+++ b/okta/README.md
@@ -0,0 +1,20 @@
+# Okta as Gerrit SAML authentication provider
+
+- Create a new SAML 2.0 application.
+- Set the following parameters:
+  - Single sign on URL: http://gerrit.site.com/plugins/saml/callback
+  - Check "Use this for Recipient URL and Destination URL".
+  - Audience URI (SP Entity Id): http://gerrit.site.com/plugins/saml/callback
+  - We need to set up the attributes in the assertion to send the right
+    information. Here is how to do it with Okta:
+    - Application username: "Okta username prefix"
+    - Add attribute statement: Name: "DisplayName" with Value
+      "user.displayName"
+    - Add attribute statement: Name: "EmailAddress" with Value
+      "user.email"
+    - **IMPORTANT**: If you are not using Okta, you need to set up an attribute
+      "UserName" with the value of the username (not email, without @). If you
+      do not do so, the name will be taken from the NameId provided by
+      the assertion.  This is why in Okta we set the application username to
+      "Okta username prefix".
+- Obtain your IdP metadata (either URL or a local XML file)
\ No newline at end of file
diff --git a/src/main/java/com/googlesource/gerrit/plugins/saml/SamlWebFilter.java b/src/main/java/com/googlesource/gerrit/plugins/saml/SamlWebFilter.java
index 5f0508e..f71398a 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/saml/SamlWebFilter.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/saml/SamlWebFilter.java
@@ -313,7 +313,7 @@
       while (wrappedHeaderNames.hasMoreElements()) {
         String header = wrappedHeaderNames.nextElement();
         if (!authHeaders.contains(header.toUpperCase())) {
-          headerNames.add(wrappedHeaderNames.nextElement());
+          headerNames.add(header);
         }
       }
       return Iterators.asEnumeration(headerNames.iterator());
diff --git a/src/main/resources/Documentation/build.md b/src/main/resources/Documentation/build.md
deleted file mode 100644
index ea50282..0000000
--- a/src/main/resources/Documentation/build.md
+++ /dev/null
@@ -1,45 +0,0 @@
-Build
-=====
-
-This plugin is built with Bazel.
-
-## Build in Gerrit tree
-
-Clone or link this plugin to the plugins directory of Gerrit's
-source tree. Put the external dependency Bazel build file into
-the Gerrit /plugins directory, replacing the existing empty one.
-
-```
-  cd gerrit/plugins
-  rm external_plugin_deps.bzl
-  ln -s @PLUGIN@/external_plugin_deps.bzl .
-```
-
-Then issue
-
-```
-  bazel build plugins/@PLUGIN@
-```
-
-The output is created in
-
-```
-  bazel-genfiles/plugins/@PLUGIN@/@PLUGIN@.jar
-```
-
-The @PLUGIN@.jar should be deployed to `gerrit_site/lib` directory:
-
-```
- cp bazel-genfiles/plugins/@PLUGIN@/@PLUGIN@.jar `$gerrit_site/lib`
-```
-
-This project can be imported into the Eclipse IDE.
-Add the plugin name to the `CUSTOM_PLUGINS` set in
-Gerrit core in `tools/bzl/plugins.bzl`, and execute:
-
-```
-  ./tools/eclipse/project.py
-```
-
-How to build the Gerrit Plugin API is described in the [Gerrit
-documentation](../../../Documentation/dev-bazel.html#_extension_and_plugin_api_jar_files).