From b9523caa7041ccd01c1eaf8f2a453a9f627d2cf1 Mon Sep 17 00:00:00 2001 From: Ralf Haferkamp Date: Thu, 9 Oct 2025 15:00:10 +0200 Subject: [PATCH] devtools: add deployment for multi-tenancy --- .../deployments/multi-tenancy/.env.example | 161 ++ devtools/deployments/multi-tenancy/README.md | 49 + .../deployments/multi-tenancy/certs/.gitkeep | 0 .../deployments/multi-tenancy/certs/acme.json | 0 .../keycloak/clients/OpenCloudAndroid.json | 63 + .../keycloak/clients/OpenCloudDesktop.json | 64 + .../config/keycloak/clients/OpenCloudIOS.json | 63 + .../config/keycloak/clients/cyberduck.json | 66 + .../config/keycloak/clients/web.json | 74 + .../keycloak/docker-entrypoint-override.sh | 8 + .../config/keycloak/openCloud-realm.dist.json | 2419 +++++++++++++++++ .../opencloud/login/resources/css/theme.css | 38 + .../fonts/OpenCloud500-Regular.woff2 | Bin 0 -> 24096 bytes .../resources/fonts/OpenCloud750-Bold.woff2 | Bin 0 -> 24624 bytes .../login/resources/img/background.png | Bin 0 -> 30595 bytes .../opencloud/login/resources/img/logo.svg | 14 + .../opencloud/login/resources/js/script.js | 19 + .../themes/opencloud/login/theme.properties | 5 + .../config/ldap/docker-entrypoint-override.sh | 9 + .../config/ldap/ldif/10_base.ldif | 20 + .../config/ldap/ldif/20_admin.ldif | 20 + .../config/opencloud/apps/.gitkeep | 0 .../multi-tenancy/config/opencloud/csp.yaml | 44 + .../multi-tenancy/config/opencloud/proxy.yaml | 40 + .../traefik/docker-entrypoint-override.sh | 72 + .../config/traefik/dynamic/.gitkeep | 0 .../multi-tenancy/docker-compose.yml | 119 + .../multi-tenancy/initialize_users.go | 140 + .../deployments/multi-tenancy/keycloak.yml | 55 + .../deployments/multi-tenancy/ldap-server.yml | 32 + .../multi-tenancy/testing/ldap-manager.yml | 24 + .../deployments/multi-tenancy/traefik.yml | 45 + 32 files changed, 3663 insertions(+) create mode 100644 devtools/deployments/multi-tenancy/.env.example create mode 100644 devtools/deployments/multi-tenancy/README.md create mode 100644 devtools/deployments/multi-tenancy/certs/.gitkeep create mode 100644 devtools/deployments/multi-tenancy/certs/acme.json create mode 100644 devtools/deployments/multi-tenancy/config/keycloak/clients/OpenCloudAndroid.json create mode 100644 devtools/deployments/multi-tenancy/config/keycloak/clients/OpenCloudDesktop.json create mode 100644 devtools/deployments/multi-tenancy/config/keycloak/clients/OpenCloudIOS.json create mode 100644 devtools/deployments/multi-tenancy/config/keycloak/clients/cyberduck.json create mode 100644 devtools/deployments/multi-tenancy/config/keycloak/clients/web.json create mode 100644 devtools/deployments/multi-tenancy/config/keycloak/docker-entrypoint-override.sh create mode 100644 devtools/deployments/multi-tenancy/config/keycloak/openCloud-realm.dist.json create mode 100644 devtools/deployments/multi-tenancy/config/keycloak/themes/opencloud/login/resources/css/theme.css create mode 100644 devtools/deployments/multi-tenancy/config/keycloak/themes/opencloud/login/resources/fonts/OpenCloud500-Regular.woff2 create mode 100644 devtools/deployments/multi-tenancy/config/keycloak/themes/opencloud/login/resources/fonts/OpenCloud750-Bold.woff2 create mode 100644 devtools/deployments/multi-tenancy/config/keycloak/themes/opencloud/login/resources/img/background.png create mode 100644 devtools/deployments/multi-tenancy/config/keycloak/themes/opencloud/login/resources/img/logo.svg create mode 100644 devtools/deployments/multi-tenancy/config/keycloak/themes/opencloud/login/resources/js/script.js create mode 100644 devtools/deployments/multi-tenancy/config/keycloak/themes/opencloud/login/theme.properties create mode 100644 devtools/deployments/multi-tenancy/config/ldap/docker-entrypoint-override.sh create mode 100644 devtools/deployments/multi-tenancy/config/ldap/ldif/10_base.ldif create mode 100644 devtools/deployments/multi-tenancy/config/ldap/ldif/20_admin.ldif create mode 100644 devtools/deployments/multi-tenancy/config/opencloud/apps/.gitkeep create mode 100644 devtools/deployments/multi-tenancy/config/opencloud/csp.yaml create mode 100644 devtools/deployments/multi-tenancy/config/opencloud/proxy.yaml create mode 100644 devtools/deployments/multi-tenancy/config/traefik/docker-entrypoint-override.sh create mode 100644 devtools/deployments/multi-tenancy/config/traefik/dynamic/.gitkeep create mode 100644 devtools/deployments/multi-tenancy/docker-compose.yml create mode 100644 devtools/deployments/multi-tenancy/initialize_users.go create mode 100644 devtools/deployments/multi-tenancy/keycloak.yml create mode 100644 devtools/deployments/multi-tenancy/ldap-server.yml create mode 100644 devtools/deployments/multi-tenancy/testing/ldap-manager.yml create mode 100644 devtools/deployments/multi-tenancy/traefik.yml diff --git a/devtools/deployments/multi-tenancy/.env.example b/devtools/deployments/multi-tenancy/.env.example new file mode 100644 index 000000000..2736dda3f --- /dev/null +++ b/devtools/deployments/multi-tenancy/.env.example @@ -0,0 +1,161 @@ +## Basic Settings ## +# Define the docker compose log driver used. +# Defaults to local +LOG_DRIVER= +# If you're on an internet facing server, comment out following line. +# It skips certificate validation for various parts of OpenCloud and is +# needed when self signed certificates are used. +INSECURE=true + +## Features ## +COMPOSE_FILE=docker-compose.yml:traefik.yml:keycloak.yml:ldap-server.yml + +## Traefik Settings ## +# Note: Traefik is always enabled and can't be disabled. +# Serve Traefik dashboard. +# Defaults to "false". +TRAEFIK_DASHBOARD= +# Domain of Traefik, where you can find the dashboard. +# Defaults to "traefik.opencloud.test" +TRAEFIK_DOMAIN= +# Basic authentication for the traefik dashboard. +# Defaults to user "admin" and password "admin" (written as: "admin:$2y$05$KDHu3xq92SPaO3G8Ybkc7edd51pPLJcG1nWk3lmlrIdANQ/B6r5pq"). +# To create user:password pair, it's possible to use this command: +# echo $(htpasswd -nB user) | sed -e s/\\$/\\$\\$/g +TRAEFIK_BASIC_AUTH_USERS= +# Email address for obtaining LetsEncrypt certificates. +# Needs only be changed if this is a public facing server. +TRAEFIK_ACME_MAIL= +# Set to the following for testing to check the certificate process: +# "https://acme-staging-v02.api.letsencrypt.org/directory" +# With staging configured, there will be an SSL error in the browser. +# When certificates are displayed and are emitted by # "Fake LE Intermediate X1", +# the process went well and the envvar can be reset to empty to get valid certificates. +TRAEFIK_ACME_CASERVER= +# Enable the Traefik ACME (Automatic Certificate Management Environment) for automatic SSL certificate management. +TRAEFIK_SERVICES_TLS_CONFIG="tls.certresolver=letsencrypt" +# Enable Traefik to use local certificates. +#TRAEFIK_SERVICES_TLS_CONFIG="tls=true" +# You also need to provide a config file in ./config/traefik/dynamic/certs.yml +# Example: +# cat ./config/traefik/dynamic/certs.yml +# tls: +# certificates: +# - certFile: /certs/opencloud.test.crt +# keyFile: /certs/opencloud.test.key +# stores: +# - default +# +# The certificates need to copied into ./certs/, the absolute path inside the container is /certs/. +# You can also use TRAEFIK_CERTS_DIR=/path/on/host to set the path to the certificates directory. +# Enable the access log for Traefik by setting the following variable to true. +TRAEFIK_ACCESS_LOG= +# Configure the log level for Traefik. +# Possible values are "TRACE", "DEBUG", "INFO", "WARN", "ERROR", "FATAL" and "PANIC". Default is "ERROR". +TRAEFIK_LOG_LEVEL= + + +## OpenCloud Settings ## +# The opencloud container image. +# For production releases: "opencloudeu/opencloud" +# For rolling releases: "opencloudeu/opencloud-rolling" +# Defaults to production if not set otherwise +OC_DOCKER_IMAGE=opencloudeu/opencloud-rolling +# The openCloud container version. +# Defaults to "latest" and points to the latest stable tag. +OC_DOCKER_TAG= +# Domain of openCloud, where you can find the frontend. +# Defaults to "cloud.opencloud.test" +OC_DOMAIN= +# Demo users should not be created on a production instance, +# because their passwords are public. Defaults to "false". +# If demo users is set to "true", the following user accounts are created automatically: +# alan, mary, margaret, dennis and lynn - the password is 'demo' for all. +DEMO_USERS= +# Admin Password for the OpenCloud admin user. +# NOTE: This is only needed when using the built-in LDAP server (idm). +# If you are using an external LDAP server, the admin password is managed by the LDAP server. +# NOTE: This variable needs to be set before the first start of OpenCloud. Changes to this variable after the first start will be IGNORED. +# If not set, opencloud will not work properly. The container will be restarting. +# After the first initialization, the admin password can only be changed via the OpenCloud User Settings UI or by using the OpenCloud CLI. +# Documentation: https://docs.opencloud.eu/docs/admin/resources/common-issues#-change-admin-password-set-in-env +INITIAL_ADMIN_PASSWORD= +# Define the openCloud loglevel used. +# +LOG_LEVEL= +# Define the kind of logging. +# The default log can be read by machines. +# Set this to true to make the log human readable. +# LOG_PRETTY=true +# +# Define the openCloud storage location. Set the paths for config and data to a local path. +# Ensure that the configuration and data directories are owned by the user and group with ID 1000:1000. +# This matches the default user inside the container and avoids permission issues when accessing files. +# Note that especially the data directory can grow big. +# Leaving it default stores data in docker internal volumes. +# OC_CONFIG_DIR=/your/local/opencloud/config +# OC_DATA_DIR=/your/local/opencloud/data + +### Compose Configuration ### +# Path separator for supplemental compose files specified in COMPOSE_FILE. +COMPOSE_PATH_SEPARATOR=: + +### Ldap Settings ### +# LDAP is always needed for OpenCloud to store user data as there is no relational database. +# The built-in LDAP server should used for testing purposes or small installations only. +# For production installations, it is recommended to use an external LDAP server. +# We are using OpenLDAP as the default LDAP server because it is proven to be stable and reliable. +# This LDAP configuration is known to work with OpenCloud and provides a blueprint for +# configuring an external LDAP server based on other products like Microsoft Active Directory or other LDAP servers. +# +# Password of LDAP bind user "cn=admin,dc=opencloud,dc=eu". Defaults to "admin" +LDAP_BIND_PASSWORD= +# The LDAP server also creates an openCloud admin user dn: uid=admin,ou=users,dc=opencloud,dc=eu +# The initial password for this user is "admin" +# NOTE: This password can only be set once, if you want to change it later, you have to use the OpenCloud User Settings UI. +# If you changed the password and lost it, you need to execute the following LDAP query to reset it: +# enter the ldap-server container with `docker compose exec ldap-server sh` +# and run the following command to change the password: +# ldappasswd -H ldap://127.0.0.1:1389 -D "cn=admin,dc=opencloud,dc=eu" -W "uid=admin,ou=users,dc=opencloud,dc=eu" +# You will be prompted for the LDAP bind password. +# The output should provide you a new password for the admin user. + + +### Keycloak Settings ### +# Keycloak is an open-source identity and access management solution. +# We are using Keycloak as the default identity provider on production installations. +# It can be used to federate authentication with other identity providers like +# Microsoft Entra ID, ADFS or other SAML/OIDC providers. +# The use of Keycloak as bridge between OpenCloud and other identity providers creates more control over the +# authentication process, the allowed clients and the session management. +# Keycloak also manages the Role Based Access Control (RBAC) for OpenCloud. +# Keycloak can be used in two different modes: +# 1. Autoprovisioning: New users are automatically created in openCloud when they log in for the first time. +# 2. Shared User Directory: Users are created in Keycloak and can be used in OpenCloud immediately +# because the LDAP server is connected to both Keycloak and OpenCloud. +# Only use one of the two modes at a time. + +## Autoprovisioning Mode ## +# Use together with idm/external-idp.yml +# If you want to use a keycloak for local testing, you can use testing/external-keycloak.yml and testing/ldap-manager.yml +# Domain of your Identity Provider. +IDP_DOMAIN= +# IdP Issuer URL, which is used to identify the Identity Provider. +# We need the complete URL, including the protocol (http or https) and the realm. +# Example: "https://keycloak.opencloud.test/realms/openCloud" +IDP_ISSUER_URL= +# Url of the account edit page from your Identity Provider. +IDP_ACCOUNT_URL= + +## Shared User Directory Mode ## +# Use together with idm/ldap-keycloak.yml and traefik/ldap-keycloak.yml +# Domain for Keycloak. Defaults to "keycloak.opencloud.test". +KEYCLOAK_DOMAIN= +# Admin user login name. Defaults to "kcadmin". +KEYCLOAK_ADMIN= +# Admin user login password. Defaults to "admin". +KEYCLOAK_ADMIN_PASSWORD= +# Keycloak Database username. Defaults to "keycloak". +KC_DB_USERNAME= +# Keycloak Database password. Defaults to "keycloak". +KC_DB_PASSWORD= diff --git a/devtools/deployments/multi-tenancy/README.md b/devtools/deployments/multi-tenancy/README.md new file mode 100644 index 000000000..f3c4c9118 --- /dev/null +++ b/devtools/deployments/multi-tenancy/README.md @@ -0,0 +1,49 @@ +# Development/Test Deployment for a multi-tenacy setup + +The docker compose files in this directory are derived from the +opencloud-compose project and can be used to deploy a Development or Testing +environment for a multi-tenancy setup of OpenCloud. It consists of the +following services: + +* `provisioning`: The OpenCloud graph service deployed in a standalone mode. It + is configured to provide the libregraph education API for managing tenants + and users. The `ldap-server`service (see below) is used to store the tenants + and users. +* `ldap-server`: An OpenLDAP server that is used by the provisioning service to + store tenants and users. Used by the OpenCloud services as the user directory + (for looking up users and searching for sharees). +* `keycloak`: The OpenID Connect Provider used for authenticating users. The + pre-loaded realm is configured to add `tenantid` claim into the identity and + access tokens. It's also currently consuming user from the `ldap-server` + (this federation will likely go away in the future and is optional for future + configurations). +* `opencloud`: The OpenCloud configured so that is hides users from different + tenants from each other. + +To deploy the setup, run: + +```bash +docker compose -f docker-compose.yml -f keycloak.yml -f ldap-server.yml -f traefik.yml up +``` + +Once deployed you can use the `initialize_users.go` to create a couple of example +tenants and some users in each tenant: + +* Tenant `Famous Coders` with users `dennis` and `grace` +* Tenant `Scientists` with users `einstein` and `marie` + +The passwords for the users is set to `demo` in keycloak + +``` +> go run initialize_users.go +Created tenant: Famous Coders with id fc58e19a-3a2a-4afc-90ec-8f94986db340 +Created user: Dennis Ritchie with id ee1e14e7-b00b-4eec-8b03-a6bf0e29c77c +Created user: Grace Hopper with id a29f3afd-e4a3-4552-91e8-cc99e26bffce +Created tenant: Scientists with id 18406c53-e2d6-4e83-98b6-a55880eef195 +Created user: Albert Einstein with id 12023d37-d6ce-4f19-a318-b70866f265ba +Created user: Marie Curie with id 30c3c825-c37d-4e85-8195-0142e4884872 +Setting password for user: grace +Setting password for user: marie +Setting password for user: dennis +Setting password for user: einstein +``` diff --git a/devtools/deployments/multi-tenancy/certs/.gitkeep b/devtools/deployments/multi-tenancy/certs/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/devtools/deployments/multi-tenancy/certs/acme.json b/devtools/deployments/multi-tenancy/certs/acme.json new file mode 100644 index 000000000..e69de29bb diff --git a/devtools/deployments/multi-tenancy/config/keycloak/clients/OpenCloudAndroid.json b/devtools/deployments/multi-tenancy/config/keycloak/clients/OpenCloudAndroid.json new file mode 100644 index 000000000..c21838d67 --- /dev/null +++ b/devtools/deployments/multi-tenancy/config/keycloak/clients/OpenCloudAndroid.json @@ -0,0 +1,63 @@ +{ + "clientId": "OpenCloudAndroid", + "name": "OpenCloud Android App", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [ + "oc://android.opencloud.eu" + ], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": true, + "serviceAccountsEnabled": false, + "publicClient": true, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "saml.assertion.signature": "false", + "saml.force.post.binding": "false", + "saml.multivalued.roles": "false", + "saml.encrypt": "false", + "post.logout.redirect.uris": "oc://android.opencloud.eu", + "backchannel.logout.revoke.offline.tokens": "false", + "saml.server.signature": "false", + "saml.server.signature.keyinfo.ext": "false", + "exclude.session.state.from.auth.response": "false", + "backchannel.logout.session.required": "true", + "client_credentials.use_refresh_token": "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, + "defaultClientScopes": [ + "web-origins", + "profile", + "roles", + "groups", + "basic", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ], + "access": { + "view": true, + "configure": true, + "manage": true + } +} \ No newline at end of file diff --git a/devtools/deployments/multi-tenancy/config/keycloak/clients/OpenCloudDesktop.json b/devtools/deployments/multi-tenancy/config/keycloak/clients/OpenCloudDesktop.json new file mode 100644 index 000000000..d17a7cb6c --- /dev/null +++ b/devtools/deployments/multi-tenancy/config/keycloak/clients/OpenCloudDesktop.json @@ -0,0 +1,64 @@ +{ + "clientId": "OpenCloudDesktop", + "name": "OpenCloud Desktop Client", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [ + "http://127.0.0.1", + "http://localhost" + ], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": true, + "serviceAccountsEnabled": false, + "publicClient": true, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "saml.assertion.signature": "false", + "saml.force.post.binding": "false", + "saml.multivalued.roles": "false", + "saml.encrypt": "false", + "post.logout.redirect.uris": "+", + "backchannel.logout.revoke.offline.tokens": "false", + "saml.server.signature": "false", + "saml.server.signature.keyinfo.ext": "false", + "exclude.session.state.from.auth.response": "false", + "backchannel.logout.session.required": "true", + "client_credentials.use_refresh_token": "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, + "defaultClientScopes": [ + "web-origins", + "profile", + "roles", + "groups", + "basic", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ], + "access": { + "view": true, + "configure": true, + "manage": true + } +} \ No newline at end of file diff --git a/devtools/deployments/multi-tenancy/config/keycloak/clients/OpenCloudIOS.json b/devtools/deployments/multi-tenancy/config/keycloak/clients/OpenCloudIOS.json new file mode 100644 index 000000000..d09e08957 --- /dev/null +++ b/devtools/deployments/multi-tenancy/config/keycloak/clients/OpenCloudIOS.json @@ -0,0 +1,63 @@ +{ + "clientId": "OpenCloudIOS", + "name": "OpenCloud iOS App", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [ + "oc://ios.opencloud.eu" + ], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": true, + "serviceAccountsEnabled": false, + "publicClient": true, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "saml.assertion.signature": "false", + "saml.force.post.binding": "false", + "saml.multivalued.roles": "false", + "saml.encrypt": "false", + "post.logout.redirect.uris": "oc://ios.opencloud.eu", + "backchannel.logout.revoke.offline.tokens": "false", + "saml.server.signature": "false", + "saml.server.signature.keyinfo.ext": "false", + "exclude.session.state.from.auth.response": "false", + "backchannel.logout.session.required": "true", + "client_credentials.use_refresh_token": "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, + "defaultClientScopes": [ + "web-origins", + "profile", + "roles", + "groups", + "basic", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ], + "access": { + "view": true, + "configure": true, + "manage": true + } +} \ No newline at end of file diff --git a/devtools/deployments/multi-tenancy/config/keycloak/clients/cyberduck.json b/devtools/deployments/multi-tenancy/config/keycloak/clients/cyberduck.json new file mode 100644 index 000000000..5143323a4 --- /dev/null +++ b/devtools/deployments/multi-tenancy/config/keycloak/clients/cyberduck.json @@ -0,0 +1,66 @@ +{ + "clientId": "Cyberduck", + "name": "Cyberduck", + "description": "File transfer utility client", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [ + "x-cyberduck-action:oauth", + "x-mountainduck-action:oauth" + ], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": true, + "serviceAccountsEnabled": false, + "publicClient": true, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "saml.assertion.signature": "false", + "saml.force.post.binding": "false", + "saml.multivalued.roles": "false", + "saml.encrypt": "false", + "oauth2.device.authorization.grant.enabled": "false", + "backchannel.logout.revoke.offline.tokens": "false", + "saml.server.signature": "false", + "saml.server.signature.keyinfo.ext": "false", + "exclude.session.state.from.auth.response": "false", + "oidc.ciba.grant.enabled": "false", + "backchannel.logout.session.required": "true", + "client_credentials.use_refresh_token": "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, + "defaultClientScopes": [ + "web-origins", + "profile", + "roles", + "groups", + "basic", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ], + "access": { + "view": true, + "configure": true, + "manage": true + } +} \ No newline at end of file diff --git a/devtools/deployments/multi-tenancy/config/keycloak/clients/web.json b/devtools/deployments/multi-tenancy/config/keycloak/clients/web.json new file mode 100644 index 000000000..9ab819a44 --- /dev/null +++ b/devtools/deployments/multi-tenancy/config/keycloak/clients/web.json @@ -0,0 +1,74 @@ +{ + "clientId": "web", + "name": "OpenCloud Web App", + "description": "", + "rootUrl": "{{OC_URL}}", + "adminUrl": "{{OC_URL}}", + "baseUrl": "", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [ + "{{OC_URL}}/", + "{{OC_URL}}/oidc-callback.html", + "{{OC_URL}}/oidc-silent-redirect.html" + ], + "webOrigins": [ + "{{OC_URL}}" + ], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": true, + "serviceAccountsEnabled": false, + "publicClient": true, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "saml.assertion.signature": "false", + "saml.force.post.binding": "false", + "saml.multivalued.roles": "false", + "saml.encrypt": "false", + "post.logout.redirect.uris": "+", + "oauth2.device.authorization.grant.enabled": "false", + "backchannel.logout.revoke.offline.tokens": "false", + "saml.server.signature": "false", + "saml.server.signature.keyinfo.ext": "false", + "exclude.session.state.from.auth.response": "false", + "oidc.ciba.grant.enabled": "false", + "backchannel.logout.url": "{{OC_URL}}/backchannel_logout", + "backchannel.logout.session.required": "true", + "client_credentials.use_refresh_token": "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, + "defaultClientScopes": [ + "web-origins", + "profile", + "roles", + "groups", + "basic", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ], + "access": { + "view": true, + "configure": true, + "manage": true + } +} \ No newline at end of file diff --git a/devtools/deployments/multi-tenancy/config/keycloak/docker-entrypoint-override.sh b/devtools/deployments/multi-tenancy/config/keycloak/docker-entrypoint-override.sh new file mode 100644 index 000000000..480975038 --- /dev/null +++ b/devtools/deployments/multi-tenancy/config/keycloak/docker-entrypoint-override.sh @@ -0,0 +1,8 @@ +#!/bin/bash +printenv +# replace openCloud domain and LDAP password in keycloak realm import +mkdir /opt/keycloak/data/import +sed -e "s/cloud.opencloud.test/${OC_DOMAIN}/g" -e "s/ldap-admin-password/${LDAP_ADMIN_PASSWORD:-admin}/g" /opt/keycloak/data/import-dist/openCloud-realm.json > /opt/keycloak/data/import/openCloud-realm.json + +# run original docker-entrypoint +/opt/keycloak/bin/kc.sh "$@" diff --git a/devtools/deployments/multi-tenancy/config/keycloak/openCloud-realm.dist.json b/devtools/deployments/multi-tenancy/config/keycloak/openCloud-realm.dist.json new file mode 100644 index 000000000..65bb52306 --- /dev/null +++ b/devtools/deployments/multi-tenancy/config/keycloak/openCloud-realm.dist.json @@ -0,0 +1,2419 @@ +{ + "id" : "openCloud", + "realm" : "openCloud", + "displayName" : "OpenCloud", + "displayNameHtml" : "
OpenCloud
", + "notBefore" : 0, + "defaultSignatureAlgorithm" : "RS256", + "revokeRefreshToken" : false, + "refreshTokenMaxReuse" : 0, + "accessTokenLifespan" : 300, + "accessTokenLifespanForImplicitFlow" : 900, + "ssoSessionIdleTimeout" : 1800, + "ssoSessionMaxLifespan" : 36000, + "ssoSessionIdleTimeoutRememberMe" : 0, + "ssoSessionMaxLifespanRememberMe" : 0, + "offlineSessionIdleTimeout" : 2592000, + "offlineSessionMaxLifespanEnabled" : false, + "offlineSessionMaxLifespan" : 5184000, + "clientSessionIdleTimeout" : 0, + "clientSessionMaxLifespan" : 0, + "clientOfflineSessionIdleTimeout" : 0, + "clientOfflineSessionMaxLifespan" : 0, + "accessCodeLifespan" : 60, + "accessCodeLifespanUserAction" : 300, + "accessCodeLifespanLogin" : 1800, + "actionTokenGeneratedByAdminLifespan" : 43200, + "actionTokenGeneratedByUserLifespan" : 300, + "oauth2DeviceCodeLifespan" : 600, + "oauth2DevicePollingInterval" : 5, + "enabled" : true, + "sslRequired" : "external", + "registrationAllowed" : false, + "registrationEmailAsUsername" : false, + "rememberMe" : true, + "verifyEmail" : false, + "loginWithEmailAllowed" : true, + "duplicateEmailsAllowed" : false, + "resetPasswordAllowed" : true, + "editUsernameAllowed" : false, + "bruteForceProtected" : true, + "permanentLockout" : false, + "maxTemporaryLockouts" : 0, + "bruteForceStrategy" : "MULTIPLE", + "maxFailureWaitSeconds" : 900, + "minimumQuickLoginWaitSeconds" : 60, + "waitIncrementSeconds" : 60, + "quickLoginCheckMilliSeconds" : 1000, + "maxDeltaTimeSeconds" : 43200, + "failureFactor" : 30, + "roles" : { + "realm" : [ { + "id" : "2d576514-4aae-46aa-9d9c-075f55f4d988", + "name" : "uma_authorization", + "description" : "${role_uma_authorization}", + "composite" : false, + "clientRole" : false, + "containerId" : "openCloud", + "attributes" : { } + }, { + "id" : "2aadd357-682c-406b-8874-293091995fdd", + "name" : "opencloudSpaceAdmin", + "description" : "", + "composite" : false, + "clientRole" : false, + "containerId" : "openCloud", + "attributes" : { } + }, { + "id" : "38071a68-456a-4553-846a-fa67bf5596cc", + "name" : "opencloudGuest", + "description" : "", + "composite" : false, + "clientRole" : false, + "containerId" : "openCloud", + "attributes" : { } + }, { + "id" : "71881883-1768-46bd-a24d-a356a2afdf7f", + "name" : "opencloudAdmin", + "description" : "", + "composite" : false, + "clientRole" : false, + "containerId" : "openCloud", + "attributes" : { } + }, { + "id" : "e2145b30-bf6f-49fb-af3f-1b40168bfcef", + "name" : "offline_access", + "description" : "${role_offline-access}", + "composite" : false, + "clientRole" : false, + "containerId" : "openCloud", + "attributes" : { } + }, { + "id" : "82e13ea7-aac4-4d2c-9fc7-cff8333dbe19", + "name" : "default-roles-opencloud", + "description" : "${role_default-roles}", + "composite" : true, + "composites" : { + "realm" : [ "opencloudGuest", "offline_access", "uma_authorization" ], + "client" : { + "account" : [ "manage-account", "view-profile" ] + } + }, + "clientRole" : false, + "containerId" : "openCloud", + "attributes" : { } + }, { + "id" : "d7beeea8-8ff4-406b-8fb6-ab2dd81e6b11", + "name" : "opencloudUser", + "description" : "", + "composite" : false, + "clientRole" : false, + "containerId" : "openCloud", + "attributes" : { } + } ], + "client" : { + "_system" : [ ], + "realm-management" : [ { + "id" : "979ce053-a671-4b50-81d5-da4bdf7404c9", + "name" : "view-clients", + "description" : "${role_view-clients}", + "composite" : true, + "composites" : { + "client" : { + "realm-management" : [ "query-clients" ] + } + }, + "clientRole" : true, + "containerId" : "7848ee94-cc9b-40db-946f-a86ac73dc9b7", + "attributes" : { } + }, { + "id" : "4bec4791-e888-4dac-bc95-71720d5981b9", + "name" : "query-users", + "description" : "${role_query-users}", + "composite" : false, + "clientRole" : true, + "containerId" : "7848ee94-cc9b-40db-946f-a86ac73dc9b7", + "attributes" : { } + }, { + "id" : "955b4406-b04f-432d-a61a-571675874341", + "name" : "manage-authorization", + "description" : "${role_manage-authorization}", + "composite" : false, + "clientRole" : true, + "containerId" : "7848ee94-cc9b-40db-946f-a86ac73dc9b7", + "attributes" : { } + }, { + "id" : "baa219af-2773-4d59-b06b-485f10fbbab3", + "name" : "view-events", + "description" : "${role_view-events}", + "composite" : false, + "clientRole" : true, + "containerId" : "7848ee94-cc9b-40db-946f-a86ac73dc9b7", + "attributes" : { } + }, { + "id" : "f280bc03-d079-478d-be06-3590580b25e9", + "name" : "manage-users", + "description" : "${role_manage-users}", + "composite" : false, + "clientRole" : true, + "containerId" : "7848ee94-cc9b-40db-946f-a86ac73dc9b7", + "attributes" : { } + }, { + "id" : "db698163-84ad-46c9-958f-bb5f80ae78b5", + "name" : "query-clients", + "description" : "${role_query-clients}", + "composite" : false, + "clientRole" : true, + "containerId" : "7848ee94-cc9b-40db-946f-a86ac73dc9b7", + "attributes" : { } + }, { + "id" : "36c04d89-abf7-4a2c-a808-8efa9aca1435", + "name" : "manage-clients", + "description" : "${role_manage-clients}", + "composite" : false, + "clientRole" : true, + "containerId" : "7848ee94-cc9b-40db-946f-a86ac73dc9b7", + "attributes" : { } + }, { + "id" : "06eae953-11d5-4344-b089-ffce1e68d5d8", + "name" : "query-realms", + "description" : "${role_query-realms}", + "composite" : false, + "clientRole" : true, + "containerId" : "7848ee94-cc9b-40db-946f-a86ac73dc9b7", + "attributes" : { } + }, { + "id" : "afe8aa78-2f06-43a5-8c99-cf68a1f5a86a", + "name" : "realm-admin", + "description" : "${role_realm-admin}", + "composite" : true, + "composites" : { + "client" : { + "realm-management" : [ "view-clients", "query-users", "manage-authorization", "view-events", "manage-users", "query-clients", "manage-clients", "query-realms", "impersonation", "manage-realm", "manage-identity-providers", "view-authorization", "create-client", "query-groups", "view-users", "view-realm", "view-identity-providers", "manage-events" ] + } + }, + "clientRole" : true, + "containerId" : "7848ee94-cc9b-40db-946f-a86ac73dc9b7", + "attributes" : { } + }, { + "id" : "22ee128a-b28e-4c6a-aa8e-ad4136d74e1b", + "name" : "impersonation", + "description" : "${role_impersonation}", + "composite" : false, + "clientRole" : true, + "containerId" : "7848ee94-cc9b-40db-946f-a86ac73dc9b7", + "attributes" : { } + }, { + "id" : "89d4f119-7f87-44d9-8eef-d207304de778", + "name" : "manage-realm", + "description" : "${role_manage-realm}", + "composite" : false, + "clientRole" : true, + "containerId" : "7848ee94-cc9b-40db-946f-a86ac73dc9b7", + "attributes" : { } + }, { + "id" : "ebffeff4-6794-4003-a2ab-a79eff7d1baa", + "name" : "manage-identity-providers", + "description" : "${role_manage-identity-providers}", + "composite" : false, + "clientRole" : true, + "containerId" : "7848ee94-cc9b-40db-946f-a86ac73dc9b7", + "attributes" : { } + }, { + "id" : "2361a7ff-d2b3-43f5-b360-ad0e44fba65c", + "name" : "view-authorization", + "description" : "${role_view-authorization}", + "composite" : false, + "clientRole" : true, + "containerId" : "7848ee94-cc9b-40db-946f-a86ac73dc9b7", + "attributes" : { } + }, { + "id" : "f7bf6d7a-a861-49c6-8f6f-225c18d0a03a", + "name" : "create-client", + "description" : "${role_create-client}", + "composite" : false, + "clientRole" : true, + "containerId" : "7848ee94-cc9b-40db-946f-a86ac73dc9b7", + "attributes" : { } + }, { + "id" : "34ccce1c-5a7e-4268-8836-2276545be900", + "name" : "query-groups", + "description" : "${role_query-groups}", + "composite" : false, + "clientRole" : true, + "containerId" : "7848ee94-cc9b-40db-946f-a86ac73dc9b7", + "attributes" : { } + }, { + "id" : "430f7831-8f22-4518-bd15-2998eae45a51", + "name" : "view-users", + "description" : "${role_view-users}", + "composite" : true, + "composites" : { + "client" : { + "realm-management" : [ "query-groups", "query-users" ] + } + }, + "clientRole" : true, + "containerId" : "7848ee94-cc9b-40db-946f-a86ac73dc9b7", + "attributes" : { } + }, { + "id" : "371a31e6-4494-4b74-b3ea-d030663423ed", + "name" : "view-realm", + "description" : "${role_view-realm}", + "composite" : false, + "clientRole" : true, + "containerId" : "7848ee94-cc9b-40db-946f-a86ac73dc9b7", + "attributes" : { } + }, { + "id" : "e875775b-7a3e-4a5d-9e4e-376351b78626", + "name" : "view-identity-providers", + "description" : "${role_view-identity-providers}", + "composite" : false, + "clientRole" : true, + "containerId" : "7848ee94-cc9b-40db-946f-a86ac73dc9b7", + "attributes" : { } + }, { + "id" : "3dce7929-ee1f-40cd-9be1-7addcae92cef", + "name" : "manage-events", + "description" : "${role_manage-events}", + "composite" : false, + "clientRole" : true, + "containerId" : "7848ee94-cc9b-40db-946f-a86ac73dc9b7", + "attributes" : { } + } ], + "OpenCloudDesktop" : [ ], + "web" : [ ], + "security-admin-console" : [ ], + "OpenCloudIOS" : [ ], + "admin-cli" : [ ], + "OpenCloudAndroid" : [ ], + "account-console" : [ ], + "broker" : [ { + "id" : "81fad68a-8dd8-4d79-9a8f-206a82460145", + "name" : "read-token", + "description" : "${role_read-token}", + "composite" : false, + "clientRole" : true, + "containerId" : "002faf0a-716c-4230-81c7-ce22d1eb832c", + "attributes" : { } + } ], + "account" : [ { + "id" : "c49a49da-8ad0-44cb-b518-6d7d72cbe494", + "name" : "manage-account", + "description" : "${role_manage-account}", + "composite" : true, + "composites" : { + "client" : { + "account" : [ "manage-account-links" ] + } + }, + "clientRole" : true, + "containerId" : "9850adad-7910-4b67-a790-da6444361618", + "attributes" : { } + }, { + "id" : "9dc2244e-b8a7-44f1-b173-d2b929fedcca", + "name" : "view-consent", + "description" : "${role_view-consent}", + "composite" : false, + "clientRole" : true, + "containerId" : "9850adad-7910-4b67-a790-da6444361618", + "attributes" : { } + }, { + "id" : "ce115327-99c9-44d4-ba7d-820397dc11e6", + "name" : "manage-account-links", + "description" : "${role_manage-account-links}", + "composite" : false, + "clientRole" : true, + "containerId" : "9850adad-7910-4b67-a790-da6444361618", + "attributes" : { } + }, { + "id" : "2ffdf854-084b-467a-91c6-7f07844efc9a", + "name" : "view-groups", + "description" : "${role_view-groups}", + "composite" : false, + "clientRole" : true, + "containerId" : "9850adad-7910-4b67-a790-da6444361618", + "attributes" : { } + }, { + "id" : "8c45ca71-32aa-4547-932d-412da5e371ed", + "name" : "view-profile", + "description" : "${role_view-profile}", + "composite" : false, + "clientRole" : true, + "containerId" : "9850adad-7910-4b67-a790-da6444361618", + "attributes" : { } + }, { + "id" : "cbeecf6d-9af8-4746-877b-74800a894c35", + "name" : "view-applications", + "description" : "${role_view-applications}", + "composite" : false, + "clientRole" : true, + "containerId" : "9850adad-7910-4b67-a790-da6444361618", + "attributes" : { } + }, { + "id" : "ea798f64-b5f8-417f-9fe0-d3cd9172884f", + "name" : "delete-account", + "description" : "${role_delete-account}", + "composite" : false, + "clientRole" : true, + "containerId" : "9850adad-7910-4b67-a790-da6444361618", + "attributes" : { } + }, { + "id" : "e73aaf6d-e67b-491a-9cc3-78c32c82b42c", + "name" : "manage-consent", + "description" : "${role_manage-consent}", + "composite" : true, + "composites" : { + "client" : { + "account" : [ "view-consent" ] + } + }, + "clientRole" : true, + "containerId" : "9850adad-7910-4b67-a790-da6444361618", + "attributes" : { } + } ] + } + }, + "groups" : [ { + "id" : "ed57d574-3bd9-4b51-9691-5d1b6aef7c68", + "name" : "administrators", + "path" : "/administrators", + "subGroups" : [ ], + "attributes" : { }, + "realmRoles" : [ "opencloudAdmin" ], + "clientRoles" : { } + }, { + "id" : "1bf1a677-302d-4c28-ac53-e83c87372a81", + "name" : "apollos", + "path" : "/apollos", + "subGroups" : [ ], + "attributes" : { }, + "realmRoles" : [ "opencloudSpaceAdmin" ], + "clientRoles" : { } + }, { + "id" : "12611514-517a-4fe6-a85d-41a018e98598", + "name" : "basic-haters", + "path" : "/basic-haters", + "subGroups" : [ ], + "attributes" : { }, + "realmRoles" : [ ], + "clientRoles" : { } + }, { + "id" : "7a56c37d-3b86-487e-abe5-144452678255", + "name" : "bible-readers", + "path" : "/bible-readers", + "subGroups" : [ ], + "attributes" : { }, + "realmRoles" : [ ], + "clientRoles" : { } + }, { + "id" : "03a5d51c-5e55-40ab-b8c8-5b796f77ab34", + "name" : "chess-lovers", + "path" : "/chess-lovers", + "subGroups" : [ ], + "attributes" : { }, + "realmRoles" : [ ], + "clientRoles" : { } + }, { + "id" : "255c4aea-a3c6-41f5-bca5-0dec59cca3ee", + "name" : "machine-lovers", + "path" : "/machine-lovers", + "subGroups" : [ ], + "attributes" : { }, + "realmRoles" : [ ], + "clientRoles" : { } + }, { + "id" : "d1d0f574-6a8e-49da-981b-bd0f667ec650", + "name" : "programmers", + "path" : "/programmers", + "subGroups" : [ ], + "attributes" : { }, + "realmRoles" : [ ], + "clientRoles" : { } + }, { + "id" : "f8ab7ab3-e44b-445b-86a7-26d61259a6a6", + "name" : "unix-lovers", + "path" : "/unix-lovers", + "subGroups" : [ ], + "attributes" : { }, + "realmRoles" : [ "opencloudAdmin" ], + "clientRoles" : { } + }, { + "id" : "c3e5bec3-a125-4043-b872-5c38dc659a13", + "name" : "users", + "path" : "/users", + "subGroups" : [ ], + "attributes" : { }, + "realmRoles" : [ "opencloudUser" ], + "clientRoles" : { } + }, { + "id" : "ed1c5f92-4938-494f-83d3-a73be393a270", + "name" : "vlsi-lovers", + "path" : "/vlsi-lovers", + "subGroups" : [ ], + "attributes" : { }, + "realmRoles" : [ ], + "clientRoles" : { } + } ], + "defaultRole" : { + "id" : "82e13ea7-aac4-4d2c-9fc7-cff8333dbe19", + "name" : "default-roles-opencloud", + "description" : "${role_default-roles}", + "composite" : true, + "clientRole" : false, + "containerId" : "openCloud" + }, + "requiredCredentials" : [ "password" ], + "otpPolicyType" : "totp", + "otpPolicyAlgorithm" : "HmacSHA1", + "otpPolicyInitialCounter" : 0, + "otpPolicyDigits" : 6, + "otpPolicyLookAheadWindow" : 1, + "otpPolicyPeriod" : 30, + "otpPolicyCodeReusable" : false, + "otpSupportedApplications" : [ "totpAppFreeOTPName", "totpAppGoogleName", "totpAppMicrosoftAuthenticatorName" ], + "localizationTexts" : { }, + "webAuthnPolicyRpEntityName" : "keycloak", + "webAuthnPolicySignatureAlgorithms" : [ "ES256" ], + "webAuthnPolicyRpId" : "", + "webAuthnPolicyAttestationConveyancePreference" : "not specified", + "webAuthnPolicyAuthenticatorAttachment" : "not specified", + "webAuthnPolicyRequireResidentKey" : "not specified", + "webAuthnPolicyUserVerificationRequirement" : "not specified", + "webAuthnPolicyCreateTimeout" : 0, + "webAuthnPolicyAvoidSameAuthenticatorRegister" : false, + "webAuthnPolicyAcceptableAaguids" : [ ], + "webAuthnPolicyExtraOrigins" : [ ], + "webAuthnPolicyPasswordlessRpEntityName" : "keycloak", + "webAuthnPolicyPasswordlessSignatureAlgorithms" : [ "ES256" ], + "webAuthnPolicyPasswordlessRpId" : "", + "webAuthnPolicyPasswordlessAttestationConveyancePreference" : "not specified", + "webAuthnPolicyPasswordlessAuthenticatorAttachment" : "not specified", + "webAuthnPolicyPasswordlessRequireResidentKey" : "not specified", + "webAuthnPolicyPasswordlessUserVerificationRequirement" : "not specified", + "webAuthnPolicyPasswordlessCreateTimeout" : 0, + "webAuthnPolicyPasswordlessAvoidSameAuthenticatorRegister" : false, + "webAuthnPolicyPasswordlessAcceptableAaguids" : [ ], + "webAuthnPolicyPasswordlessExtraOrigins" : [ ], + "scopeMappings" : [ { + "clientScope" : "offline_access", + "roles" : [ "offline_access" ] + }, { + "clientScope" : "roles", + "roles" : [ "opencloudGuest", "opencloudAdmin", "opencloudSpaceAdmin", "opencloudUser" ] + } ], + "clientScopeMappings" : { + "account" : [ { + "client" : "account-console", + "roles" : [ "manage-account", "view-groups" ] + } ] + }, + "clients" : [ { + "id" : "c8367556-1d13-4979-b4f6-5e2cff1f82ae", + "clientId" : "OpenCloudAndroid", + "name" : "OpenCloud Android App", + "surrogateAuthRequired" : false, + "enabled" : true, + "alwaysDisplayInConsole" : false, + "clientAuthenticatorType" : "client-secret", + "redirectUris" : [ "oc://android.opencloud.eu" ], + "webOrigins" : [ ], + "notBefore" : 0, + "bearerOnly" : false, + "consentRequired" : false, + "standardFlowEnabled" : true, + "implicitFlowEnabled" : false, + "directAccessGrantsEnabled" : true, + "serviceAccountsEnabled" : false, + "publicClient" : true, + "frontchannelLogout" : false, + "protocol" : "openid-connect", + "attributes" : { + "saml.assertion.signature" : "false", + "saml.force.post.binding" : "false", + "saml.multivalued.roles" : "false", + "saml.encrypt" : "false", + "post.logout.redirect.uris" : "oc://android.opencloud.eu", + "backchannel.logout.revoke.offline.tokens" : "false", + "saml.server.signature" : "false", + "saml.server.signature.keyinfo.ext" : "false", + "exclude.session.state.from.auth.response" : "false", + "realm_client" : "false", + "backchannel.logout.session.required" : "true", + "client_credentials.use_refresh_token" : "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, + "defaultClientScopes" : [ "web-origins", "profile", "roles", "groups", "basic", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] + }, { + "id" : "fc7d8a8e-cb92-4cb0-b404-d723c07d8d4f", + "clientId" : "OpenCloudDesktop", + "name" : "OpenCloud Desktop Client", + "surrogateAuthRequired" : false, + "enabled" : true, + "alwaysDisplayInConsole" : false, + "clientAuthenticatorType" : "client-secret", + "redirectUris" : [ "http://127.0.0.1", "http://localhost" ], + "webOrigins" : [ ], + "notBefore" : 0, + "bearerOnly" : false, + "consentRequired" : false, + "standardFlowEnabled" : true, + "implicitFlowEnabled" : false, + "directAccessGrantsEnabled" : true, + "serviceAccountsEnabled" : false, + "publicClient" : true, + "frontchannelLogout" : false, + "protocol" : "openid-connect", + "attributes" : { + "saml.assertion.signature" : "false", + "saml.force.post.binding" : "false", + "saml.multivalued.roles" : "false", + "saml.encrypt" : "false", + "post.logout.redirect.uris" : "+", + "backchannel.logout.revoke.offline.tokens" : "false", + "saml.server.signature" : "false", + "saml.server.signature.keyinfo.ext" : "false", + "exclude.session.state.from.auth.response" : "false", + "realm_client" : "false", + "backchannel.logout.session.required" : "true", + "client_credentials.use_refresh_token" : "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, + "defaultClientScopes" : [ "web-origins", "profile", "roles", "groups", "OpenCloudUnique_ID", "basic", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] + }, { + "id" : "6ae0e3da-38ff-47a4-a76e-b59eec0a2de9", + "clientId" : "OpenCloudIOS", + "name" : "OpenCloud iOS App", + "surrogateAuthRequired" : false, + "enabled" : true, + "alwaysDisplayInConsole" : false, + "clientAuthenticatorType" : "client-secret", + "redirectUris" : [ "oc://ios.opencloud.eu" ], + "webOrigins" : [ ], + "notBefore" : 0, + "bearerOnly" : false, + "consentRequired" : false, + "standardFlowEnabled" : true, + "implicitFlowEnabled" : false, + "directAccessGrantsEnabled" : true, + "serviceAccountsEnabled" : false, + "publicClient" : true, + "frontchannelLogout" : false, + "protocol" : "openid-connect", + "attributes" : { + "saml.assertion.signature" : "false", + "saml.force.post.binding" : "false", + "saml.multivalued.roles" : "false", + "saml.encrypt" : "false", + "post.logout.redirect.uris" : "oc://ios.opencloud.eu", + "backchannel.logout.revoke.offline.tokens" : "false", + "saml.server.signature" : "false", + "saml.server.signature.keyinfo.ext" : "false", + "exclude.session.state.from.auth.response" : "false", + "realm_client" : "false", + "backchannel.logout.session.required" : "true", + "client_credentials.use_refresh_token" : "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, + "defaultClientScopes" : [ "web-origins", "profile", "roles", "groups", "OpenCloudUnique_ID", "basic", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] + }, { + "id" : "294b6cf4-b646-4f6c-bab2-616546ec3167", + "clientId" : "_system", + "name" : "_system", + "surrogateAuthRequired" : false, + "enabled" : true, + "alwaysDisplayInConsole" : false, + "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" : { + "realm_client" : "false", + "client.secret.creation.time" : "1718778122", + "post.logout.redirect.uris" : "+" + }, + "authenticationFlowBindingOverrides" : { }, + "fullScopeAllowed" : false, + "nodeReRegistrationTimeout" : 0, + "defaultClientScopes" : [ "web-origins", "profile", "roles", "basic", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] + }, { + "id" : "9850adad-7910-4b67-a790-da6444361618", + "clientId" : "account", + "name" : "${client_account}", + "rootUrl" : "${authBaseUrl}", + "baseUrl" : "/realms/openCloud/account/", + "surrogateAuthRequired" : false, + "enabled" : true, + "alwaysDisplayInConsole" : false, + "clientAuthenticatorType" : "client-secret", + "secret" : "**********", + "redirectUris" : [ "/realms/openCloud/account/*" ], + "webOrigins" : [ ], + "notBefore" : 0, + "bearerOnly" : false, + "consentRequired" : false, + "standardFlowEnabled" : true, + "implicitFlowEnabled" : false, + "directAccessGrantsEnabled" : false, + "serviceAccountsEnabled" : false, + "publicClient" : false, + "frontchannelLogout" : false, + "protocol" : "openid-connect", + "attributes" : { + "realm_client" : "false", + "client.secret.creation.time" : "1718778122", + "post.logout.redirect.uris" : "+" + }, + "authenticationFlowBindingOverrides" : { }, + "fullScopeAllowed" : false, + "nodeReRegistrationTimeout" : 0, + "defaultClientScopes" : [ "basic" ], + "optionalClientScopes" : [ ] + }, { + "id" : "55bb4cdc-045b-422a-8830-61245949d6aa", + "clientId" : "account-console", + "name" : "${client_account-console}", + "rootUrl" : "${authBaseUrl}", + "baseUrl" : "/realms/openCloud/account/", + "surrogateAuthRequired" : false, + "enabled" : true, + "alwaysDisplayInConsole" : false, + "clientAuthenticatorType" : "client-secret", + "redirectUris" : [ "/realms/openCloud/account/*" ], + "webOrigins" : [ ], + "notBefore" : 0, + "bearerOnly" : false, + "consentRequired" : false, + "standardFlowEnabled" : true, + "implicitFlowEnabled" : false, + "directAccessGrantsEnabled" : false, + "serviceAccountsEnabled" : false, + "publicClient" : true, + "frontchannelLogout" : false, + "protocol" : "openid-connect", + "attributes" : { + "realm_client" : "false", + "post.logout.redirect.uris" : "+", + "pkce.code.challenge.method" : "S256" + }, + "authenticationFlowBindingOverrides" : { }, + "fullScopeAllowed" : false, + "nodeReRegistrationTimeout" : 0, + "protocolMappers" : [ { + "id" : "9bf413ed-402f-438d-a72c-033f3c45dab2", + "name" : "audience resolve", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-audience-resolve-mapper", + "consentRequired" : false, + "config" : { } + } ], + "defaultClientScopes" : [ "web-origins", "acr", "profile", "roles", "basic", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] + }, { + "id" : "2969b8ff-2ab3-4907-aaa7-091a7a627ccb", + "clientId" : "admin-cli", + "name" : "${client_admin-cli}", + "surrogateAuthRequired" : false, + "enabled" : true, + "alwaysDisplayInConsole" : false, + "clientAuthenticatorType" : "client-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" : { + "realm_client" : "false", + "client.use.lightweight.access.token.enabled" : "true", + "post.logout.redirect.uris" : "+" + }, + "authenticationFlowBindingOverrides" : { }, + "fullScopeAllowed" : true, + "nodeReRegistrationTimeout" : 0, + "defaultClientScopes" : [ "basic" ], + "optionalClientScopes" : [ ] + }, { + "id" : "002faf0a-716c-4230-81c7-ce22d1eb832c", + "clientId" : "broker", + "name" : "${client_broker}", + "surrogateAuthRequired" : false, + "enabled" : true, + "alwaysDisplayInConsole" : false, + "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" : { + "realm_client" : "true", + "client.secret.creation.time" : "1718778122", + "post.logout.redirect.uris" : "+" + }, + "authenticationFlowBindingOverrides" : { }, + "fullScopeAllowed" : false, + "nodeReRegistrationTimeout" : 0, + "defaultClientScopes" : [ "basic" ], + "optionalClientScopes" : [ ] + }, { + "id" : "7848ee94-cc9b-40db-946f-a86ac73dc9b7", + "clientId" : "realm-management", + "name" : "${client_realm-management}", + "surrogateAuthRequired" : false, + "enabled" : true, + "alwaysDisplayInConsole" : false, + "clientAuthenticatorType" : "client-secret", + "redirectUris" : [ ], + "webOrigins" : [ ], + "notBefore" : 0, + "bearerOnly" : true, + "consentRequired" : false, + "standardFlowEnabled" : true, + "implicitFlowEnabled" : false, + "directAccessGrantsEnabled" : false, + "serviceAccountsEnabled" : false, + "publicClient" : false, + "frontchannelLogout" : false, + "protocol" : "openid-connect", + "attributes" : { + "realm_client" : "true", + "post.logout.redirect.uris" : "+" + }, + "authenticationFlowBindingOverrides" : { }, + "fullScopeAllowed" : false, + "nodeReRegistrationTimeout" : 0, + "defaultClientScopes" : [ ], + "optionalClientScopes" : [ ] + }, { + "id" : "97264f49-a8c1-4585-99b6-e706339c62f8", + "clientId" : "security-admin-console", + "name" : "${client_security-admin-console}", + "rootUrl" : "${authAdminUrl}", + "baseUrl" : "/admin/openCloud/console/", + "surrogateAuthRequired" : false, + "enabled" : true, + "alwaysDisplayInConsole" : false, + "clientAuthenticatorType" : "client-secret", + "redirectUris" : [ "/admin/openCloud/console/*" ], + "webOrigins" : [ "+" ], + "notBefore" : 0, + "bearerOnly" : false, + "consentRequired" : false, + "standardFlowEnabled" : true, + "implicitFlowEnabled" : false, + "directAccessGrantsEnabled" : false, + "serviceAccountsEnabled" : false, + "publicClient" : true, + "frontchannelLogout" : false, + "protocol" : "openid-connect", + "attributes" : { + "realm_client" : "false", + "client.use.lightweight.access.token.enabled" : "true", + "post.logout.redirect.uris" : "+", + "pkce.code.challenge.method" : "S256" + }, + "authenticationFlowBindingOverrides" : { }, + "fullScopeAllowed" : true, + "nodeReRegistrationTimeout" : 0, + "protocolMappers" : [ { + "id" : "96092024-21dd-4d31-a004-2c5b96031da3", + "name" : "locale", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "user.attribute" : "locale", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "locale", + "jsonType.label" : "String", + "userinfo.token.claim" : "true" + } + } ], + "defaultClientScopes" : [ "basic" ], + "optionalClientScopes" : [ ] + }, { + "id" : "54b18eca-cf79-4263-9db9-2d79f8a1c831", + "clientId" : "web", + "name" : "OpenCloud Web App", + "description" : "", + "rootUrl" : "https://cloud.opencloud.test", + "adminUrl" : "https://cloud.opencloud.test", + "baseUrl" : "", + "surrogateAuthRequired" : false, + "enabled" : true, + "alwaysDisplayInConsole" : false, + "clientAuthenticatorType" : "client-secret", + "redirectUris" : [ "https://cloud.opencloud.test/", "https://cloud.opencloud.test/oidc-silent-redirect.html", "https://cloud.opencloud.test/oidc-callback.html" ], + "webOrigins" : [ "https://cloud.opencloud.test" ], + "notBefore" : 0, + "bearerOnly" : false, + "consentRequired" : false, + "standardFlowEnabled" : true, + "implicitFlowEnabled" : false, + "directAccessGrantsEnabled" : true, + "serviceAccountsEnabled" : false, + "publicClient" : true, + "frontchannelLogout" : false, + "protocol" : "openid-connect", + "attributes" : { + "saml.assertion.signature" : "false", + "saml.force.post.binding" : "false", + "saml.multivalued.roles" : "false", + "saml.encrypt" : "false", + "post.logout.redirect.uris" : "+", + "oauth2.device.authorization.grant.enabled" : "false", + "backchannel.logout.revoke.offline.tokens" : "false", + "saml.server.signature" : "false", + "saml.server.signature.keyinfo.ext" : "false", + "exclude.session.state.from.auth.response" : "false", + "realm_client" : "false", + "oidc.ciba.grant.enabled" : "false", + "backchannel.logout.session.required" : "true", + "backchannel.logout.url" : "https://cloud.opencloud.test/backchannel_logout", + "client_credentials.use_refresh_token" : "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, + "defaultClientScopes" : [ "web-origins", "profile", "roles", "tenantid", "groups", "OpenCloudUnique_ID", "basic", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] + } ], + "clientScopes" : [ { + "id" : "258e56a8-1eeb-49ea-957b-aff8df4656ba", + "name" : "email", + "description" : "OpenID Connect built-in scope: email", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "true", + "consent.screen.text" : "${emailScopeConsentText}", + "display.on.consent.screen" : "true" + }, + "protocolMappers" : [ { + "id" : "068bcfb6-4a17-4c20-b083-ae542a7f76c8", + "name" : "email verified", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-property-mapper", + "consentRequired" : false, + "config" : { + "user.attribute" : "emailVerified", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "email_verified", + "jsonType.label" : "boolean", + "userinfo.token.claim" : "true" + } + }, { + "id" : "c00d6c21-2fd1-435f-9ee9-87e011048cbe", + "name" : "email", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-property-mapper", + "consentRequired" : false, + "config" : { + "user.attribute" : "email", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "email", + "jsonType.label" : "String", + "userinfo.token.claim" : "true" + } + } ] + }, { + "id" : "b3e1e47e-3912-4b55-ba89-b0198e767682", + "name" : "address", + "description" : "OpenID Connect built-in scope: address", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "true", + "consent.screen.text" : "${addressScopeConsentText}", + "display.on.consent.screen" : "true" + }, + "protocolMappers" : [ { + "id" : "876baab9-39d1-4845-abb4-561a58aa152d", + "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" : "9cae7ced-e7d9-4f7b-8e54-7402125f6ead", + "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" : "79713daf-89ca-4ed4-ad97-a88b13ee9a18", + "name" : "phone", + "description" : "OpenID Connect built-in scope: phone", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "true", + "consent.screen.text" : "${phoneScopeConsentText}", + "display.on.consent.screen" : "true" + }, + "protocolMappers" : [ { + "id" : "b5f4f5ed-1008-42ba-8b3b-7d8851a2a680", + "name" : "phone number", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "user.attribute" : "phoneNumber", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "phone_number", + "jsonType.label" : "String", + "userinfo.token.claim" : "true" + } + }, { + "id" : "08a246f1-2b4c-4def-af5c-aefc31b4820d", + "name" : "phone number verified", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "user.attribute" : "phoneNumberVerified", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "phone_number_verified", + "jsonType.label" : "boolean", + "userinfo.token.claim" : "true" + } + } ] + }, { + "id" : "ad7d7bed-d74d-4582-8bae-c9ef9b02f717", + "name" : "tenantid", + "description" : "", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "true", + "display.on.consent.screen" : "true", + "gui.order" : "", + "consent.screen.text" : "" + }, + "protocolMappers" : [ { + "id" : "22ff6103-80bf-40a5-a557-262e341b5e57", + "name" : "tenantid", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "tenantId", + "id.token.claim" : "true", + "lightweight.claim" : "false", + "access.token.claim" : "true", + "claim.name" : "tenantid", + "jsonType.label" : "String" + } + } ] + }, { + "id" : "5ce87358-3bca-4874-a6f0-6dccae6209a8", + "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", + "consent.screen.text" : "", + "display.on.consent.screen" : "false" + }, + "protocolMappers" : [ { + "id" : "bbd23c51-918d-4ea6-9ac0-db68b512fb0a", + "name" : "allowed web origins", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-allowed-origins-mapper", + "consentRequired" : false, + "config" : { } + } ] + }, { + "id" : "bdb3e320-76c8-4ad7-9d0f-a08efc060101", + "name" : "microprofile-jwt", + "description" : "Microprofile - JWT built-in scope", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "true", + "display.on.consent.screen" : "false" + }, + "protocolMappers" : [ { + "id" : "1d08316c-493b-42ab-afa3-66f621860661", + "name" : "groups", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-realm-role-mapper", + "consentRequired" : false, + "config" : { + "multivalued" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "foo", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "groups", + "jsonType.label" : "String" + } + }, { + "id" : "52061d2d-7a41-4f1d-ba1b-3c4a53e739e4", + "name" : "upn", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-property-mapper", + "consentRequired" : false, + "config" : { + "user.attribute" : "username", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "upn", + "jsonType.label" : "String", + "userinfo.token.claim" : "true" + } + } ] + }, { + "id" : "57eeeee9-99fd-4ac0-b997-87b006989486", + "name" : "service_account", + "description" : "Specific scope for a client enabled for service accounts", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "false", + "display.on.consent.screen" : "false" + }, + "protocolMappers" : [ { + "id" : "9891dc9a-c843-4f4d-968d-0d7866f6c572", + "name" : "Client Host", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usersessionmodel-note-mapper", + "consentRequired" : false, + "config" : { + "user.session.note" : "clientHost", + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "clientHost", + "jsonType.label" : "String" + } + }, { + "id" : "63be4abb-12ac-43dd-b6a0-6d8f7be5b2b1", + "name" : "Client ID", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usersessionmodel-note-mapper", + "consentRequired" : false, + "config" : { + "user.session.note" : "client_id", + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "client_id", + "jsonType.label" : "String" + } + }, { + "id" : "789cb39b-848a-40b4-9ba5-269e3154c99e", + "name" : "Client IP Address", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usersessionmodel-note-mapper", + "consentRequired" : false, + "config" : { + "user.session.note" : "clientAddress", + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "clientAddress", + "jsonType.label" : "String" + } + } ] + }, { + "id" : "d68b434c-c589-45d4-ada1-5370b7e65cae", + "name" : "OpenCloudUnique_ID", + "description" : "Id to be used in OpenCloud and Keycloak", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "false", + "display.on.consent.screen" : "false", + "gui.order" : "", + "consent.screen.text" : "" + }, + "protocolMappers" : [ { + "id" : "d84f29ea-54d4-477e-9b36-cf5d7bec8f0c", + "name" : "Unique ID", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "uuid", + "id.token.claim" : "true", + "lightweight.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "uuid", + "jsonType.label" : "String" + } + } ] + }, { + "id" : "8eb1f69b-b941-4185-bca1-f916953f7cf5", + "name" : "role_list", + "description" : "SAML role list", + "protocol" : "saml", + "attributes" : { + "consent.screen.text" : "${samlRoleListScopeConsentText}", + "display.on.consent.screen" : "true" + }, + "protocolMappers" : [ { + "id" : "fb587847-806f-4443-bab0-501efc0f0b46", + "name" : "role list", + "protocol" : "saml", + "protocolMapper" : "saml-role-list-mapper", + "consentRequired" : false, + "config" : { + "single" : "false", + "attribute.nameformat" : "Basic", + "attribute.name" : "Role" + } + } ] + }, { + "id" : "947da1ff-f614-48fc-9ecb-c98cbcfd3390", + "name" : "profile", + "description" : "OpenID Connect built-in scope: profile", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "true", + "consent.screen.text" : "${profileScopeConsentText}", + "display.on.consent.screen" : "true" + }, + "protocolMappers" : [ { + "id" : "46fec552-2f92-408a-84cf-ba98bf8e35fd", + "name" : "family name", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-property-mapper", + "consentRequired" : false, + "config" : { + "user.attribute" : "lastName", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "family_name", + "jsonType.label" : "String", + "userinfo.token.claim" : "true" + } + }, { + "id" : "c7ed5458-4d32-423e-8ea1-d112c45045d4", + "name" : "middle name", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "user.attribute" : "middleName", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "middle_name", + "jsonType.label" : "String", + "userinfo.token.claim" : "true" + } + }, { + "id" : "e18d1ce4-3969-4ec1-9941-a27fd7555245", + "name" : "picture", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "user.attribute" : "picture", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "picture", + "jsonType.label" : "String", + "userinfo.token.claim" : "true" + } + }, { + "id" : "dab85a5e-9af8-4fcd-88e4-9d3ae50dd5b6", + "name" : "locale", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "user.attribute" : "locale", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "locale", + "jsonType.label" : "String", + "userinfo.token.claim" : "true" + } + }, { + "id" : "7484f47e-3bb1-48d0-ba64-e8330dcefe6e", + "name" : "profile", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "user.attribute" : "profile", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "profile", + "jsonType.label" : "String", + "userinfo.token.claim" : "true" + } + }, { + "id" : "fcd00995-9693-4803-8f41-c84044be83ed", + "name" : "website", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "user.attribute" : "website", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "website", + "jsonType.label" : "String", + "userinfo.token.claim" : "true" + } + }, { + "id" : "f09e7268-5284-449b-849b-cf8225523584", + "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" : "0317f4b3-3f7b-47ab-88d3-5d6f604d944d", + "name" : "nickname", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "user.attribute" : "nickname", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "nickname", + "jsonType.label" : "String", + "userinfo.token.claim" : "true" + } + }, { + "id" : "db81244c-e739-461b-8822-52ceaa11bdf4", + "name" : "updated at", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "user.attribute" : "updatedAt", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "updated_at", + "jsonType.label" : "String", + "userinfo.token.claim" : "true" + } + }, { + "id" : "c6a16bf9-9370-4dff-a718-be53131bb238", + "name" : "gender", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "user.attribute" : "gender", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "gender", + "jsonType.label" : "String", + "userinfo.token.claim" : "true" + } + }, { + "id" : "32d76647-b542-484c-9062-edc34eb350e0", + "name" : "birthdate", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "user.attribute" : "birthdate", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "birthdate", + "jsonType.label" : "String", + "userinfo.token.claim" : "true" + } + }, { + "id" : "ac6530db-6463-446b-99da-32d5298b5fa0", + "name" : "zoneinfo", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "user.attribute" : "zoneinfo", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "zoneinfo", + "jsonType.label" : "String", + "userinfo.token.claim" : "true" + } + }, { + "id" : "ed10983b-8700-415e-933e-226ce3f397a6", + "name" : "given name", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-property-mapper", + "consentRequired" : false, + "config" : { + "user.attribute" : "firstName", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "given_name", + "jsonType.label" : "String", + "userinfo.token.claim" : "true" + } + }, { + "id" : "8205ccd0-1266-4060-b5df-3a6eb229d91e", + "name" : "username", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-property-mapper", + "consentRequired" : false, + "config" : { + "user.attribute" : "username", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "preferred_username", + "jsonType.label" : "String", + "userinfo.token.claim" : "true" + } + } ] + }, { + "id" : "c3a6224b-49aa-4a25-953d-7e326d66893d", + "name" : "basic", + "description" : "OpenID Connect scope for add all basic claims to the token", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "false", + "display.on.consent.screen" : "false" + }, + "protocolMappers" : [ { + "id" : "2d4f3f17-1ab7-429e-88e1-cdf08d3533c6", + "name" : "auth_time", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usersessionmodel-note-mapper", + "consentRequired" : false, + "config" : { + "user.session.note" : "AUTH_TIME", + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "auth_time", + "jsonType.label" : "long" + } + }, { + "id" : "3e7da934-3de3-4bd1-a565-8ac62419c138", + "name" : "sub", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-sub-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "access.token.claim" : "true" + } + } ] + }, { + "id" : "0c72b80b-28d5-48d8-b593-c99030aab58d", + "name" : "roles", + "description" : "OpenID Connect scope for add user roles to the access token", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "false", + "consent.screen.text" : "${rolesScopeConsentText}", + "display.on.consent.screen" : "true" + }, + "protocolMappers" : [ { + "id" : "bc7f015e-329f-4e99-be6b-72382f4310c7", + "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" : "215f645f-ad0b-4523-9ece-f09f69ead5c4", + "name" : "audience resolve", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-audience-resolve-mapper", + "consentRequired" : false, + "config" : { } + }, { + "id" : "4a10b958-d34d-413a-b349-1415d02cdcde", + "name" : "realm roles", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-realm-role-mapper", + "consentRequired" : false, + "config" : { + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "roles", + "jsonType.label" : "String", + "userinfo.token.claim" : "true", + "multivalued" : "true" + } + } ] + }, { + "id" : "7438d93e-b07a-4913-9419-3273be364c4b", + "name" : "groups", + "description" : "OpenID Connect scope for add user groups to the access token", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "false", + "display.on.consent.screen" : "true", + "gui.order" : "", + "consent.screen.text" : "" + }, + "protocolMappers" : [ { + "id" : "5349faf2-64a6-481f-b207-39ffef2cd597", + "name" : "groups", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-group-membership-mapper", + "consentRequired" : false, + "config" : { + "full.path" : "false", + "introspection.token.claim" : "true", + "multivalued" : "true", + "userinfo.token.claim" : "true", + "id.token.claim" : "true", + "lightweight.claim" : "false", + "access.token.claim" : "true", + "claim.name" : "groups" + } + } ] + }, { + "id" : "86883395-e439-4cab-9d8d-31d71389969c", + "name" : "acr", + "description" : "OpenID Connect scope for add acr (authentication context class reference) to the token", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "false", + "display.on.consent.screen" : "false" + }, + "protocolMappers" : [ { + "id" : "b849b14b-7c9c-4b7b-9329-c56debefb47c", + "name" : "acr loa level", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-acr-mapper", + "consentRequired" : false, + "config" : { + "id.token.claim" : "true", + "access.token.claim" : "true", + "userinfo.token.claim" : "true" + } + } ] + } ], + "defaultDefaultClientScopes" : [ "role_list", "profile", "email", "roles", "web-origins", "acr", "basic", "groups", "OpenCloudUnique_ID", "tenantid" ], + "defaultOptionalClientScopes" : [ "offline_access", "address", "phone", "microprofile-jwt" ], + "browserSecurityHeaders" : { + "contentSecurityPolicyReportOnly" : "", + "xContentTypeOptions" : "nosniff", + "referrerPolicy" : "no-referrer", + "xRobotsTag" : "none", + "xFrameOptions" : "SAMEORIGIN", + "contentSecurityPolicy" : "frame-src 'self'; frame-ancestors 'self'; object-src 'none';", + "xXSSProtection" : "1; mode=block", + "strictTransportSecurity" : "max-age=31536000; includeSubDomains" + }, + "smtpServer" : { }, + "loginTheme" : "opencloud", + "eventsEnabled" : false, + "eventsListeners" : [ "jboss-logging" ], + "enabledEventTypes" : [ "SEND_RESET_PASSWORD", "UPDATE_CONSENT_ERROR", "GRANT_CONSENT", "VERIFY_PROFILE_ERROR", "REMOVE_TOTP", "REVOKE_GRANT", "UPDATE_TOTP", "LOGIN_ERROR", "CLIENT_LOGIN", "RESET_PASSWORD_ERROR", "IMPERSONATE_ERROR", "CODE_TO_TOKEN_ERROR", "CUSTOM_REQUIRED_ACTION", "OAUTH2_DEVICE_CODE_TO_TOKEN_ERROR", "RESTART_AUTHENTICATION", "IMPERSONATE", "UPDATE_PROFILE_ERROR", "LOGIN", "OAUTH2_DEVICE_VERIFY_USER_CODE", "UPDATE_PASSWORD_ERROR", "CLIENT_INITIATED_ACCOUNT_LINKING", "OAUTH2_EXTENSION_GRANT", "USER_DISABLED_BY_PERMANENT_LOCKOUT", "TOKEN_EXCHANGE", "AUTHREQID_TO_TOKEN", "LOGOUT", "REGISTER", "DELETE_ACCOUNT_ERROR", "CLIENT_REGISTER", "IDENTITY_PROVIDER_LINK_ACCOUNT", "USER_DISABLED_BY_TEMPORARY_LOCKOUT", "DELETE_ACCOUNT", "UPDATE_PASSWORD", "CLIENT_DELETE", "FEDERATED_IDENTITY_LINK_ERROR", "IDENTITY_PROVIDER_FIRST_LOGIN", "CLIENT_DELETE_ERROR", "VERIFY_EMAIL", "CLIENT_LOGIN_ERROR", "RESTART_AUTHENTICATION_ERROR", "EXECUTE_ACTIONS", "REMOVE_FEDERATED_IDENTITY_ERROR", "TOKEN_EXCHANGE_ERROR", "PERMISSION_TOKEN", "FEDERATED_IDENTITY_OVERRIDE_LINK", "SEND_IDENTITY_PROVIDER_LINK_ERROR", "EXECUTE_ACTION_TOKEN_ERROR", "OAUTH2_EXTENSION_GRANT_ERROR", "SEND_VERIFY_EMAIL", "OAUTH2_DEVICE_AUTH", "EXECUTE_ACTIONS_ERROR", "REMOVE_FEDERATED_IDENTITY", "OAUTH2_DEVICE_CODE_TO_TOKEN", "IDENTITY_PROVIDER_POST_LOGIN", "IDENTITY_PROVIDER_LINK_ACCOUNT_ERROR", "FEDERATED_IDENTITY_OVERRIDE_LINK_ERROR", "OAUTH2_DEVICE_VERIFY_USER_CODE_ERROR", "UPDATE_EMAIL", "REGISTER_ERROR", "REVOKE_GRANT_ERROR", "EXECUTE_ACTION_TOKEN", "LOGOUT_ERROR", "UPDATE_EMAIL_ERROR", "CLIENT_UPDATE_ERROR", "AUTHREQID_TO_TOKEN_ERROR", "INVITE_ORG_ERROR", "UPDATE_PROFILE", "CLIENT_REGISTER_ERROR", "FEDERATED_IDENTITY_LINK", "INVITE_ORG", "SEND_IDENTITY_PROVIDER_LINK", "SEND_VERIFY_EMAIL_ERROR", "RESET_PASSWORD", "CLIENT_INITIATED_ACCOUNT_LINKING_ERROR", "OAUTH2_DEVICE_AUTH_ERROR", "UPDATE_CONSENT", "REMOVE_TOTP_ERROR", "VERIFY_EMAIL_ERROR", "SEND_RESET_PASSWORD_ERROR", "CLIENT_UPDATE", "CUSTOM_REQUIRED_ACTION_ERROR", "IDENTITY_PROVIDER_POST_LOGIN_ERROR", "UPDATE_TOTP_ERROR", "CODE_TO_TOKEN", "VERIFY_PROFILE", "GRANT_CONSENT_ERROR", "IDENTITY_PROVIDER_FIRST_LOGIN_ERROR" ], + "adminEventsEnabled" : false, + "adminEventsDetailsEnabled" : false, + "identityProviders" : [ ], + "identityProviderMappers" : [ ], + "components" : { + "org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy" : [ { + "id" : "4682fe74-f3a9-445a-a7ab-557fb532fe6b", + "name" : "Consent Required", + "providerId" : "consent-required", + "subType" : "anonymous", + "subComponents" : { }, + "config" : { } + }, { + "id" : "c46009e5-c8b5-4051-bf7f-7b1481a9aa86", + "name" : "Max Clients Limit", + "providerId" : "max-clients", + "subType" : "anonymous", + "subComponents" : { }, + "config" : { + "max-clients" : [ "200" ] + } + }, { + "id" : "43edf979-28d2-46c8-9f93-48b3de185570", + "name" : "Allowed Protocol Mapper Types", + "providerId" : "allowed-protocol-mappers", + "subType" : "anonymous", + "subComponents" : { }, + "config" : { + "allowed-protocol-mapper-types" : [ "saml-user-property-mapper", "saml-user-attribute-mapper", "oidc-usermodel-property-mapper", "oidc-sha256-pairwise-sub-mapper", "oidc-address-mapper", "oidc-usermodel-attribute-mapper", "oidc-full-name-mapper", "saml-role-list-mapper" ] + } + }, { + "id" : "6fc7d765-7da8-4985-ba0b-e83827b04bd3", + "name" : "Allowed Client Scopes", + "providerId" : "allowed-client-templates", + "subType" : "anonymous", + "subComponents" : { }, + "config" : { + "allow-default-scopes" : [ "true" ] + } + }, { + "id" : "5a9aef85-98a6-4e90-b30f-8aa715e1f5e6", + "name" : "Allowed Protocol Mapper Types", + "providerId" : "allowed-protocol-mappers", + "subType" : "authenticated", + "subComponents" : { }, + "config" : { + "allowed-protocol-mapper-types" : [ "saml-user-property-mapper", "oidc-sha256-pairwise-sub-mapper", "oidc-usermodel-property-mapper", "oidc-usermodel-attribute-mapper", "saml-user-attribute-mapper", "oidc-full-name-mapper", "oidc-address-mapper", "saml-role-list-mapper" ] + } + }, { + "id" : "e3eadb04-8862-4567-869c-a76485268159", + "name" : "Allowed Client Scopes", + "providerId" : "allowed-client-templates", + "subType" : "authenticated", + "subComponents" : { }, + "config" : { + "allow-default-scopes" : [ "true" ] + } + }, { + "id" : "c788e6bf-2f57-4a82-b32e-ac8d48a4f676", + "name" : "Full Scope Disabled", + "providerId" : "scope", + "subType" : "anonymous", + "subComponents" : { }, + "config" : { } + } ], + "org.keycloak.userprofile.UserProfileProvider" : [ { + "id" : "28d6b4ce-33d4-40c0-adef-b27e35b7e122", + "providerId" : "declarative-user-profile", + "subComponents" : { }, + "config" : { + "kc.user.profile.config" : [ "{\"attributes\":[{\"name\":\"username\",\"displayName\":\"${username}\",\"validations\":{\"length\":{\"min\":3,\"max\":255},\"username-prohibited-characters\":{},\"up-username-not-idn-homograph\":{}},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"multivalued\":false},{\"name\":\"email\",\"displayName\":\"${email}\",\"validations\":{\"email\":{},\"length\":{\"max\":255}},\"required\":{\"roles\":[\"user\"]},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"multivalued\":false},{\"name\":\"firstName\",\"displayName\":\"${firstName}\",\"validations\":{\"length\":{\"max\":255},\"person-name-prohibited-characters\":{}},\"required\":{\"roles\":[\"user\"]},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"multivalued\":false},{\"name\":\"lastName\",\"displayName\":\"${lastName}\",\"validations\":{\"length\":{\"max\":255},\"person-name-prohibited-characters\":{}},\"required\":{\"roles\":[\"user\"]},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"multivalued\":false},{\"name\":\"uuid\",\"displayName\":\"Unique LDAP ID\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[]},\"multivalued\":false}],\"groups\":[{\"name\":\"user-metadata\",\"displayHeader\":\"User metadata\",\"displayDescription\":\"Attributes, which refer to user metadata\"}]}" ] + } + } ], + "org.keycloak.storage.UserStorageProvider" : [ { + "id" : "91da1f3b-b9f4-4759-aed4-50124d9b9f3e", + "name" : "ldap", + "providerId" : "ldap", + "subComponents" : { + "org.keycloak.storage.ldap.mappers.LDAPStorageMapper" : [ { + "id" : "5ce17b39-efb7-45a2-b600-55c57d3a3021", + "name" : "email", + "providerId" : "user-attribute-ldap-mapper", + "subComponents" : { }, + "config" : { + "ldap.attribute" : [ "mail" ], + "is.mandatory.in.ldap" : [ "false" ], + "always.read.value.from.ldap" : [ "false" ], + "read.only" : [ "false" ], + "user.model.attribute" : [ "email" ] + } + }, { + "id" : "f913ed32-5025-4d26-9870-7dffeba1093d", + "name" : "tenantId", + "providerId" : "user-attribute-ldap-mapper", + "subComponents" : { }, + "config" : { + "ldap.attribute" : [ "opencloudMemberOfSchool" ], + "is.mandatory.in.ldap" : [ "true" ], + "attribute.force.default" : [ "true" ], + "is.binary.attribute" : [ "false" ], + "always.read.value.from.ldap" : [ "true" ], + "read.only" : [ "true" ], + "user.model.attribute" : [ "tenantId" ] + } + }, { + "id" : "7bf4374a-10d6-4192-9fb5-119ebf87f9f6", + "name" : "modify date", + "providerId" : "user-attribute-ldap-mapper", + "subComponents" : { }, + "config" : { + "ldap.attribute" : [ "modifyTimestamp" ], + "is.mandatory.in.ldap" : [ "false" ], + "read.only" : [ "true" ], + "always.read.value.from.ldap" : [ "true" ], + "user.model.attribute" : [ "modifyTimestamp" ] + } + }, { + "id" : "9fe968b5-c338-4419-93c4-f339cbad5ef8", + "name" : "username", + "providerId" : "user-attribute-ldap-mapper", + "subComponents" : { }, + "config" : { + "ldap.attribute" : [ "uid" ], + "is.mandatory.in.ldap" : [ "true" ], + "read.only" : [ "false" ], + "always.read.value.from.ldap" : [ "false" ], + "user.model.attribute" : [ "username" ] + } + }, { + "id" : "330d06e9-3e12-492e-af5e-53b1b950a122", + "name" : "uuid", + "providerId" : "user-attribute-ldap-mapper", + "subComponents" : { }, + "config" : { + "ldap.attribute" : [ "openCloudUUID" ], + "attribute.force.default" : [ "false" ], + "is.mandatory.in.ldap" : [ "false" ], + "is.binary.attribute" : [ "false" ], + "read.only" : [ "true" ], + "always.read.value.from.ldap" : [ "true" ], + "user.model.attribute" : [ "uuid" ] + } + }, { + "id" : "79e83df0-9406-4609-b78f-b27de957bc41", + "name" : "last name", + "providerId" : "user-attribute-ldap-mapper", + "subComponents" : { }, + "config" : { + "ldap.attribute" : [ "sn" ], + "is.mandatory.in.ldap" : [ "true" ], + "always.read.value.from.ldap" : [ "true" ], + "read.only" : [ "false" ], + "user.model.attribute" : [ "lastName" ] + } + }, { + "id" : "96bc2621-a714-4f15-ac1d-bc32df94382d", + "name" : "display name", + "providerId" : "full-name-ldap-mapper", + "subComponents" : { }, + "config" : { + "read.only" : [ "false" ], + "write.only" : [ "true" ], + "ldap.full.name.attribute" : [ "displayName" ] + } + }, { + "id" : "cab8b569-0f50-4e13-b2a5-d24ee513cd8b", + "name" : "first name", + "providerId" : "user-attribute-ldap-mapper", + "subComponents" : { }, + "config" : { + "ldap.attribute" : [ "cn" ], + "is.mandatory.in.ldap" : [ "true" ], + "read.only" : [ "false" ], + "always.read.value.from.ldap" : [ "true" ], + "user.model.attribute" : [ "firstName" ] + } + }, { + "id" : "30cec76e-539e-4ac4-9816-8a0ae4a49798", + "name" : "creation date", + "providerId" : "user-attribute-ldap-mapper", + "subComponents" : { }, + "config" : { + "ldap.attribute" : [ "createTimestamp" ], + "is.mandatory.in.ldap" : [ "false" ], + "always.read.value.from.ldap" : [ "true" ], + "read.only" : [ "true" ], + "user.model.attribute" : [ "createTimestamp" ] + } + } ] + }, + "config" : { + "fullSyncPeriod" : [ "86400" ], + "pagination" : [ "false" ], + "startTls" : [ "false" ], + "usersDn" : [ "ou=users,dc=opencloud,dc=eu" ], + "connectionPooling" : [ "false" ], + "cachePolicy" : [ "EVICT_DAILY" ], + "useKerberosForPasswordAuthentication" : [ "false" ], + "evictionHour" : [ "0" ], + "importEnabled" : [ "true" ], + "enabled" : [ "true" ], + "bindDn" : [ "cn=admin,dc=opencloud,dc=eu" ], + "bindCredential" : [ "admin" ], + "changedSyncPeriod" : [ "3600" ], + "usernameLDAPAttribute" : [ "uid" ], + "lastSync" : [ "1759930583" ], + "vendor" : [ "other" ], + "uuidLDAPAttribute" : [ "entryUUID" ], + "connectionUrl" : [ "ldap://ldap-server:1389" ], + "allowKerberosAuthentication" : [ "false" ], + "syncRegistrations" : [ "true" ], + "authType" : [ "simple" ], + "krbPrincipalAttribute" : [ "krb5PrincipalName" ], + "customUserSearchFilter" : [ "(objectclass=inetOrgPerson)" ], + "searchScope" : [ "1" ], + "useTruststoreSpi" : [ "always" ], + "usePasswordModifyExtendedOp" : [ "false" ], + "trustEmail" : [ "false" ], + "userObjectClasses" : [ "top,person,organizationalPerson,inetOrgPerson" ], + "evictionMinute" : [ "0" ], + "rdnLDAPAttribute" : [ "uid" ], + "editMode" : [ "WRITABLE" ], + "validatePasswordPolicy" : [ "false" ] + } + } ], + "org.keycloak.keys.KeyProvider" : [ { + "id" : "0e3d0048-cb16-49c3-8a9a-05d83f0daeca", + "name" : "rsa-generated", + "providerId" : "rsa-generated", + "subComponents" : { }, + "config" : { + "privateKey" : [ "MIIEogIBAAKCAQEA08cSBcaKZRgS0uPFg3G0Mz17lmFzhgWR3DSbcDWhEIT5aYCJlALFT/7WDAc/x/xyYAh0yjp8iEEWkCPZY0Im8ORsEz1rmobO6qRjuHA41fXClz0S8PpqB8TsVgHGThCAYOkQHOfikfiDgaBxvgXokYJzYk1m9ULS/VH5SiO2ycVQQa/i7pgF1UQ/kX8AV3gqjmZI8qIp8HvaK33c6Za5gP0YROVG3JN/6CgWqmjitLeY2bItXIdjYoh2u19H5hsh3OxmfRBYzDQ2L/nU4u2/2lViNPOwCnmFQL1twKrSbSeUBQhQPmLmaixVJ/oePlWNhiTCFT+x0Dd8tqJuy0lh8wIDAQABAoIBAAYiG8mDCHsJCXKGGjBGWLurKisew2O8QY8pDt9fDj0WPMrraBCbS++XJ8fNZfF6xC030XoR241upSKjhyYywkHzu5WLD9S31sJA5Ipw5ZR8u5f5stS7y9pRv651+rJEFBO6bDcKGCu2EMjuRp0EOsoMBx9vsm2j88xWVWvqQN7zBG2uMVvISkPjLLhXTT1kkG1AtWmqxHqAhwGeXsdWIXQPs86sTWDYjnpYsvpOalps8L24fRCJ4Ko5j3wGEnfRgfD/lJjBtcqcWbeGeJd+GseP4ZiWXvrAt9cAcUlA2XdkYNKcDMWSe1aROsr70Kub6cGjY5zyRXIrfBEzx+MfvrECgYEA+POxWYuxFrYV3lEthBVTpSjy6I8lqtb/I4IYK5PJVQjTd91YsptXM1cKzWc4VAGQf7qdCsLT6Bx8gsTjVnn3egVKiPYJxI2fZAVWmVTUSIjk2wd7Qy2DfmUZqSl/yhni3vR/JPHrhhvV3aDHIrkKLygNUT1YLHrCmjGXI2BY75UCgYEA2cXz4EBrpf5Ypl/gg3K5uKzIh7j5pPbSkBEReYjvAXFDRXIODkzYClq7/qx6HL+oJ5li/Tw85UZJOifo29tIYAS/DcTCQafEhhyVup2myEpWNJGOEHRV+wbDSm5qmScMkZfWIi2LfDgkG8gg9JWrwff3+un+ZbZxnaOMF4CRyWcCgYAkQhHdcdv7688pjvcrGpQRJPKj7l1VEXUdbdApYJ/dE6kA9yr48Gj14EKBgfwROB0cjUhcDd4hGy0DOrvFl4P0sJqXcgYgK2RAhAqF0HYOjmmOgPtx4EobeYwQOCwVekmxoTh3YAimgp1WZxk5tGuITLQL1fxmlgaLwJc7nse1NQKBgDPEfbuyNKTJ+JLcOceljwaadDTnQNj5H4bFt+y3I3GpVf99JGA73iNf/E6G+6Q33yZCrShbmHI8Kg4cDJuu3d6NYklIm5D1656hzuoEre/w09s9OpMoxIOe0O+xnb94a+F0QZ/wSbcmtVaxVTwvFBxQ8O8Bjsro+sspZJT1qDJDAoGASBrXDqiP1gPXIDtdIpK/HH7UEG52WrLfFO2nZxJMN13to2HEq9D4vTHz4qtgKWyvrm9khfKK/j8c6YBmJChypJ+M+0qoutXKkLZveqn56e9v1CtgDx9fkMxYxwY13vEPy/ArJQ6Q0QSKRNP/4Is6kqaGPelfr3TL1ddoQIXODic=" ], + "certificate" : [ "MIICoTCCAYkCBgGZw0iH2TANBgkqhkiG9w0BAQsFADAUMRIwEAYDVQQDDAlvcGVuQ2xvdWQwHhcNMjUxMDA4MTAwMzU0WhcNMzUxMDA4MTAwNTM0WjAUMRIwEAYDVQQDDAlvcGVuQ2xvdWQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDTxxIFxoplGBLS48WDcbQzPXuWYXOGBZHcNJtwNaEQhPlpgImUAsVP/tYMBz/H/HJgCHTKOnyIQRaQI9ljQibw5GwTPWuahs7qpGO4cDjV9cKXPRLw+moHxOxWAcZOEIBg6RAc5+KR+IOBoHG+BeiRgnNiTWb1QtL9UflKI7bJxVBBr+LumAXVRD+RfwBXeCqOZkjyoinwe9orfdzplrmA/RhE5Ubck3/oKBaqaOK0t5jZsi1ch2NiiHa7X0fmGyHc7GZ9EFjMNDYv+dTi7b/aVWI087AKeYVAvW3AqtJtJ5QFCFA+YuZqLFUn+h4+VY2GJMIVP7HQN3y2om7LSWHzAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAF0MZ4/8JtCfBGcCGx4ak2mEZQiUTfMlfIRMS4eK3z52Hv6BBidEIg3KN0SHpYa5buTNGwRvrY1zrC5kNU1QJmYxykV50h9M1+QyXaX9HDfj0z5nbsLg8gSPC1vnp8SaBkRqHYvMWHWT9n1aPK+wfl2Yv5DPeL12oL+HqQNS0lkm10/1JLlWUgq4R3zF85M+5TOyLN8PSZuO9dqpChtOgY1xJbzpyZ9suGC9dd5mkK6Je4MA0oHBO4ABzAFICZMRAgnMoz7VA7rLGEgAoWiCei5b8a675XVwiSioH/b/6asmA+ItCnTe5nTKrBldYRgE90BqGPyB2bTf62Wmcml9MjU=" ], + "priority" : [ "100" ] + } + }, { + "id" : "f92ecf31-c3c7-4c3b-af20-839fc05bcf99", + "name" : "hmac-generated", + "providerId" : "hmac-generated", + "subComponents" : { }, + "config" : { + "kid" : [ "1dad7095-af6e-4e90-8f25-1d76f021ac06" ], + "secret" : [ "aY4Yn4iSA7Yc_njM0iouDg_6b3M-NMPiAytkS1ZsLBFJIHNyyF684u9U5mW_lJ_C-BFBJYiJg5_af9ix9KKYwE1RVSABtBDLItKB5v5WX8ypLj4ViNXHJLvbqgBf382bI4IfH3ZoJZ8kr2t7nPDHymKVInC9MPiL0Ub9K39I8bw" ], + "priority" : [ "100" ], + "algorithm" : [ "HS256" ] + } + }, { + "id" : "a137a686-5876-4faf-8d1e-e3a59f55095e", + "name" : "hmac-generated-hs512", + "providerId" : "hmac-generated", + "subComponents" : { }, + "config" : { + "kid" : [ "97869da6-f625-4e74-90c9-f86586844bc5" ], + "secret" : [ "5uKd4nFtOsdqqIDe2x5qSDq9klHWYAHZb75YP3sEsmWmn4uGt0Y9j7fVaw5XpY_9sE1H9OFiBPSMme0jmDPuhhjhdzYrf8RvoT4hPJC1bx8K6K8wcbWeJHYNwurFQPZFiKTVJN4U_SeXcO0RgGsma7jq7pUMGuiikygMc7jwKDE" ], + "priority" : [ "100" ], + "algorithm" : [ "HS512" ] + } + }, { + "id" : "992dcc80-dc41-4b00-bab8-6ec1c839f3a4", + "name" : "aes-generated", + "providerId" : "aes-generated", + "subComponents" : { }, + "config" : { + "kid" : [ "bf83b483-9971-4d99-ba41-a3e6487d5c2f" ], + "secret" : [ "1Ku-5g39B0XOFE9XRCQqsw" ], + "priority" : [ "100" ] + } + } ] + }, + "internationalizationEnabled" : false, + "authenticationFlows" : [ { + "id" : "8964f931-b866-4a05-ab1c-89331a566887", + "alias" : "Account verification options", + "description" : "Method with which to verity the existing account", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "idp-email-verification", + "authenticatorFlow" : false, + "requirement" : "ALTERNATIVE", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticatorFlow" : true, + "requirement" : "ALTERNATIVE", + "priority" : 20, + "autheticatorFlow" : true, + "flowAlias" : "Verify Existing Account by Re-authentication", + "userSetupAllowed" : false + } ] + }, { + "id" : "123e5711-1ee5-4f7e-ac9c-64c644daaea9", + "alias" : "Browser - Conditional OTP", + "description" : "Flow to determine if the OTP is required for the authentication", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "conditional-user-configured", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "auth-otp-form", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 20, + "autheticatorFlow" : false, + "userSetupAllowed" : false + } ] + }, { + "id" : "be73b7f5-9a66-487c-b7dd-80e0f7ac0c7c", + "alias" : "Direct Grant - Conditional OTP", + "description" : "Flow to determine if the OTP is required for the authentication", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "conditional-user-configured", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "direct-grant-validate-otp", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 20, + "autheticatorFlow" : false, + "userSetupAllowed" : false + } ] + }, { + "id" : "597ca917-91fc-4898-a279-cd592af286e3", + "alias" : "First broker login - Conditional OTP", + "description" : "Flow to determine if the OTP is required for the authentication", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "conditional-user-configured", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "auth-otp-form", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 20, + "autheticatorFlow" : false, + "userSetupAllowed" : false + } ] + }, { + "id" : "3daadb6b-4d63-4be1-a89e-ec8e41e72afa", + "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", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticatorFlow" : true, + "requirement" : "REQUIRED", + "priority" : 20, + "autheticatorFlow" : true, + "flowAlias" : "Account verification options", + "userSetupAllowed" : false + } ] + }, { + "id" : "5942598c-d7e9-4941-b13e-4a8a75e2c2a3", + "alias" : "Reset - Conditional OTP", + "description" : "Flow to determine if the OTP should be reset or not. Set to REQUIRED to force.", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "conditional-user-configured", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "reset-otp", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 20, + "autheticatorFlow" : false, + "userSetupAllowed" : false + } ] + }, { + "id" : "6e4b336e-eb5f-423c-8d32-4ab94d1122e6", + "alias" : "User creation or linking", + "description" : "Flow for the existing/non-existing user alternatives", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticatorConfig" : "create unique user config", + "authenticator" : "idp-create-user-if-unique", + "authenticatorFlow" : false, + "requirement" : "ALTERNATIVE", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticatorFlow" : true, + "requirement" : "ALTERNATIVE", + "priority" : 20, + "autheticatorFlow" : true, + "flowAlias" : "Handle Existing Account", + "userSetupAllowed" : false + } ] + }, { + "id" : "35ac1997-b6af-44ff-ab27-c34f9be32e56", + "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", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticatorFlow" : true, + "requirement" : "CONDITIONAL", + "priority" : 20, + "autheticatorFlow" : true, + "flowAlias" : "First broker login - Conditional OTP", + "userSetupAllowed" : false + } ] + }, { + "id" : "a3473070-fe69-4de1-a0b2-dd54b8a769d5", + "alias" : "browser", + "description" : "browser based authentication", + "providerId" : "basic-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "auth-cookie", + "authenticatorFlow" : false, + "requirement" : "ALTERNATIVE", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "auth-spnego", + "authenticatorFlow" : false, + "requirement" : "DISABLED", + "priority" : 20, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "identity-provider-redirector", + "authenticatorFlow" : false, + "requirement" : "ALTERNATIVE", + "priority" : 25, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticatorFlow" : true, + "requirement" : "ALTERNATIVE", + "priority" : 30, + "autheticatorFlow" : true, + "flowAlias" : "forms", + "userSetupAllowed" : false + } ] + }, { + "id" : "cc714857-b114-4df6-9030-b464bbb3964d", + "alias" : "clients", + "description" : "Base authentication for clients", + "providerId" : "client-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "client-secret", + "authenticatorFlow" : false, + "requirement" : "ALTERNATIVE", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "client-jwt", + "authenticatorFlow" : false, + "requirement" : "ALTERNATIVE", + "priority" : 20, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "client-secret-jwt", + "authenticatorFlow" : false, + "requirement" : "ALTERNATIVE", + "priority" : 30, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "client-x509", + "authenticatorFlow" : false, + "requirement" : "ALTERNATIVE", + "priority" : 40, + "autheticatorFlow" : false, + "userSetupAllowed" : false + } ] + }, { + "id" : "0ebe891c-1a72-4842-bf29-a9abe9c2a4d2", + "alias" : "direct grant", + "description" : "OpenID Connect Resource Owner Grant", + "providerId" : "basic-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "direct-grant-validate-username", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "direct-grant-validate-password", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 20, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticatorFlow" : true, + "requirement" : "CONDITIONAL", + "priority" : 30, + "autheticatorFlow" : true, + "flowAlias" : "Direct Grant - Conditional OTP", + "userSetupAllowed" : false + } ] + }, { + "id" : "d97d5579-b3d4-49c4-a60e-0e1e6b1c9d79", + "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", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + } ] + }, { + "id" : "009f7c28-0f41-4237-9911-9091c3d751b7", + "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", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticatorFlow" : true, + "requirement" : "REQUIRED", + "priority" : 20, + "autheticatorFlow" : true, + "flowAlias" : "User creation or linking", + "userSetupAllowed" : false + } ] + }, { + "id" : "f9911022-b3cf-4d96-9a96-51bc53c437eb", + "alias" : "forms", + "description" : "Username, password, otp and other auth forms.", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "auth-username-password-form", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticatorFlow" : true, + "requirement" : "CONDITIONAL", + "priority" : 20, + "autheticatorFlow" : true, + "flowAlias" : "Browser - Conditional OTP", + "userSetupAllowed" : false + } ] + }, { + "id" : "c53eb19d-49e9-4252-8a10-4d5c6a12e61b", + "alias" : "registration", + "description" : "registration flow", + "providerId" : "basic-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "registration-page-form", + "authenticatorFlow" : true, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : true, + "flowAlias" : "registration form", + "userSetupAllowed" : false + } ] + }, { + "id" : "3b4f48d3-1706-4630-80e0-e0542780a1f7", + "alias" : "registration form", + "description" : "registration form", + "providerId" : "form-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "registration-user-creation", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 20, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "registration-password-action", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 50, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "registration-recaptcha-action", + "authenticatorFlow" : false, + "requirement" : "DISABLED", + "priority" : 60, + "autheticatorFlow" : false, + "userSetupAllowed" : false + } ] + }, { + "id" : "5520aa89-cd76-438a-abae-7ccd3a2d7615", + "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", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "reset-credential-email", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 20, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "reset-password", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 30, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticatorFlow" : true, + "requirement" : "CONDITIONAL", + "priority" : 40, + "autheticatorFlow" : true, + "flowAlias" : "Reset - Conditional OTP", + "userSetupAllowed" : false + } ] + }, { + "id" : "cce548d6-9bef-4449-88ea-99b949488fe7", + "alias" : "saml ecp", + "description" : "SAML ECP Profile Authentication Flow", + "providerId" : "basic-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "http-basic-authenticator", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + } ] + } ], + "authenticatorConfig" : [ { + "id" : "0848606c-7510-4b09-ba0e-4dc2ef3d63f8", + "alias" : "create unique user config", + "config" : { + "require.password.update.after.registration" : "false" + } + }, { + "id" : "91a8dee7-c679-4202-866e-234eb4164cfd", + "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" : { } + }, { + "alias" : "delete_account", + "name" : "Delete Account", + "providerId" : "delete_account", + "enabled" : false, + "defaultAction" : false, + "priority" : 60, + "config" : { } + }, { + "alias" : "delete_credential", + "name" : "Delete Credential", + "providerId" : "delete_credential", + "enabled" : true, + "defaultAction" : false, + "priority" : 100, + "config" : { } + }, { + "alias" : "idp_link", + "name" : "Linking Identity Provider", + "providerId" : "idp_link", + "enabled" : true, + "defaultAction" : false, + "priority" : 120, + "config" : { } + }, { + "alias" : "update_user_locale", + "name" : "Update User Locale", + "providerId" : "update_user_locale", + "enabled" : true, + "defaultAction" : false, + "priority" : 1000, + "config" : { } + } ], + "browserFlow" : "browser", + "registrationFlow" : "registration", + "directGrantFlow" : "direct grant", + "resetCredentialsFlow" : "reset credentials", + "clientAuthenticationFlow" : "clients", + "dockerAuthenticationFlow" : "docker auth", + "firstBrokerLoginFlow" : "first broker login", + "attributes" : { + "cibaBackchannelTokenDeliveryMode" : "poll", + "cibaExpiresIn" : "120", + "cibaAuthRequestedUserHint" : "login_hint", + "oauth2DeviceCodeLifespan" : "600", + "clientOfflineSessionMaxLifespan" : "0", + "oauth2DevicePollingInterval" : "5", + "clientSessionIdleTimeout" : "0", + "parRequestUriLifespan" : "60", + "clientSessionMaxLifespan" : "0", + "clientOfflineSessionIdleTimeout" : "0", + "cibaInterval" : "5", + "realmReusableOtpCode" : "false" + }, + "keycloakVersion" : "26.4.0", + "userManagedAccessAllowed" : false, + "organizationsEnabled" : false, + "verifiableCredentialsEnabled" : false, + "adminPermissionsEnabled" : false, + "clientProfiles" : { + "profiles" : [ ] + }, + "clientPolicies" : { + "policies" : [ ] + } +} \ No newline at end of file diff --git a/devtools/deployments/multi-tenancy/config/keycloak/themes/opencloud/login/resources/css/theme.css b/devtools/deployments/multi-tenancy/config/keycloak/themes/opencloud/login/resources/css/theme.css new file mode 100644 index 000000000..4a529475c --- /dev/null +++ b/devtools/deployments/multi-tenancy/config/keycloak/themes/opencloud/login/resources/css/theme.css @@ -0,0 +1,38 @@ +:root { + --pf-global--primary-color--100: #e2baff; + --pf-global--primary-color--200: #e2baff; + --pf-global--primary-color--dark-100: #e2baff; + --pf-global--Color--light-100: #20434f; +} + +@font-face { + font-family: OpenCloud; + src: url('../fonts/OpenCloud500-Regular.woff2') format('woff2'); + font-weight: normal; + font-style: normal; +} + +@font-face { + font-family: OpenCloud; + src: url('../fonts/OpenCloud750-Bold.woff2') format('woff2'); + font-weight: bold; + font-style: normal; +} + +body { + font-family: "OpenCloud", "Open Sans", Helvetica, Arial, sans-serif; + background: url(../img/background.png) no-repeat center !important; + background-size: cover !important; +} + +.kc-logo-text { + background-image: url(../img/logo.svg) !important; + background-size: contain; + width: 400px; + margin: 0 !important; +} + +#kc-header-wrapper{ + display: flex; + justify-content: center; +} \ No newline at end of file diff --git a/devtools/deployments/multi-tenancy/config/keycloak/themes/opencloud/login/resources/fonts/OpenCloud500-Regular.woff2 b/devtools/deployments/multi-tenancy/config/keycloak/themes/opencloud/login/resources/fonts/OpenCloud500-Regular.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..219abd872f295e68cb0922eb2729c1db340a47d6 GIT binary patch literal 24096 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 literal 0 HcmV?d00001 diff --git a/devtools/deployments/multi-tenancy/config/keycloak/themes/opencloud/login/resources/fonts/OpenCloud750-Bold.woff2 b/devtools/deployments/multi-tenancy/config/keycloak/themes/opencloud/login/resources/fonts/OpenCloud750-Bold.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..1bc7076851ede28500298d631a1500dc29b9e8db GIT binary patch literal 24624 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 literal 0 HcmV?d00001 diff --git a/devtools/deployments/multi-tenancy/config/keycloak/themes/opencloud/login/resources/img/logo.svg b/devtools/deployments/multi-tenancy/config/keycloak/themes/opencloud/login/resources/img/logo.svg new file mode 100644 index 000000000..c8e40ea36 --- /dev/null +++ b/devtools/deployments/multi-tenancy/config/keycloak/themes/opencloud/login/resources/img/logo.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/devtools/deployments/multi-tenancy/config/keycloak/themes/opencloud/login/resources/js/script.js b/devtools/deployments/multi-tenancy/config/keycloak/themes/opencloud/login/resources/js/script.js new file mode 100644 index 000000000..9c1d5165d --- /dev/null +++ b/devtools/deployments/multi-tenancy/config/keycloak/themes/opencloud/login/resources/js/script.js @@ -0,0 +1,19 @@ +document.addEventListener("DOMContentLoaded", function () { + const setLogoUrl = (url) => { + const logoTextSelector = document.querySelector(".kc-logo-text"); + + if (!logoTextSelector) { + return + } + + const link = document.createElement("a"); + link.href = url; + link.target = "_blank"; + + const parent = logoTextSelector.parentNode; + parent.insertBefore(link, logoTextSelector); + link.appendChild(logoTextSelector); + } + + setLogoUrl('https://opencloud.eu') +}); \ No newline at end of file diff --git a/devtools/deployments/multi-tenancy/config/keycloak/themes/opencloud/login/theme.properties b/devtools/deployments/multi-tenancy/config/keycloak/themes/opencloud/login/theme.properties new file mode 100644 index 000000000..767efe452 --- /dev/null +++ b/devtools/deployments/multi-tenancy/config/keycloak/themes/opencloud/login/theme.properties @@ -0,0 +1,5 @@ +parent=keycloak +import=common/keycloak + +styles=css/login.css css/theme.css +scripts=js/script.js \ No newline at end of file diff --git a/devtools/deployments/multi-tenancy/config/ldap/docker-entrypoint-override.sh b/devtools/deployments/multi-tenancy/config/ldap/docker-entrypoint-override.sh new file mode 100644 index 000000000..67dc850a6 --- /dev/null +++ b/devtools/deployments/multi-tenancy/config/ldap/docker-entrypoint-override.sh @@ -0,0 +1,9 @@ +#!/bin/bash +echo "Running custom LDAP entrypoint script..." + +if [ ! -f /opt/bitnami/openldap/share/openldap.key ] +then + openssl req -x509 -newkey rsa:4096 -keyout /opt/bitnami/openldap/share/openldap.key -out /opt/bitnami/openldap/share/openldap.crt -sha256 -days 365 -batch -nodes +fi +# run original docker-entrypoint +/opt/bitnami/scripts/openldap/entrypoint.sh "$@" diff --git a/devtools/deployments/multi-tenancy/config/ldap/ldif/10_base.ldif b/devtools/deployments/multi-tenancy/config/ldap/ldif/10_base.ldif new file mode 100644 index 000000000..650a5f378 --- /dev/null +++ b/devtools/deployments/multi-tenancy/config/ldap/ldif/10_base.ldif @@ -0,0 +1,20 @@ +dn: dc=opencloud,dc=eu +objectClass: organization +objectClass: dcObject +dc: opencloud +o: openCloud + +dn: ou=users,dc=opencloud,dc=eu +objectClass: organizationalUnit +ou: users + +dn: cn=admin,dc=opencloud,dc=eu +objectClass: inetOrgPerson +objectClass: person +cn: admin +sn: admin +uid: ldapadmin + +dn: ou=tenants,dc=opencloud,dc=eu +objectClass: organizationalUnit +ou: tenants diff --git a/devtools/deployments/multi-tenancy/config/ldap/ldif/20_admin.ldif b/devtools/deployments/multi-tenancy/config/ldap/ldif/20_admin.ldif new file mode 100644 index 000000000..966cbeafd --- /dev/null +++ b/devtools/deployments/multi-tenancy/config/ldap/ldif/20_admin.ldif @@ -0,0 +1,20 @@ +dn: uid=admin,ou=users,dc=opencloud,dc=eu +objectClass: inetOrgPerson +objectClass: organizationalPerson +objectClass: person +objectClass: top +uid: admin +givenName: Admin +sn: Admin +cn: admin +displayName: Admin +description: An admin for this OpenCloud instance. +mail: admin@example.org +userPassword:: e1NTSEF9UWhmaFB3dERydTUydURoWFFObDRMbzVIckI3TkI5Nmo== + +dn: cn=administrators,ou=groups,dc=opencloud,dc=eu +objectClass: groupOfNames +objectClass: top +cn: administrators +description: OpenCloud Administrators +member: uid=admin,ou=users,dc=opencloud,dc=eu diff --git a/devtools/deployments/multi-tenancy/config/opencloud/apps/.gitkeep b/devtools/deployments/multi-tenancy/config/opencloud/apps/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/devtools/deployments/multi-tenancy/config/opencloud/csp.yaml b/devtools/deployments/multi-tenancy/config/opencloud/csp.yaml new file mode 100644 index 000000000..9314c97fb --- /dev/null +++ b/devtools/deployments/multi-tenancy/config/opencloud/csp.yaml @@ -0,0 +1,44 @@ +directives: + child-src: + - '''self''' + connect-src: + - '''self''' + - 'blob:' + - 'https://${COMPANION_DOMAIN|companion.opencloud.test}/' + - 'wss://${COMPANION_DOMAIN|companion.opencloud.test}/' + - 'https://raw.githubusercontent.com/opencloud-eu/awesome-apps/' + - 'https://${IDP_DOMAIN|keycloak.opencloud.test}/' + default-src: + - '''none''' + font-src: + - '''self''' + frame-ancestors: + - '''self''' + frame-src: + - '''self''' + - 'blob:' + - 'https://embed.diagrams.net/' + # In contrary to bash and docker the default is given after the | character + - 'https://${COLLABORA_DOMAIN|collabora.opencloud.test}/' + # This is needed for the external-sites web extension when embedding sites + - 'https://docs.opencloud.eu' + img-src: + - '''self''' + - 'data:' + - 'blob:' + - 'https://raw.githubusercontent.com/opencloud-eu/awesome-apps/' + # In contrary to bash and docker the default is given after the | character + - 'https://${COLLABORA_DOMAIN|collabora.opencloud.test}/' + manifest-src: + - '''self''' + media-src: + - '''self''' + object-src: + - '''self''' + - 'blob:' + script-src: + - '''self''' + - '''unsafe-inline''' + style-src: + - '''self''' + - '''unsafe-inline''' diff --git a/devtools/deployments/multi-tenancy/config/opencloud/proxy.yaml b/devtools/deployments/multi-tenancy/config/opencloud/proxy.yaml new file mode 100644 index 000000000..93d34af67 --- /dev/null +++ b/devtools/deployments/multi-tenancy/config/opencloud/proxy.yaml @@ -0,0 +1,40 @@ +# This adds four additional routes to the proxy. Forwarding +# request on '/carddav/', '/caldav/' and the respective '/.well-knwown' +# endpoints to the radicale container and setting the required headers. +additional_policies: + - name: default + routes: + - endpoint: /caldav/ + backend: http://radicale:5232 + remote_user_header: X-Remote-User + skip_x_access_token: true + additional_headers: + - X-Script-Name: /caldav + - endpoint: /.well-known/caldav + backend: http://radicale:5232 + remote_user_header: X-Remote-User + skip_x_access_token: true + additional_headers: + - X-Script-Name: /caldav + - endpoint: /carddav/ + backend: http://radicale:5232 + remote_user_header: X-Remote-User + skip_x_access_token: true + additional_headers: + - X-Script-Name: /carddav + - endpoint: /.well-known/carddav + backend: http://radicale:5232 + remote_user_header: X-Remote-User + skip_x_access_token: true + additional_headers: + - X-Script-Name: /carddav +# To enable the radicale web UI add this rule. +# "unprotected" is True because the Web UI itself ask for +# the password. +# Also set "type" to "internal" in the config/radicale/config +# - endpoint: /caldav/.web/ +# backend: http://radicale:5232/ +# unprotected: true +# skip_x_access_token: true +# additional_headers: +# - X-Script-Name: /caldav diff --git a/devtools/deployments/multi-tenancy/config/traefik/docker-entrypoint-override.sh b/devtools/deployments/multi-tenancy/config/traefik/docker-entrypoint-override.sh new file mode 100644 index 000000000..456a62df4 --- /dev/null +++ b/devtools/deployments/multi-tenancy/config/traefik/docker-entrypoint-override.sh @@ -0,0 +1,72 @@ +set -e + +printenv +# Function to add arguments to the command +add_arg() { + TRAEFIK_CMD="$TRAEFIK_CMD $1" +} + +# Initialize the base command +TRAEFIK_CMD="traefik" + +# Base Traefik arguments (from your existing configuration) +add_arg "--log.level=${TRAEFIK_LOG_LEVEL:-ERROR}" +# enable dashboard +add_arg "--api.dashboard=true" +# define entrypoints +add_arg "--entryPoints.http.address=:80" +add_arg "--entryPoints.http.http.redirections.entryPoint.to=https" +add_arg "--entryPoints.http.http.redirections.entryPoint.scheme=https" +add_arg "--entryPoints.https.address=:443" +# change default timeouts for long-running requests +# this is needed for webdav clients that do not support the TUS protocol +add_arg "--entryPoints.https.transport.respondingTimeouts.readTimeout=12h" +add_arg "--entryPoints.https.transport.respondingTimeouts.writeTimeout=12h" +add_arg "--entryPoints.https.transport.respondingTimeouts.idleTimeout=3m" +# docker provider (get configuration from container labels) +add_arg "--providers.docker.endpoint=unix:///var/run/docker.sock" +add_arg "--providers.docker.exposedByDefault=false" +# access log +add_arg "--accessLog=${TRAEFIK_ACCESS_LOG:-false}" +add_arg "--accessLog.format=json" +add_arg "--accessLog.fields.headers.names.X-Request-Id=keep" + +# Add Let's Encrypt configuration if enabled +if [ "${TRAEFIK_SERVICES_TLS_CONFIG}" = "tls.certresolver=letsencrypt" ]; then + echo "Configuring Traefik with Let's Encrypt..." + add_arg "--certificatesResolvers.letsencrypt.acme.email=${TRAEFIK_ACME_MAIL:-example@example.org}" + add_arg "--certificatesResolvers.letsencrypt.acme.storage=/certs/acme.json" + add_arg "--certificatesResolvers.letsencrypt.acme.httpChallenge.entryPoint=http" + add_arg "--certificatesResolvers.letsencrypt.acme.caserver=${TRAEFIK_ACME_CASERVER:-https://acme-v02.api.letsencrypt.org/directory}" +fi + +# Add local certificate configuration if enabled +if [ "${TRAEFIK_SERVICES_TLS_CONFIG}" = "tls=true" ]; then + echo "Configuring Traefik with local certificates..." + add_arg "--providers.file.directory=/etc/traefik/dynamic" + add_arg "--providers.file.watch=true" +fi + +# Warning if neither certificate method is enabled +if [ "${TRAEFIK_SERVICES_TLS_CONFIG}" != "tls=true" ] && [ "${TRAEFIK_SERVICES_TLS_CONFIG}" != "tls.certresolver=letsencrypt" ]; then + echo "WARNING: Neither Let's Encrypt nor local certificates are enabled." + echo "HTTPS will not work properly without certificate configuration." +fi + +# Add any custom arguments from environment variable +if [ -n "${TRAEFIK_CUSTOM_ARGS}" ]; then + echo "Adding custom Traefik arguments: ${TRAEFIK_CUSTOM_ARGS}" + TRAEFIK_CMD="$TRAEFIK_CMD $TRAEFIK_CUSTOM_ARGS" +fi + +# Add any additional arguments passed to the script +for arg in "$@"; do + add_arg "$arg" +done + +# Print the final command for debugging +echo "Starting Traefik with command:" +echo "$TRAEFIK_CMD" + +# Execute Traefik +exec $TRAEFIK_CMD diff --git a/devtools/deployments/multi-tenancy/config/traefik/dynamic/.gitkeep b/devtools/deployments/multi-tenancy/config/traefik/dynamic/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/devtools/deployments/multi-tenancy/docker-compose.yml b/devtools/deployments/multi-tenancy/docker-compose.yml new file mode 100644 index 000000000..dcba81d79 --- /dev/null +++ b/devtools/deployments/multi-tenancy/docker-compose.yml @@ -0,0 +1,119 @@ +--- +services: + # OpenCloud instance configured for multi-tenancy using keycloak as identity provider + # The graph service is setup to consume users via the CS3 API. + opencloud: + image: ${OC_DOCKER_IMAGE:-opencloudeu/opencloud-rolling}:${OC_DOCKER_TAG:-latest} + # changelog: https://github.com/opencloud-eu/opencloud/tree/main/changelog + # release notes: https://docs.opencloud.eu/opencloud_release_notes.html + networks: + opencloud-net: + entrypoint: + - /bin/sh + # run opencloud init to initialize a configuration file with random secrets + # it will fail on subsequent runs, because the config file already exists + # therefore we ignore the error and then start the opencloud server + command: ["-c", "opencloud init || true; opencloud server"] + environment: + OC_MULTI_TENANT_ENABLED: "true" + # enable services that are not started automatically + OC_URL: https://${OC_DOMAIN:-cloud.opencloud.test} + OC_LOG_LEVEL: ${LOG_LEVEL:-info} + OC_LOG_COLOR: "${LOG_PRETTY:-false}" + OC_LOG_PRETTY: "${LOG_PRETTY:-false}" + OC_EXCLUDE_RUN_SERVICES: idm,idp + PROXY_ROLE_ASSIGNMENT_DRIVER: "oidc" + OC_OIDC_ISSUER: https://${KEYCLOAK_DOMAIN:-keycloak.opencloud.test}/realms/openCloud + PROXY_OIDC_REWRITE_WELLKNOWN: "true" + WEB_OIDC_CLIENT_ID: ${OC_OIDC_CLIENT_ID:-web} + PROXY_USER_OIDC_CLAIM: "uuid" + PROXY_USER_CS3_CLAIM: "userid" + WEB_OPTION_ACCOUNT_EDIT_LINK_HREF: "https://${KEYCLOAK_DOMAIN:-keycloak.opencloud.test}/realms/openCloud/account" + # admin and demo accounts must be created in Keycloak + OC_ADMIN_USER_ID: "" + SETTINGS_SETUP_DEFAULT_ASSIGNMENTS: "false" + GRAPH_ASSIGN_DEFAULT_USER_ROLE: "false" + GRAPH_USERNAME_MATCH: "none" + GROUPS_DRIVER: "null" + # This is needed to set the correct CSP rules for OpenCloud + IDP_DOMAIN: ${KEYCLOAK_DOMAIN:-keycloak.opencloud.test} + # do not use SSL between the reverse proxy and OpenCloud + PROXY_TLS: "false" + # INSECURE: needed if OpenCloud / reverse proxy is using self generated certificates + OC_INSECURE: "${INSECURE:-false}" + # basic auth (not recommended, but needed for eg. WebDav clients that do not support OpenID Connect) + PROXY_ENABLE_BASIC_AUTH: "false" + GRAPH_IDENTITY_BACKEND: "cs3" + PROXY_CSP_CONFIG_FILE_LOCATION: /etc/opencloud/csp.yaml + OC_LDAP_URI: ldaps://ldap-server:1636 + OC_LDAP_INSECURE: "true" + OC_LDAP_BIND_DN: "cn=admin,dc=opencloud,dc=eu" + OC_LDAP_BIND_PASSWORD: ${LDAP_BIND_PASSWORD:-admin} + OC_LDAP_USER_BASE_DN: "ou=users,dc=opencloud,dc=eu" + OC_LDAP_USER_SCHEMA_TENANT_ID: "openCloudMemberOfSchool" + PROXY_LOG_LEVEL: "debug" + volumes: + - ./config/opencloud/csp.yaml:/etc/opencloud/csp.yaml + # configure the .env file to use own paths instead of docker internal volumes + - ${OC_CONFIG_DIR:-opencloud-config}:/etc/opencloud + - ${OC_DATA_DIR:-opencloud-data}:/var/lib/opencloud + logging: + driver: ${LOG_DRIVER:-local} + restart: always + labels: + - "traefik.enable=true" + - "traefik.http.routers.opencloud.entrypoints=https" + - "traefik.http.routers.opencloud.rule=Host(`${OC_DOMAIN:-cloud.opencloud.test}`)" + - "traefik.http.routers.opencloud.service=opencloud" + - "traefik.http.services.opencloud.loadbalancer.server.port=9200" + - "traefik.http.routers.opencloud.${TRAEFIK_SERVICES_TLS_CONFIG}" + # Stand-alone instance of the 'graph' service to serve the provisioning API + provsioning: + image: ${OC_DOCKER_IMAGE:-opencloudeu/opencloud-rolling}:${OC_DOCKER_TAG:-latest} + networks: + opencloud-net: + entrypoint: + - /bin/sh + # run opencloud init to initialize a configuration file with random secrets + # it will fail on subsequent runs, because the config file already exists + # therefore we ignore the error and then start the opencloud server + command: ["-c", "opencloud init || true; opencloud graph server"] + environment: + OC_LOG_LEVEL: "debug" + OC_LOG_COLOR: "${LOG_PRETTY:-false}" + OC_LOG_PRETTY: "${LOG_PRETTY:-false}" + # This just runs the standalone graph service we don't need access to the registry + MICRO_REGISTRY: "memory" + # INSECURE: needed if OpenCloud / reverse proxy is using self generated certificates + OC_INSECURE: "${INSECURE:-false}" + GRAPH_HTTP_ADDR: "0.0.0.0:9120" + GRAPH_HTTP_API_TOKEN: "${PROVISIONING_API_TOKEN:-changeme}" + # disable listening for events + GRAPH_EVENTS_ENDPOINT: "" + GRAPH_STORE_NODES: "" + GRAPH_ASSIGN_DEFAULT_USER_ROLE: "false" + GRAPH_USERNAME_MATCH: "none" + GRAPH_LDAP_EDUCATION_RESOURCES_ENABLED: "true" + GRAPH_LDAP_SCHOOL_BASE_DN: "ou=tenants,dc=opencloud,dc=eu" + OC_LDAP_URI: ldaps://ldap-server:1636 + OC_LDAP_INSECURE: "true" + OC_LDAP_BIND_DN: "cn=admin,dc=opencloud,dc=eu" + OC_LDAP_BIND_PASSWORD: ${LDAP_BIND_PASSWORD:-admin} + OC_LDAP_USER_BASE_DN: "ou=users,dc=opencloud,dc=eu" + OC_LDAP_USER_FILTER: "(objectclass=inetOrgPerson)" + volumes: + # configure the .env file to use own paths instead of docker internal volumes + - ${PROVISIONING_CONFIG_DIR:-provisioning-config}:/etc/opencloud + logging: + driver: ${LOG_DRIVER:-local} + restart: always + ports: + - "9120:9120" + +volumes: + opencloud-config: + opencloud-data: + provisioning-config: + +networks: + opencloud-net: diff --git a/devtools/deployments/multi-tenancy/initialize_users.go b/devtools/deployments/multi-tenancy/initialize_users.go new file mode 100644 index 000000000..245a2569b --- /dev/null +++ b/devtools/deployments/multi-tenancy/initialize_users.go @@ -0,0 +1,140 @@ +package main + +import ( + "context" + "crypto/tls" + "fmt" + + "github.com/Nerzal/gocloak/v13" + "github.com/go-resty/resty/v2" + libregraph "github.com/opencloud-eu/libre-graph-api-go" +) + +const ( + provisioningAPIURL = "http://localhost:9120/graph" + provisioningAuthToken = "changeme" +) + +type tenantWithUsers struct { + tenant libregraph.EducationSchool + users []libregraph.EducationUser +} + +var demoTenants = []tenantWithUsers{ + { + tenant: libregraph.EducationSchool{ + DisplayName: libregraph.PtrString("Famous Coders"), + }, + users: []libregraph.EducationUser{ + { + DisplayName: libregraph.PtrString("Dennis Ritchie"), + OnPremisesSamAccountName: libregraph.PtrString("dennis"), + Mail: libregraph.PtrString("dennis@example.org"), + }, + { + DisplayName: libregraph.PtrString("Grace Hopper"), + OnPremisesSamAccountName: libregraph.PtrString("grace"), + Mail: libregraph.PtrString("grace@example.org"), + }, + }, + }, + { + tenant: libregraph.EducationSchool{ + DisplayName: libregraph.PtrString("Scientists"), + }, + users: []libregraph.EducationUser{ + { + DisplayName: libregraph.PtrString("Albert Einstein"), + OnPremisesSamAccountName: libregraph.PtrString("einstein"), + Mail: libregraph.PtrString("einstein@example.org"), + }, + { + DisplayName: libregraph.PtrString("Marie Curie"), + OnPremisesSamAccountName: libregraph.PtrString("marie"), + Mail: libregraph.PtrString("marie@example.org"), + }, + }, + }, +} + +func main() { + lgconf := libregraph.NewConfiguration() + lgconf.Servers = libregraph.ServerConfigurations{ + { + URL: provisioningAPIURL, + }, + } + lgconf.DefaultHeader = map[string]string{"Authorization": "Bearer " + provisioningAuthToken} + lgclient := libregraph.NewAPIClient(lgconf) + + for _, tenant := range demoTenants { + tenantid, err := createTenant(lgclient, tenant.tenant) + if err != nil { + fmt.Printf("Failed to create tenant: %s\n", err) + continue + } + for _, user := range tenant.users { + userid1, err := createUser(lgclient, user) + if err != nil { + fmt.Printf("Failed to create user: %s\n", err) + continue + } + _, err = lgclient.EducationSchoolApi.AddUserToSchool(context.TODO(), tenantid).EducationUserReference(libregraph.EducationUserReference{ + OdataId: libregraph.PtrString(fmt.Sprintf("%s/education/users/%s", provisioningAPIURL, userid1)), + }).Execute() + if err != nil { + fmt.Printf("Failed to add user to tenant: %s\n", err) + continue + } + } + } + + resetAllUserPasswords() +} + +func createUser(client *libregraph.APIClient, user libregraph.EducationUser) (string, error) { + newUser, _, err := client.EducationUserApi.CreateEducationUser(context.TODO()).EducationUser(user).Execute() + if err != nil { + fmt.Printf("Failed to create user: %s\n", err) + return "", err + } + fmt.Printf("Created user: %s with id %s\n", newUser.GetDisplayName(), newUser.GetId()) + return newUser.GetId(), nil +} + +func createTenant(client *libregraph.APIClient, tenant libregraph.EducationSchool) (string, error) { + newTenant, _, err := client.EducationSchoolApi.CreateSchool(context.TODO()).EducationSchool(tenant).Execute() + if err != nil { + fmt.Printf("Failed to create tenant: %s\n", err) + return "", err + } + fmt.Printf("Created tenant: %s with id %s\n", newTenant.GetDisplayName(), newTenant.GetId()) + return newTenant.GetId(), nil +} + +func resetAllUserPasswords() { + tls := tls.Config{InsecureSkipVerify: true} + restyClient := resty.New().SetTLSClientConfig(&tls) + client := gocloak.NewClient("https://keycloak.opencloud.test") + client.SetRestyClient(restyClient) + ctx := context.Background() + token, err := client.LoginAdmin(ctx, "kcadmin", "admin", "master") + if err != nil { + fmt.Printf("Failed to login: %s\n", err) + panic("Something wrong with the credentials or url") + } + + users, err := client.GetUsers(ctx, token.AccessToken, "openCloud", gocloak.GetUsersParams{}) + if err != nil { + fmt.Printf("%s\n", err) + panic("Oh no!, failed to list users :(") + } + for _, user := range users { + fmt.Printf("Setting password for user: %s\n", *user.Username) + err = client.SetPassword(ctx, token.AccessToken, *user.ID, "openCloud", "demo", false) + if err != nil { + fmt.Printf("Failed to set password for user %s: %s\n", *user.Username, err) + } + } + +} diff --git a/devtools/deployments/multi-tenancy/keycloak.yml b/devtools/deployments/multi-tenancy/keycloak.yml new file mode 100644 index 000000000..ecc55479a --- /dev/null +++ b/devtools/deployments/multi-tenancy/keycloak.yml @@ -0,0 +1,55 @@ +--- +services: + opencloud: + environment: + postgres: + image: postgres:alpine + networks: + opencloud-net: + volumes: + - keycloak_postgres_data:/var/lib/postgresql/data + environment: + POSTGRES_DB: keycloak + POSTGRES_USER: ${KC_DB_USERNAME:-keycloak} + POSTGRES_PASSWORD: ${KC_DB_PASSWORD:-keycloak} + logging: + driver: ${LOG_DRIVER:-local} + restart: always + + keycloak: + labels: + - "traefik.enable=true" + - "traefik.http.routers.keycloak.entrypoints=https" + - "traefik.http.routers.keycloak.rule=Host(`${KEYCLOAK_DOMAIN:-keycloak.opencloud.test}`)" + - "traefik.http.routers.keycloak.${TRAEFIK_SERVICES_TLS_CONFIG}" + - "traefik.http.routers.keycloak.service=keycloak" + - "traefik.http.services.keycloak.loadbalancer.server.port=8080" + image: quay.io/keycloak/keycloak:26.4 + networks: + opencloud-net: + command: [ "start", "--spi-connections-http-client-default-disable-trust-manager=${INSECURE:-false}", "--import-realm" ] + entrypoint: [ "/bin/sh", "/opt/keycloak/bin/docker-entrypoint-override.sh" ] + volumes: + - "./config/keycloak/docker-entrypoint-override.sh:/opt/keycloak/bin/docker-entrypoint-override.sh" + - "./config/keycloak/openCloud-realm.dist.json:/opt/keycloak/data/import-dist/openCloud-realm.json" + - "./config/keycloak/themes/opencloud:/opt/keycloak/themes/opencloud" + environment: + OC_DOMAIN: ${OC_DOMAIN:-cloud.opencloud.test} + KC_HOSTNAME: ${KEYCLOAK_DOMAIN:-keycloak.opencloud.test} + KC_DB: postgres + KC_DB_URL: "jdbc:postgresql://postgres:5432/keycloak" + KC_DB_USERNAME: ${KC_DB_USERNAME:-keycloak} + KC_DB_PASSWORD: ${KC_DB_PASSWORD:-keycloak} + KC_FEATURES: impersonation + KC_PROXY_HEADERS: xforwarded + KC_HTTP_ENABLED: true + KEYCLOAK_ADMIN: ${KEYCLOAK_ADMIN:-kcadmin} + KEYCLOAK_ADMIN_PASSWORD: ${KEYCLOAK_ADMIN_PASSWORD:-admin} + depends_on: + - postgres + logging: + driver: ${LOG_DRIVER:-local} + restart: always + +volumes: + keycloak_postgres_data: diff --git a/devtools/deployments/multi-tenancy/ldap-server.yml b/devtools/deployments/multi-tenancy/ldap-server.yml new file mode 100644 index 000000000..3aebf8039 --- /dev/null +++ b/devtools/deployments/multi-tenancy/ldap-server.yml @@ -0,0 +1,32 @@ +--- +services: + ldap-server: + image: bitnamilegacy/openldap:2.6 + networks: + opencloud-net: + entrypoint: [ "/bin/sh", "/opt/bitnami/scripts/openldap/docker-entrypoint-override.sh", "/opt/bitnami/scripts/openldap/run.sh" ] + environment: + BITNAMI_DEBUG: true + LDAP_TLS_VERIFY_CLIENT: never + LDAP_ENABLE_TLS: "yes" + LDAP_TLS_CA_FILE: /opt/bitnami/openldap/share/openldap.crt + LDAP_TLS_CERT_FILE: /opt/bitnami/openldap/share/openldap.crt + LDAP_TLS_KEY_FILE: /opt/bitnami/openldap/share/openldap.key + LDAP_ROOT: "dc=opencloud,dc=eu" + LDAP_ADMIN_PASSWORD: ${LDAP_BIND_PASSWORD:-admin} + ports: + - "127.0.0.1:389:1389" + - "127.0.0.1:636:1636" + volumes: + # Only use the base ldif file to create the base structure + - ./config/ldap/ldif/10_base.ldif:/ldifs/10_base.ldif + # Use the custom schema from opencloud because we are in full control of the ldap server + - ../shared/config/ldap/schemas/10_opencloud_schema.ldif:/schemas/10_opencloud_schema.ldif + - ../shared/config/ldap/schemas/20_opencloud_education_schema.ldif:/schemas/20_opencloud_education_schema.ldif + - ./config/ldap/docker-entrypoint-override.sh:/opt/bitnami/scripts/openldap/docker-entrypoint-override.sh + - ${LDAP_CERTS_DIR:-ldap-certs}:/opt/bitnami/openldap/share + - ${LDAP_DATA_DIR:-ldap-data}:/bitnami/openldap + +volumes: + ldap-certs: + ldap-data: diff --git a/devtools/deployments/multi-tenancy/testing/ldap-manager.yml b/devtools/deployments/multi-tenancy/testing/ldap-manager.yml new file mode 100644 index 000000000..2374ffe57 --- /dev/null +++ b/devtools/deployments/multi-tenancy/testing/ldap-manager.yml @@ -0,0 +1,24 @@ +--- +# This file can be used to be added to the opencloud_full example +# to browse the LDAP server with a web interface. +# This is not a production ready setup. +services: + ldap-manager: + image: phpldapadmin/phpldapadmin:latest + networks: + opencloud-net: + environment: + LDAP_HOST: ldap-server + LDAP_PORT: 1389 + LDAP_LOGIN_OBJECTCLASS: "inetOrgPerson" + APP_URL: "https://${LDAP_MANAGER_DOMAIN:-ldap.opencloud.test}" + labels: + - "traefik.enable=true" + - "traefik.http.routers.ldap-manager.entrypoints=https" + - "traefik.http.routers.ldap-manager.rule=Host(`${LDAP_MANAGER_DOMAIN:-ldap.opencloud.test}`)" + - "traefik.http.routers.ldap-manager.${TRAEFIK_SERVICES_TLS_CONFIG}" + - "traefik.http.routers.ldap-manager.service=ldap-manager" + - "traefik.http.services.ldap-manager.loadbalancer.server.port=8080" + logging: + driver: ${LOG_DRIVER:-local} + restart: always \ No newline at end of file diff --git a/devtools/deployments/multi-tenancy/traefik.yml b/devtools/deployments/multi-tenancy/traefik.yml new file mode 100644 index 000000000..528cc09b6 --- /dev/null +++ b/devtools/deployments/multi-tenancy/traefik.yml @@ -0,0 +1,45 @@ +--- +services: + opencloud: + labels: + - "traefik.enable=true" + - "traefik.http.routers.opencloud.entrypoints=https" + - "traefik.http.routers.opencloud.rule=Host(`${OC_DOMAIN:-cloud.opencloud.test}`)" + - "traefik.http.routers.opencloud.service=opencloud" + - "traefik.http.services.opencloud.loadbalancer.server.port=9200" + - "traefik.http.routers.opencloud.${TRAEFIK_SERVICES_TLS_CONFIG}" + traefik: + image: traefik:v3.3.1 + # release notes: https://github.com/traefik/traefik/releases + networks: + opencloud-net: + aliases: + - ${OC_DOMAIN:-cloud.opencloud.test} + - ${KEYCLOAK_DOMAIN:-keycloak.opencloud.test} + entrypoint: [ "/bin/sh", "/opt/traefik/bin/docker-entrypoint-override.sh"] + environment: + - "TRAEFIK_SERVICES_TLS_CONFIG=${TRAEFIK_SERVICES_TLS_CONFIG:-tls.certresolver=letsencrypt}" + - "TRAEFIK_ACME_MAIL=${TRAEFIK_ACME_MAIL:-example@example.org}" + - "TRAEFIK_ACME_CASERVER=${TRAEFIK_ACME_CASERVER:-https://acme-v02.api.letsencrypt.org/directory}" + - "TRAEFIK_LOG_LEVEL=${TRAEFIK_LOG_LEVEL:-ERROR}" + - "TRAEFIK_ACCESS_LOG=${TRAEFIK_ACCESS_LOG:-false}" + ports: + - "80:80" + - "443:443" + volumes: + - "${DOCKER_SOCKET_PATH:-/var/run/docker.sock}:/var/run/docker.sock:ro" + - "./config/traefik/docker-entrypoint-override.sh:/opt/traefik/bin/docker-entrypoint-override.sh" + - "${TRAEFIK_CERTS_DIR:-./certs}:/certs" + - "./config/traefik/dynamic:/etc/traefik/dynamic" + labels: + - "traefik.enable=${TRAEFIK_DASHBOARD:-false}" + # defaults to admin:admin + - "traefik.http.middlewares.traefik-auth.basicauth.users=${TRAEFIK_BASIC_AUTH_USERS:-admin:$$apr1$$4vqie50r$$YQAmQdtmz5n9rEALhxJ4l.}" + - "traefik.http.routers.traefik.entrypoints=https" + - "traefik.http.routers.traefik.rule=Host(`${TRAEFIK_DOMAIN:-traefik.opencloud.test}`)" + - "traefik.http.routers.traefik.middlewares=traefik-auth" + - "traefik.http.routers.traefik.${TRAEFIK_SERVICES_TLS_CONFIG}" + - "traefik.http.routers.traefik.service=api@internal" + logging: + driver: ${LOG_DRIVER:-local} + restart: always