From c2d68ee68c023bdbba938076fda31176a349d448 Mon Sep 17 00:00:00 2001 From: mark H Date: Mon, 13 Jan 2025 18:00:03 +0800 Subject: [PATCH] up --- README.rst | 247 ++ __init__.py | 5 + __manifest__.py | 21 + __pycache__/__init__.cpython-312.pyc | Bin 0 -> 207 bytes controllers/__init__.py | 4 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 182 bytes controllers/__pycache__/main.cpython-312.pyc | Bin 0 -> 2230 bytes controllers/main.py | 50 + data/auth_oauth_data.xml | 39 + demo/local_keycloak.xml | 20 + i18n/auth_oidc.pot | 119 + i18n/es.po | 128 ++ i18n/it.po | 127 ++ i18n/zh_CN.po | 124 + models/__init__.py | 5 + models/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 225 bytes .../auth_oauth_provider.cpython-312.pyc | Bin 0 -> 5000 bytes models/__pycache__/res_users.cpython-312.pyc | Bin 0 -> 3644 bytes models/auth_oauth_provider.py | 106 + models/res_users.py | 82 + pyproject.toml | 3 + readme/CONFIGURE.md | 72 + readme/CONTRIBUTORS.md | 4 + readme/DESCRIPTION.md | 5 + readme/HISTORY.md | 39 + readme/INSTALL.md | 3 + readme/ROADMAP.md | 4 + readme/USAGE.md | 1 + static/description/icon.png | Bin 0 -> 2780 bytes static/description/index.html | 607 +++++ .../oauth-microsoft_azure-api_permissions.png | Bin 0 -> 59625 bytes .../oauth-microsoft_azure-optional_claims.png | Bin 0 -> 43656 bytes .../description/odoo-azure_ad_multitenant.png | Bin 0 -> 37344 bytes tests/__init__.py | 1 + tests/keycloak/keycloak-config.json | 1997 +++++++++++++++++ tests/keycloak/keycloak.sh | 10 + tests/test_auth_oidc_auth_code.py | 321 +++ views/auth_oauth_provider.xml | 24 + 38 files changed, 4168 insertions(+) create mode 100644 README.rst create mode 100644 __init__.py create mode 100644 __manifest__.py create mode 100644 __pycache__/__init__.cpython-312.pyc create mode 100644 controllers/__init__.py create mode 100644 controllers/__pycache__/__init__.cpython-312.pyc create mode 100644 controllers/__pycache__/main.cpython-312.pyc create mode 100644 controllers/main.py create mode 100644 data/auth_oauth_data.xml create mode 100644 demo/local_keycloak.xml create mode 100644 i18n/auth_oidc.pot create mode 100644 i18n/es.po create mode 100644 i18n/it.po create mode 100644 i18n/zh_CN.po create mode 100644 models/__init__.py create mode 100644 models/__pycache__/__init__.cpython-312.pyc create mode 100644 models/__pycache__/auth_oauth_provider.cpython-312.pyc create mode 100644 models/__pycache__/res_users.cpython-312.pyc create mode 100644 models/auth_oauth_provider.py create mode 100644 models/res_users.py create mode 100644 pyproject.toml create mode 100644 readme/CONFIGURE.md create mode 100644 readme/CONTRIBUTORS.md create mode 100644 readme/DESCRIPTION.md create mode 100644 readme/HISTORY.md create mode 100644 readme/INSTALL.md create mode 100644 readme/ROADMAP.md create mode 100644 readme/USAGE.md create mode 100644 static/description/icon.png create mode 100644 static/description/index.html create mode 100644 static/description/oauth-microsoft_azure-api_permissions.png create mode 100644 static/description/oauth-microsoft_azure-optional_claims.png create mode 100644 static/description/odoo-azure_ad_multitenant.png create mode 100644 tests/__init__.py create mode 100644 tests/keycloak/keycloak-config.json create mode 100644 tests/keycloak/keycloak.sh create mode 100644 tests/test_auth_oidc_auth_code.py create mode 100644 views/auth_oauth_provider.xml diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..0f673d1 --- /dev/null +++ b/README.rst @@ -0,0 +1,247 @@ +============================= +Authentication OpenID Connect +============================= + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:cd754fc72d2039d02ab1b8aec98af43fb9543c9a70f2150ab6e482954e4e83d6 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png + :target: https://odoo-community.org/page/development-status + :alt: Beta +.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fserver--auth-lightgray.png?logo=github + :target: https://github.com/OCA/server-auth/tree/18.0/auth_oidc + :alt: OCA/server-auth +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/server-auth-18-0/server-auth-18-0-auth_oidc + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png + :target: https://runboat.odoo-community.org/builds?repo=OCA/server-auth&target_branch=18.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module allows users to login through an OpenID Connect provider +using the authorization code flow or implicit flow. + +Note the implicit flow is not recommended because it exposes access +tokens to the browser and in http logs. + +**Table of contents** + +.. contents:: + :local: + +Installation +============ + +This module depends on the +`python-jose `__ library, not to +be confused with ``jose`` which is also available on PyPI. + +Configuration +============= + +Setup for Microsoft Azure +------------------------- + +Example configuration with OpenID Connect authorization code flow. + +1. configure a new web application in Azure with OpenID and code flow + (see the `provider + documentation `__)) + +2. in this application the redirect url must be be "/auth_oauth/signin" and of course this URL should be reachable + from Azure + +3. create a new authentication provider in Odoo with the following + parameters (see the `portal + documentation `__ + for more information): + +|image| + +|image1| + +Single tenant provider limits the access to user of your tenant, while +Multitenants allow access for all AzureAD users, so user of foreign +companies can use their AzureAD login without an guest account. + +- Provider Name: Azure AD Single Tenant +- Client ID: Application (client) id +- Client Secret: Client secret +- Allowed: yes + +or + +- Provider Name: Azure AD Multitenant +- Client ID: Application (client) id +- Client Secret: Client secret +- Allowed: yes +- replace {tenant_id} in urls with your Azure tenant id + +|image2| + +Setup for Keycloak +------------------ + +Example configuration with OpenID Connect authorization code flow. + +In Keycloak: + +1. configure a new Client +2. make sure Authorization Code Flow is Enabled. +3. configure the client Access Type as "confidential" and take note of + the client secret in the Credentials tab +4. configure the redirect url to be "/auth_oauth/signin" + +In Odoo, create a new Oauth Provider with the following parameters: + +- Provider name: Keycloak (or any name you like that identify your + keycloak provider) +- Auth Flow: OpenID Connect (authorization code flow) +- Client ID: the same Client ID you entered when configuring the client + in Keycloak +- Client Secret: found in keycloak on the client Credentials tab +- Allowed: yes +- Body: the link text to appear on the login page, such as Login with + Keycloak +- Scope: openid email +- Authentication URL: The "authorization_endpoint" URL found in the + OpenID Endpoint Configuration of your Keycloak realm +- Token URL: The "token_endpoint" URL found in the OpenID Endpoint + Configuration of your Keycloak realm +- JWKS URL: The "jwks_uri" URL found in the OpenID Endpoint + Configuration of your Keycloak realm + +.. |image| image:: https://raw.githubusercontent.com/OCA/server-auth/18.0/auth_oidc/static/description/oauth-microsoft_azure-api_permissions.png +.. |image1| image:: https://raw.githubusercontent.com/OCA/server-auth/18.0/auth_oidc/static/description/oauth-microsoft_azure-optional_claims.png +.. |image2| image:: https://raw.githubusercontent.com/OCA/server-auth/18.0/auth_oidc/static/description/odoo-azure_ad_multitenant.png + +Usage +===== + +On the login page, click on the authentication provider you configured. + +Known issues / Roadmap +====================== + +- When going to the login screen, check for a existing token and do a + direct login without the clicking on the SSO link +- When doing a logout an extra option to also logout at the SSO + provider. + +Changelog +========= + +18.0.1.0.0 2024-10-09 +--------------------- + +- Odoo 18 migration + +17.0.1.0.0 2024-03-20 +--------------------- + +- Odoo 17 migration + +16.0.1.1.0 2024-02-28 +--------------------- + +- Forward port OpenID Connect fixes from 15.0 to 16.0 + +16.0.1.0.2 2023-11-16 +--------------------- + +- Readme link updates + +16.0.1.0.1 2023-10-09 +--------------------- + +- Add AzureAD code flow provider + +16.0.1.0.0 2023-01-27 +--------------------- + +- Odoo 16 migration + +15.0.1.0.0 2023-01-06 +--------------------- + +- Odoo 15 migration + +14.0.1.0.0 2021-12-10 +--------------------- + +- Odoo 14 migration + +13.0.1.0.0 2020-04-10 +--------------------- + +- Odoo 13 migration, add authorization code flow. + +10.0.1.0.0 2018-10-05 +--------------------- + +- Initial implementation + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +------- + +* ICTSTUDIO +* André Schenkels +* ACSONE SA/NV + +Contributors +------------ + +- Alexandre Fayolle +- Stéphane Bidoul +- David Jaen +- Andreas Perhab + +Maintainers +----------- + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +.. |maintainer-sbidoul| image:: https://github.com/sbidoul.png?size=40px + :target: https://github.com/sbidoul + :alt: sbidoul + +Current `maintainer `__: + +|maintainer-sbidoul| + +This module is part of the `OCA/server-auth `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..8b36099 --- /dev/null +++ b/__init__.py @@ -0,0 +1,5 @@ +# Copyright 2016 ICTSTUDIO +# License: AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) + +from . import controllers +from . import models diff --git a/__manifest__.py b/__manifest__.py new file mode 100644 index 0000000..f6897d2 --- /dev/null +++ b/__manifest__.py @@ -0,0 +1,21 @@ +# Copyright 2016 ICTSTUDIO +# Copyright 2021 ACSONE SA/NV +# License: AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) + +{ + "name": "Authentication OpenID Connect", + "version": "18.0.1.0.0", + "license": "AGPL-3", + "author": ( + "ICTSTUDIO, André Schenkels, " + "ACSONE SA/NV, " + "Odoo Community Association (OCA)" + ), + "maintainers": ["sbidoul"], + "website": "https://github.com/OCA/server-auth", + "summary": "Allow users to login through OpenID Connect Provider", + "external_dependencies": {"python": ["python-jose"]}, + "depends": ["auth_oauth"], + "data": ["views/auth_oauth_provider.xml", "data/auth_oauth_data.xml"], + "demo": ["demo/local_keycloak.xml"], +} diff --git a/__pycache__/__init__.cpython-312.pyc b/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bf62bd7cfd15596aa5c48382361b5afa43b79834 GIT binary patch literal 207 zcmX@j%ge<81XbtD)0YD2#~=<2FhLogWq^$73@HpLj5!Rsj8Tk?AT|?_%@oDN$WY0w z$?}pBs6>L#Y7 +# License: AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) + +from . import main diff --git a/controllers/__pycache__/__init__.cpython-312.pyc b/controllers/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b432735c3eb7f0dd737a095a2ef1e3414f62b9fd GIT binary patch literal 182 zcmX@j%ge<81XbtD(>sCmV-N=hn4pZ$0zk%eh7^Vr#vF!R#wbQchDs()=9i2>VNJ$c zEV+r9d48Hqw-}0;fg&pzK7;iAGS<({E74D_C@D(RO-xD2&nwnXEG@~1&(BOr)=$pQ zD=Es)$w@6L){l?R%*!l^kJl@x{Ka9Do1apelWJGQ0W=aQPQUXLm4XG-SlAEh)web#S&3f0JT_AC6 zDThd=9qgAWpN~|DF>vsigH78t46uBGrJz#MD1wxe)H!2=DpdU zg24bFtMv0`ZpsJ1uasfS(JQ+_x$Os(O zbE)2fFXQ6?4NxVFF@F5|uh zi2b(ZBGhgyIA)rG*#<~y01c_JMGSt!?EupjQ}Cs?489_z+G|QGT$x({RqnHK04jW$ z&$BX~o&cnfEbWS`?2`EnU~mu5m|mt*=4DruFnK?dw6N@EXJrr7VO+&+wlS@IJEK~d z#aDLA!VD1;zuvVm0FWU2h|EuAT7XCcw$vlHF)4|roqDQx%uZ%6-^HjFHp7h0M;O(@ zWlz4h#W6|_viKR!Q&(>-7W=7A|62-#l{?sOMJNj-_D-6S+(oEvJKz7li4Z%5Cgd>5 zH1vOUXEAJWsK-3Z{O1Q4)xvUw+2jDTNTLYY@$}>eTbhg_TJKQ5=J2WH^p^LUElhM= z)Iybem~w+MM;f+w3A2x+P>@1#10rQbfe3pfBdaJ4IwV(dhrO3wl~fgK zIf&i2CO(+LJ#R|I0yI~38GDSZUW8a6KvU$>c?ZX`x~9Rb8MAj|g>}QEB2!YdSlm&G zmpu0>bN4DZXy*hqxu`3enFtY+>m*p*p(=(c7LmT8$PgLWm4hatV@ni@s$ycV0ka62 z1`gWUisU&lq!kj&ZOMRBleoJn5N}USI!_Dsu1dzLs@%qcu`1E5gyx1ID>-PGSU`q} zlp^+us-DZy%0Ps4goPCy6(ke6iT{KTyA7zWV1Fw(A0rVRax4@jBoz!qA4$|hC4N!N zs*+)dqCuaZn6vJ`PNfQ(nSysrBwdtbnOs)Nu7|G3*%U1q(#bU-BUK=^OBU}U+N`BB z%dMv^`DUG{OXDWF$yfKmQ6##3-HM#AMaH-M^}fLdc-J4=TC9ircV@O{9tpK@d~2cJ z8Q$-mJm{P}iViZ8fWo7QNW#FpM(J|0W`(D9_B9yz-!T9J$2m!6K+QrC$0M5yZ9?XPv6 zJ4us@oy>r)=)SM7K}rh}9_$lEO)5Z9!~sz(=<>Qs>2^{4d|gtV9s8PauzBfOsH&ov z;82QQ4xO@jbcsMj{}DFmrW-*5UOfPR@i9;I1Q3AUXZwjDqAv}iPYU+Z-c+<4qR$l$ zZbGzHg6p{??RR@Z>^8-5UDoxa(^N^PsVGeTtE-Z +# Copyright 2021 ACSONE SA/NV +# License: AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) + +import base64 +import hashlib +import logging +import secrets + +from werkzeug.urls import url_decode, url_encode + +from odoo.addons.auth_oauth.controllers.main import OAuthLogin + +_logger = logging.getLogger(__name__) + + +class OpenIDLogin(OAuthLogin): + def list_providers(self): + providers = super().list_providers() + for provider in providers: + flow = provider.get("flow") + if flow in ("id_token", "id_token_code"): + params = url_decode(provider["auth_link"].split("?")[-1]) + # nonce + params["nonce"] = secrets.token_urlsafe() + # response_type + if flow == "id_token": + # https://openid.net/specs/openid-connect-core-1_0.html + # #ImplicitAuthRequest + params["response_type"] = "id_token token" + elif flow == "id_token_code": + # https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest + params["response_type"] = "code" + # PKCE (https://tools.ietf.org/html/rfc7636) + code_verifier = provider["code_verifier"] + code_challenge = base64.urlsafe_b64encode( + hashlib.sha256(code_verifier.encode("ascii")).digest() + ).rstrip(b"=") + params["code_challenge"] = code_challenge + params["code_challenge_method"] = "S256" + # scope + if provider.get("scope"): + if "openid" not in provider["scope"].split(): + _logger.error("openid connect scope must contain 'openid'") + params["scope"] = provider["scope"] + # auth link that the user will click + provider["auth_link"] = "{}?{}".format( + provider["auth_endpoint"], url_encode(params) + ) + return providers diff --git a/data/auth_oauth_data.xml b/data/auth_oauth_data.xml new file mode 100644 index 0000000..bdeea59 --- /dev/null +++ b/data/auth_oauth_data.xml @@ -0,0 +1,39 @@ + + + + Azure AD Multitenant + id_token_code + False + upn:user_id upn:email + https://login.microsoftonline.com/organizations/oauth2/v2.0/authorize + profile openid + https://login.microsoftonline.com/organizations/oauth2/v2.0/token + https://login.microsoftonline.com/organizations/discovery/v2.0/keys + fa fa-fw fa-windows + Log in with Microsoft + + + Azure AD Single Tenant + id_token_code + False + upn:user_id upn:email + https://login.microsoftonline.com/{tenant_id}/oauth2/v2.0/authorize + profile openid + https://login.microsoftonline.com/{tenant_id}/oauth2/v2.0/token + https://login.microsoftonline.com/{tenant_id}/discovery/v2.0/keys + fa fa-fw fa-windows + Log in with Microsoft + + diff --git a/demo/local_keycloak.xml b/demo/local_keycloak.xml new file mode 100644 index 0000000..919754d --- /dev/null +++ b/demo/local_keycloak.xml @@ -0,0 +1,20 @@ + + + keycloak:8080 on localhost + id_token_code + auth_oidc-test + preferred_username:user_id + keycloak:8080 on localhost + + openid email + http://localhost:8080/auth/realms/master/protocol/openid-connect/auth + http://localhost:8080/auth/realms/master/protocol/openid-connect/token + http://localhost:8080/auth/realms/master/protocol/openid-connect/certs + + diff --git a/i18n/auth_oidc.pot b/i18n/auth_oidc.pot new file mode 100644 index 0000000..d5aa8dd --- /dev/null +++ b/i18n/auth_oidc.pot @@ -0,0 +1,119 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * auth_oidc +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 18.0\n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: auth_oidc +#: model:ir.model.fields,field_description:auth_oidc.field_auth_oauth_provider__flow +msgid "Auth Flow" +msgstr "" + +#. module: auth_oidc +#: model:ir.model.fields,field_description:auth_oidc.field_auth_oauth_provider__client_secret +msgid "Client Secret" +msgstr "" + +#. module: auth_oidc +#: model:ir.model.fields,field_description:auth_oidc.field_auth_oauth_provider__code_verifier +msgid "Code Verifier" +msgstr "" + +#. module: auth_oidc +#: model:ir.model.fields,field_description:auth_oidc.field_auth_oauth_provider__jwks_uri +msgid "JWKS URL" +msgstr "" + +#. module: auth_oidc +#: model:auth.oauth.provider,body:auth_oidc.provider_azuread_multi +#: model:auth.oauth.provider,body:auth_oidc.provider_azuread_single +msgid "Log in with Microsoft" +msgstr "" + +#. module: auth_oidc +#: model:ir.model.fields.selection,name:auth_oidc.selection__auth_oauth_provider__flow__access_token +msgid "OAuth2" +msgstr "" + +#. module: auth_oidc +#: model:ir.model,name:auth_oidc.model_auth_oauth_provider +msgid "OAuth2 provider" +msgstr "" + +#. module: auth_oidc +#: model:ir.model.fields.selection,name:auth_oidc.selection__auth_oauth_provider__flow__id_token_code +msgid "OpenID Connect (authorization code flow)" +msgstr "" + +#. module: auth_oidc +#: model:ir.model.fields.selection,name:auth_oidc.selection__auth_oauth_provider__flow__id_token +msgid "OpenID Connect (implicit flow, not recommended)" +msgstr "" + +#. module: auth_oidc +#: model:ir.model.fields,help:auth_oidc.field_auth_oauth_provider__token_endpoint +msgid "Required for OpenID Connect authorization code flow." +msgstr "" + +#. module: auth_oidc +#: model:ir.model.fields,help:auth_oidc.field_auth_oauth_provider__jwks_uri +msgid "Required for OpenID Connect." +msgstr "" + +#. module: auth_oidc +#: model:ir.model.fields,help:auth_oidc.field_auth_oauth_provider__token_map +msgid "" +"Some Oauth providers don't map keys in their responses exactly as required." +" It is important to ensure user_id and email at least are mapped. For " +"OpenID Connect user_id is the sub key in the standard." +msgstr "" + +#. module: auth_oidc +#: model:ir.model.fields,field_description:auth_oidc.field_auth_oauth_provider__token_map +msgid "Token Map" +msgstr "" + +#. module: auth_oidc +#: model:ir.model.fields,field_description:auth_oidc.field_auth_oauth_provider__token_endpoint +msgid "Token URL" +msgstr "" + +#. module: auth_oidc +#: model:ir.model.fields,help:auth_oidc.field_auth_oauth_provider__code_verifier +msgid "Used for PKCE." +msgstr "" + +#. module: auth_oidc +#: model:ir.model.fields,help:auth_oidc.field_auth_oauth_provider__client_secret +msgid "" +"Used in OpenID Connect authorization code flow for confidential clients." +msgstr "" + +#. module: auth_oidc +#: model:ir.model,name:auth_oidc.model_res_users +msgid "User" +msgstr "" + +#. module: auth_oidc +#: model:ir.model.fields,field_description:auth_oidc.field_auth_oauth_provider__validation_endpoint +msgid "UserInfo URL" +msgstr "" + +#. module: auth_oidc +#: model_terms:ir.ui.view,arch_db:auth_oidc.view_oidc_provider_form +msgid "e.g from:to upn:email sub:user_id" +msgstr "" + +#. module: auth_oidc +#: model:auth.oauth.provider,body:auth_oidc.local_keycloak +msgid "keycloak:8080 on localhost" +msgstr "" diff --git a/i18n/es.po b/i18n/es.po new file mode 100644 index 0000000..6cda934 --- /dev/null +++ b/i18n/es.po @@ -0,0 +1,128 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * auth_oidc +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 16.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2023-10-15 19:36+0000\n" +"Last-Translator: Ivorra78 \n" +"Language-Team: none\n" +"Language: es\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 4.17\n" + +#. module: auth_oidc +#: model:ir.model.fields,field_description:auth_oidc.field_auth_oauth_provider__flow +msgid "Auth Flow" +msgstr "Flujo de autenticación" + +#. module: auth_oidc +#: model:ir.model.fields,field_description:auth_oidc.field_auth_oauth_provider__client_secret +msgid "Client Secret" +msgstr "Secreto del cliente" + +#. module: auth_oidc +#: model:ir.model.fields,field_description:auth_oidc.field_auth_oauth_provider__code_verifier +msgid "Code Verifier" +msgstr "Verificador del código" + +#. module: auth_oidc +#: model:ir.model.fields,field_description:auth_oidc.field_auth_oauth_provider__jwks_uri +msgid "JWKS URL" +msgstr "URL JWKS" + +#. module: auth_oidc +#: model:auth.oauth.provider,body:auth_oidc.provider_azuread_multi +#: model:auth.oauth.provider,body:auth_oidc.provider_azuread_single +msgid "Log in with Microsoft" +msgstr "Iniciar sesión con Microsoft" + +#. module: auth_oidc +#: model:ir.model.fields.selection,name:auth_oidc.selection__auth_oauth_provider__flow__access_token +msgid "OAuth2" +msgstr "OAuth2" + +#. module: auth_oidc +#: model:ir.model,name:auth_oidc.model_auth_oauth_provider +msgid "OAuth2 provider" +msgstr "Proveedor OAuth2" + +#. module: auth_oidc +#: model:ir.model.fields.selection,name:auth_oidc.selection__auth_oauth_provider__flow__id_token_code +msgid "OpenID Connect (authorization code flow)" +msgstr "OpenID Connect (flujo de código de autorización)" + +#. module: auth_oidc +#: model:ir.model.fields.selection,name:auth_oidc.selection__auth_oauth_provider__flow__id_token +msgid "OpenID Connect (implicit flow, not recommended)" +msgstr "OpenID Connect (flujo implícito, no recomendado)" + +#. module: auth_oidc +#: model:ir.model.fields,help:auth_oidc.field_auth_oauth_provider__token_endpoint +msgid "Required for OpenID Connect authorization code flow." +msgstr "Necesario para el flujo de código de autorización de OpenID Connect." + +#. module: auth_oidc +#: model:ir.model.fields,help:auth_oidc.field_auth_oauth_provider__jwks_uri +msgid "Required for OpenID Connect." +msgstr "Requerido para OpenID Connect." + +#. module: auth_oidc +#: model:ir.model.fields,help:auth_oidc.field_auth_oauth_provider__token_map +msgid "" +"Some Oauth providers don't map keys in their responses exactly as required. " +"It is important to ensure user_id and email at least are mapped. For OpenID " +"Connect user_id is the sub key in the standard." +msgstr "" +"Algunos proveedores de Oauth no mapean las claves en sus respuestas " +"exactamente como se requiere. Es importante asegurarse de que al menos " +"user_id y email están mapeados. Para OpenID Connect user_id es la subclave " +"en el estándar." + +#. module: auth_oidc +#: model:ir.model.fields,field_description:auth_oidc.field_auth_oauth_provider__token_map +msgid "Token Map" +msgstr "Mapa de Símbolos" + +#. module: auth_oidc +#: model:ir.model.fields,field_description:auth_oidc.field_auth_oauth_provider__token_endpoint +msgid "Token URL" +msgstr "URL de la ficha" + +#. module: auth_oidc +#: model:ir.model.fields,help:auth_oidc.field_auth_oauth_provider__code_verifier +msgid "Used for PKCE." +msgstr "Utilizado para PKCE." + +#. module: auth_oidc +#: model:ir.model.fields,help:auth_oidc.field_auth_oauth_provider__client_secret +msgid "" +"Used in OpenID Connect authorization code flow for confidential clients." +msgstr "" +"Se utiliza en el flujo de código de autorización de OpenID Connect para " +"clientes confidenciales." + +#. module: auth_oidc +#: model:ir.model,name:auth_oidc.model_res_users +msgid "User" +msgstr "Usuario" + +#. module: auth_oidc +#: model:ir.model.fields,field_description:auth_oidc.field_auth_oauth_provider__validation_endpoint +msgid "UserInfo URL" +msgstr "URL de información del usuario" + +#. module: auth_oidc +#: model_terms:ir.ui.view,arch_db:auth_oidc.view_oidc_provider_form +msgid "e.g from:to upn:email sub:user_id" +msgstr "p.ej. from:to upn:email sub:user_id" + +#. module: auth_oidc +#: model:auth.oauth.provider,body:auth_oidc.local_keycloak +msgid "keycloak:8080 on localhost" +msgstr "keycloak:8080 en el servidor local" diff --git a/i18n/it.po b/i18n/it.po new file mode 100644 index 0000000..27ba50d --- /dev/null +++ b/i18n/it.po @@ -0,0 +1,127 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * auth_oidc +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 16.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2024-01-05 10:34+0000\n" +"Last-Translator: mymage \n" +"Language-Team: none\n" +"Language: it\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 4.17\n" + +#. module: auth_oidc +#: model:ir.model.fields,field_description:auth_oidc.field_auth_oauth_provider__flow +msgid "Auth Flow" +msgstr "Flusso atorizzazione" + +#. module: auth_oidc +#: model:ir.model.fields,field_description:auth_oidc.field_auth_oauth_provider__client_secret +msgid "Client Secret" +msgstr "Chiave segreta client" + +#. module: auth_oidc +#: model:ir.model.fields,field_description:auth_oidc.field_auth_oauth_provider__code_verifier +msgid "Code Verifier" +msgstr "Verificatore codice" + +#. module: auth_oidc +#: model:ir.model.fields,field_description:auth_oidc.field_auth_oauth_provider__jwks_uri +msgid "JWKS URL" +msgstr "URL JWKS" + +#. module: auth_oidc +#: model:auth.oauth.provider,body:auth_oidc.provider_azuread_multi +#: model:auth.oauth.provider,body:auth_oidc.provider_azuread_single +msgid "Log in with Microsoft" +msgstr "Accedi con Mcrosoft" + +#. module: auth_oidc +#: model:ir.model.fields.selection,name:auth_oidc.selection__auth_oauth_provider__flow__access_token +msgid "OAuth2" +msgstr "OAuth2" + +#. module: auth_oidc +#: model:ir.model,name:auth_oidc.model_auth_oauth_provider +msgid "OAuth2 provider" +msgstr "Provider OAuth2" + +#. module: auth_oidc +#: model:ir.model.fields.selection,name:auth_oidc.selection__auth_oauth_provider__flow__id_token_code +msgid "OpenID Connect (authorization code flow)" +msgstr "OpenID Connect (flusso codice autorizzazione)" + +#. module: auth_oidc +#: model:ir.model.fields.selection,name:auth_oidc.selection__auth_oauth_provider__flow__id_token +msgid "OpenID Connect (implicit flow, not recommended)" +msgstr "OpenID Connect (flusso implicito, non raccomandato)" + +#. module: auth_oidc +#: model:ir.model.fields,help:auth_oidc.field_auth_oauth_provider__token_endpoint +msgid "Required for OpenID Connect authorization code flow." +msgstr "Richiesto per flusso codice atorizzazione OpenID Connect." + +#. module: auth_oidc +#: model:ir.model.fields,help:auth_oidc.field_auth_oauth_provider__jwks_uri +msgid "Required for OpenID Connect." +msgstr "Richiesto per OpenID Connect." + +#. module: auth_oidc +#: model:ir.model.fields,help:auth_oidc.field_auth_oauth_provider__token_map +msgid "" +"Some Oauth providers don't map keys in their responses exactly as required. " +"It is important to ensure user_id and email at least are mapped. For OpenID " +"Connect user_id is the sub key in the standard." +msgstr "" +"Alcuni Provider Oauth non mappano le chiavi nelle loro risposte esattamente " +"come richiesto. È importante assicurare che almeno user_id ed e-mail siano " +"mappati. Per OpenID Connect user_id è la sotto-chiave nello standard." + +#. module: auth_oidc +#: model:ir.model.fields,field_description:auth_oidc.field_auth_oauth_provider__token_map +msgid "Token Map" +msgstr "Mappa token" + +#. module: auth_oidc +#: model:ir.model.fields,field_description:auth_oidc.field_auth_oauth_provider__token_endpoint +msgid "Token URL" +msgstr "URL token" + +#. module: auth_oidc +#: model:ir.model.fields,help:auth_oidc.field_auth_oauth_provider__code_verifier +msgid "Used for PKCE." +msgstr "Utilizzato per PKCE." + +#. module: auth_oidc +#: model:ir.model.fields,help:auth_oidc.field_auth_oauth_provider__client_secret +msgid "" +"Used in OpenID Connect authorization code flow for confidential clients." +msgstr "" +"Utilizzato nel flusso codice autorizzazione OpenID Connect per client " +"riservati." + +#. module: auth_oidc +#: model:ir.model,name:auth_oidc.model_res_users +msgid "User" +msgstr "Utente" + +#. module: auth_oidc +#: model:ir.model.fields,field_description:auth_oidc.field_auth_oauth_provider__validation_endpoint +msgid "UserInfo URL" +msgstr "URL info utente" + +#. module: auth_oidc +#: model_terms:ir.ui.view,arch_db:auth_oidc.view_oidc_provider_form +msgid "e.g from:to upn:email sub:user_id" +msgstr "es. from:to upn:email sub:user_id" + +#. module: auth_oidc +#: model:auth.oauth.provider,body:auth_oidc.local_keycloak +msgid "keycloak:8080 on localhost" +msgstr "keycloak:8080 su localhost" diff --git a/i18n/zh_CN.po b/i18n/zh_CN.po new file mode 100644 index 0000000..4914868 --- /dev/null +++ b/i18n/zh_CN.po @@ -0,0 +1,124 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * auth_oidc +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 17.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2024-07-03 10:47+0000\n" +"Last-Translator: xtanuiha \n" +"Language-Team: none\n" +"Language: zh_CN\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=1; plural=0;\n" +"X-Generator: Weblate 4.17\n" + +#. module: auth_oidc +#: model:ir.model.fields,field_description:auth_oidc.field_auth_oauth_provider__flow +msgid "Auth Flow" +msgstr "认证流程" + +#. module: auth_oidc +#: model:ir.model.fields,field_description:auth_oidc.field_auth_oauth_provider__client_secret +msgid "Client Secret" +msgstr "客户端密钥" + +#. module: auth_oidc +#: model:ir.model.fields,field_description:auth_oidc.field_auth_oauth_provider__code_verifier +msgid "Code Verifier" +msgstr "代码验证器" + +#. module: auth_oidc +#: model:ir.model.fields,field_description:auth_oidc.field_auth_oauth_provider__jwks_uri +msgid "JWKS URL" +msgstr "JWKS 网址" + +#. module: auth_oidc +#: model:auth.oauth.provider,body:auth_oidc.provider_azuread_multi +#: model:auth.oauth.provider,body:auth_oidc.provider_azuread_single +msgid "Log in with Microsoft" +msgstr "使用 Microsoft 登录" + +#. module: auth_oidc +#: model:ir.model.fields.selection,name:auth_oidc.selection__auth_oauth_provider__flow__access_token +msgid "OAuth2" +msgstr "OAuth2" + +#. module: auth_oidc +#: model:ir.model,name:auth_oidc.model_auth_oauth_provider +msgid "OAuth2 provider" +msgstr "OAuth2 提供者" + +#. module: auth_oidc +#: model:ir.model.fields.selection,name:auth_oidc.selection__auth_oauth_provider__flow__id_token_code +msgid "OpenID Connect (authorization code flow)" +msgstr "OpenID Connect(授权码流程)" + +#. module: auth_oidc +#: model:ir.model.fields.selection,name:auth_oidc.selection__auth_oauth_provider__flow__id_token +msgid "OpenID Connect (implicit flow, not recommended)" +msgstr "OpenID Connect(隐式流程,不推荐)" + +#. module: auth_oidc +#: model:ir.model.fields,help:auth_oidc.field_auth_oauth_provider__token_endpoint +msgid "Required for OpenID Connect authorization code flow." +msgstr "OpenID Connect 授权码流程所需。" + +#. module: auth_oidc +#: model:ir.model.fields,help:auth_oidc.field_auth_oauth_provider__jwks_uri +msgid "Required for OpenID Connect." +msgstr "OpenID Connect 所需。" + +#. module: auth_oidc +#: model:ir.model.fields,help:auth_oidc.field_auth_oauth_provider__token_map +msgid "" +"Some Oauth providers don't map keys in their responses exactly as required. " +"It is important to ensure user_id and email at least are mapped. For OpenID " +"Connect user_id is the sub key in the standard." +msgstr "" +"一些 OAuth 提供者在其响应中并没有完全按照要求映射键。至少需要确保 user_id 和 " +"email 被映射。对于 OpenID Connect,user_id 是标准中的 sub 键。" + +#. module: auth_oidc +#: model:ir.model.fields,field_description:auth_oidc.field_auth_oauth_provider__token_map +msgid "Token Map" +msgstr "令牌映射" + +#. module: auth_oidc +#: model:ir.model.fields,field_description:auth_oidc.field_auth_oauth_provider__token_endpoint +msgid "Token URL" +msgstr "令牌网址" + +#. module: auth_oidc +#: model:ir.model.fields,help:auth_oidc.field_auth_oauth_provider__code_verifier +msgid "Used for PKCE." +msgstr "用于 PKCE。" + +#. module: auth_oidc +#: model:ir.model.fields,help:auth_oidc.field_auth_oauth_provider__client_secret +msgid "" +"Used in OpenID Connect authorization code flow for confidential clients." +msgstr "在 OpenID Connect 授权码流程中用于保密客户端。" + +#. module: auth_oidc +#: model:ir.model,name:auth_oidc.model_res_users +msgid "User" +msgstr "用户" + +#. module: auth_oidc +#: model:ir.model.fields,field_description:auth_oidc.field_auth_oauth_provider__validation_endpoint +msgid "UserInfo URL" +msgstr "用户信息网址" + +#. module: auth_oidc +#: model_terms:ir.ui.view,arch_db:auth_oidc.view_oidc_provider_form +msgid "e.g from:to upn:email sub:user_id" +msgstr "例如 from:to upn:email sub:user_id" + +#. module: auth_oidc +#: model:auth.oauth.provider,body:auth_oidc.local_keycloak +msgid "keycloak:8080 on localhost" +msgstr "localhost 上的 keycloak:8080" diff --git a/models/__init__.py b/models/__init__.py new file mode 100644 index 0000000..ebc8e5b --- /dev/null +++ b/models/__init__.py @@ -0,0 +1,5 @@ +# Copyright 2016 ICTSTUDIO +# License: AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) + +from . import auth_oauth_provider +from . import res_users diff --git a/models/__pycache__/__init__.cpython-312.pyc b/models/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..17249cc37087eee3cbe4a884511e6ca91c025307 GIT binary patch literal 225 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 literal 0 HcmV?d00001 diff --git a/models/auth_oauth_provider.py b/models/auth_oauth_provider.py new file mode 100644 index 0000000..ac498a7 --- /dev/null +++ b/models/auth_oauth_provider.py @@ -0,0 +1,106 @@ +# Copyright 2016 ICTSTUDIO +# Copyright 2021 ACSONE SA/NV +# License: AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) + +import logging +import secrets + +import requests + +from odoo import fields, models, tools + +try: + from jose import jwt + from jose.exceptions import JWSError, JWTError +except ImportError: + logging.getLogger(__name__).debug("jose library not installed") + + +class AuthOauthProvider(models.Model): + _inherit = "auth.oauth.provider" + + flow = fields.Selection( + [ + ("access_token", "OAuth2"), + ("id_token_code", "OpenID Connect (authorization code flow)"), + ("id_token", "OpenID Connect (implicit flow, not recommended)"), + ], + string="Auth Flow", + required=True, + default="access_token", + ) + token_map = fields.Char( + help="Some Oauth providers don't map keys in their responses " + "exactly as required. It is important to ensure user_id and " + "email at least are mapped. For OpenID Connect user_id is " + "the sub key in the standard." + ) + client_secret = fields.Char( + help="Used in OpenID Connect authorization code flow for confidential clients.", + ) + code_verifier = fields.Char( + default=lambda self: secrets.token_urlsafe(32), help="Used for PKCE." + ) + validation_endpoint = fields.Char(required=False) + token_endpoint = fields.Char( + string="Token URL", help="Required for OpenID Connect authorization code flow." + ) + jwks_uri = fields.Char(string="JWKS URL", help="Required for OpenID Connect.") + + @tools.ormcache("self.jwks_uri", "kid") + def _get_keys(self, kid): + r = requests.get(self.jwks_uri, timeout=10) + r.raise_for_status() + response = r.json() + # the keys returned here should follow + # JWS Notes on Key Selection + # https://datatracker.ietf.org/doc/html/draft-ietf-jose-json-web-signature#appendix-D + return [ + key + for key in response["keys"] + if kid is None or key.get("kid", None) == kid + ] + + def _map_token_values(self, res): + if self.token_map: + for pair in self.token_map.split(" "): + from_key, to_key = (k.strip() for k in pair.split(":", 1)) + if to_key not in res: + res[to_key] = res.get(from_key, "") + return res + + def _parse_id_token(self, id_token, access_token): + self.ensure_one() + res = {} + header = jwt.get_unverified_header(id_token) + res.update(self._decode_id_token(access_token, id_token, header.get("kid"))) + res.update(self._map_token_values(res)) + return res + + def _decode_id_token(self, access_token, id_token, kid): + keys = self._get_keys(kid) + if len(keys) > 1 and kid is None: + # https://openid.net/specs/openid-connect-core-1_0.html#rfc.section.10.1 + # If there are multiple keys in the referenced JWK Set document, a kid + # value MUST be provided in the JOSE Header. + raise JWTError( + "OpenID Connect requires kid to be set if there is more" + " than one key in the JWKS" + ) + error = None + # we accept multiple keys with the same kid in case a key gets rotated. + for key in keys: + try: + values = jwt.decode( + id_token, + key, + algorithms=["RS256"], + audience=self.client_id, + access_token=access_token, + ) + return values + except (JWTError, JWSError) as e: + error = e + if error: + raise error + return {} diff --git a/models/res_users.py b/models/res_users.py new file mode 100644 index 0000000..1684480 --- /dev/null +++ b/models/res_users.py @@ -0,0 +1,82 @@ +# Copyright 2016 ICTSTUDIO +# Copyright 2021 ACSONE SA/NV +# License: AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) + +import logging + +import requests + +from odoo import api, models +from odoo.exceptions import AccessDenied +from odoo.http import request + +_logger = logging.getLogger(__name__) + + +class ResUsers(models.Model): + _inherit = "res.users" + + def _auth_oauth_get_tokens_implicit_flow(self, oauth_provider, params): + # https://openid.net/specs/openid-connect-core-1_0.html#ImplicitAuthResponse + return params.get("access_token"), params.get("id_token") + + def _auth_oauth_get_tokens_auth_code_flow(self, oauth_provider, params): + # https://openid.net/specs/openid-connect-core-1_0.html#AuthResponse + code = params.get("code") + # https://openid.net/specs/openid-connect-core-1_0.html#TokenRequest + auth = None + if oauth_provider.client_secret: + auth = (oauth_provider.client_id, oauth_provider.client_secret) + response = requests.post( + oauth_provider.token_endpoint, + data=dict( + client_id=oauth_provider.client_id, + grant_type="authorization_code", + code=code, + code_verifier=oauth_provider.code_verifier, # PKCE + redirect_uri=request.httprequest.url_root + "auth_oauth/signin", + ), + auth=auth, + timeout=10, + ) + response.raise_for_status() + response_json = response.json() + # https://openid.net/specs/openid-connect-core-1_0.html#TokenResponse + return response_json.get("access_token"), response_json.get("id_token") + + @api.model + def auth_oauth(self, provider, params): + oauth_provider = self.env["auth.oauth.provider"].browse(provider) + if oauth_provider.flow == "id_token": + access_token, id_token = self._auth_oauth_get_tokens_implicit_flow( + oauth_provider, params + ) + elif oauth_provider.flow == "id_token_code": + access_token, id_token = self._auth_oauth_get_tokens_auth_code_flow( + oauth_provider, params + ) + else: + return super().auth_oauth(provider, params) + if not access_token: + _logger.error("No access_token in response.") + raise AccessDenied() + if not id_token: + _logger.error("No id_token in response.") + raise AccessDenied() + validation = oauth_provider._parse_id_token(id_token, access_token) + # required check + if "sub" in validation and "user_id" not in validation: + # set user_id for auth_oauth, user_id is not an OpenID Connect standard + # claim: + # https://openid.net/specs/openid-connect-core-1_0.html#StandardClaims + validation["user_id"] = validation["sub"] + elif not validation.get("user_id"): + _logger.error("user_id claim not found in id_token (after mapping).") + raise AccessDenied() + # retrieve and sign in user + params["access_token"] = access_token + login = self._auth_oauth_signin(provider, validation, params) + if not login: + raise AccessDenied() + # return user credentials + return (self.env.cr.dbname, login, access_token) diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..4231d0c --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["whool"] +build-backend = "whool.buildapi" diff --git a/readme/CONFIGURE.md b/readme/CONFIGURE.md new file mode 100644 index 0000000..275e4c0 --- /dev/null +++ b/readme/CONFIGURE.md @@ -0,0 +1,72 @@ +## Setup for Microsoft Azure + +Example configuration with OpenID Connect authorization code flow. + +1. configure a new web application in Azure with OpenID and code flow (see +the [provider +documentation](https://docs.microsoft.com/en-us/powerapps/maker/portals/configure/configure-openid-provider))) + +2. in this application the redirect url must be be "\/auth_oauth/signin" and of course this URL should be reachable +from Azure + +3. create a new authentication provider in Odoo with the following +parameters (see the [portal +documentation](https://docs.microsoft.com/en-us/powerapps/maker/portals/configure/configure-openid-settings) +for more information): + +![image](../static/description/oauth-microsoft_azure-api_permissions.png) + +![image](../static/description/oauth-microsoft_azure-optional_claims.png) + +Single tenant provider limits the access to user of your tenant, while +Multitenants allow access for all AzureAD users, so user of foreign +companies can use their AzureAD login without an guest account. + +- Provider Name: Azure AD Single Tenant +- Client ID: Application (client) id +- Client Secret: Client secret +- Allowed: yes + +or + +- Provider Name: Azure AD Multitenant +- Client ID: Application (client) id +- Client Secret: Client secret +- Allowed: yes +- replace {tenant_id} in urls with your Azure tenant id + +![image](../static/description/odoo-azure_ad_multitenant.png) + +## Setup for Keycloak + +Example configuration with OpenID Connect authorization code flow. + +In Keycloak: + +1. configure a new Client +2. make sure Authorization Code Flow is +Enabled. +3. configure the client Access Type as "confidential" and take +note of the client secret in the Credentials tab +4. configure the +redirect url to be "\/auth_oauth/signin" + +In Odoo, create a new Oauth Provider with the following parameters: + +- Provider name: Keycloak (or any name you like that identify your + keycloak provider) +- Auth Flow: OpenID Connect (authorization code flow) +- Client ID: the same Client ID you entered when configuring the client + in Keycloak +- Client Secret: found in keycloak on the client Credentials tab +- Allowed: yes +- Body: the link text to appear on the login page, such as Login with + Keycloak +- Scope: openid email +- Authentication URL: The "authorization_endpoint" URL found in the + OpenID Endpoint Configuration of your Keycloak realm +- Token URL: The "token_endpoint" URL found in the OpenID Endpoint + Configuration of your Keycloak realm +- JWKS URL: The "jwks_uri" URL found in the OpenID Endpoint + Configuration of your Keycloak realm diff --git a/readme/CONTRIBUTORS.md b/readme/CONTRIBUTORS.md new file mode 100644 index 0000000..8bdbc1d --- /dev/null +++ b/readme/CONTRIBUTORS.md @@ -0,0 +1,4 @@ +- Alexandre Fayolle \<\> +- Stéphane Bidoul \<\> +- David Jaen \<\> +- Andreas Perhab \<\> diff --git a/readme/DESCRIPTION.md b/readme/DESCRIPTION.md new file mode 100644 index 0000000..3677c8b --- /dev/null +++ b/readme/DESCRIPTION.md @@ -0,0 +1,5 @@ +This module allows users to login through an OpenID Connect provider +using the authorization code flow or implicit flow. + +Note the implicit flow is not recommended because it exposes access +tokens to the browser and in http logs. diff --git a/readme/HISTORY.md b/readme/HISTORY.md new file mode 100644 index 0000000..48ad92f --- /dev/null +++ b/readme/HISTORY.md @@ -0,0 +1,39 @@ +## 18.0.1.0.0 2024-10-09 + +- Odoo 18 migration + +## 17.0.1.0.0 2024-03-20 + +- Odoo 17 migration + +## 16.0.1.1.0 2024-02-28 + +- Forward port OpenID Connect fixes from 15.0 to 16.0 + +## 16.0.1.0.2 2023-11-16 + +- Readme link updates + +## 16.0.1.0.1 2023-10-09 + +- Add AzureAD code flow provider + +## 16.0.1.0.0 2023-01-27 + +- Odoo 16 migration + +## 15.0.1.0.0 2023-01-06 + +- Odoo 15 migration + +## 14.0.1.0.0 2021-12-10 + +- Odoo 14 migration + +## 13.0.1.0.0 2020-04-10 + +- Odoo 13 migration, add authorization code flow. + +## 10.0.1.0.0 2018-10-05 + +- Initial implementation diff --git a/readme/INSTALL.md b/readme/INSTALL.md new file mode 100644 index 0000000..37af7b9 --- /dev/null +++ b/readme/INSTALL.md @@ -0,0 +1,3 @@ +This module depends on the +[python-jose](https://pypi.org/project/python-jose/) library, not to be +confused with `jose` which is also available on PyPI. diff --git a/readme/ROADMAP.md b/readme/ROADMAP.md new file mode 100644 index 0000000..712da2f --- /dev/null +++ b/readme/ROADMAP.md @@ -0,0 +1,4 @@ +- When going to the login screen, check for a existing token and do a + direct login without the clicking on the SSO link +- When doing a logout an extra option to also logout at the SSO + provider. diff --git a/readme/USAGE.md b/readme/USAGE.md new file mode 100644 index 0000000..0fa7425 --- /dev/null +++ b/readme/USAGE.md @@ -0,0 +1 @@ +On the login page, click on the authentication provider you configured. diff --git a/static/description/icon.png b/static/description/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..06a05ebd480fdd359c2600a27660aced7a5a48a0 GIT binary patch literal 2780 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 aj#6WMOub(s7;@l40rob|*437% + + + + +Authentication OpenID Connect + + + +
+

Authentication OpenID Connect

+ + +

Beta License: AGPL-3 OCA/server-auth Translate me on Weblate Try me on Runboat

+

This module allows users to login through an OpenID Connect provider +using the authorization code flow or implicit flow.

+

Note the implicit flow is not recommended because it exposes access +tokens to the browser and in http logs.

+

Table of contents

+ +
+

Installation

+

This module depends on the +python-jose library, not to +be confused with jose which is also available on PyPI.

+
+
+

Configuration

+
+

Setup for Microsoft Azure

+

Example configuration with OpenID Connect authorization code flow.

+
    +
  1. configure a new web application in Azure with OpenID and code flow +(see the provider +documentation))
  2. +
  3. in this application the redirect url must be be “<url of your +server>/auth_oauth/signin” and of course this URL should be reachable +from Azure
  4. +
  5. create a new authentication provider in Odoo with the following +parameters (see the portal +documentation +for more information):
  6. +
+

image

+

image1

+

Single tenant provider limits the access to user of your tenant, while +Multitenants allow access for all AzureAD users, so user of foreign +companies can use their AzureAD login without an guest account.

+
    +
  • Provider Name: Azure AD Single Tenant
  • +
  • Client ID: Application (client) id
  • +
  • Client Secret: Client secret
  • +
  • Allowed: yes
  • +
+

or

+
    +
  • Provider Name: Azure AD Multitenant
  • +
  • Client ID: Application (client) id
  • +
  • Client Secret: Client secret
  • +
  • Allowed: yes
  • +
  • replace {tenant_id} in urls with your Azure tenant id
  • +
+

image2

+
+
+

Setup for Keycloak

+

Example configuration with OpenID Connect authorization code flow.

+

In Keycloak:

+
    +
  1. configure a new Client
  2. +
  3. make sure Authorization Code Flow is Enabled.
  4. +
  5. configure the client Access Type as “confidential” and take note of +the client secret in the Credentials tab
  6. +
  7. configure the redirect url to be “<url of your +server>/auth_oauth/signin”
  8. +
+

In Odoo, create a new Oauth Provider with the following parameters:

+
    +
  • Provider name: Keycloak (or any name you like that identify your +keycloak provider)
  • +
  • Auth Flow: OpenID Connect (authorization code flow)
  • +
  • Client ID: the same Client ID you entered when configuring the client +in Keycloak
  • +
  • Client Secret: found in keycloak on the client Credentials tab
  • +
  • Allowed: yes
  • +
  • Body: the link text to appear on the login page, such as Login with +Keycloak
  • +
  • Scope: openid email
  • +
  • Authentication URL: The “authorization_endpoint” URL found in the +OpenID Endpoint Configuration of your Keycloak realm
  • +
  • Token URL: The “token_endpoint” URL found in the OpenID Endpoint +Configuration of your Keycloak realm
  • +
  • JWKS URL: The “jwks_uri” URL found in the OpenID Endpoint +Configuration of your Keycloak realm
  • +
+
+
+
+

Usage

+

On the login page, click on the authentication provider you configured.

+
+
+

Known issues / Roadmap

+
    +
  • When going to the login screen, check for a existing token and do a +direct login without the clicking on the SSO link
  • +
  • When doing a logout an extra option to also logout at the SSO +provider.
  • +
+
+
+

Changelog

+
+

18.0.1.0.0 2024-10-09

+
    +
  • Odoo 18 migration
  • +
+
+
+

17.0.1.0.0 2024-03-20

+
    +
  • Odoo 17 migration
  • +
+
+
+

16.0.1.1.0 2024-02-28

+
    +
  • Forward port OpenID Connect fixes from 15.0 to 16.0
  • +
+
+
+

16.0.1.0.2 2023-11-16

+
    +
  • Readme link updates
  • +
+
+
+

16.0.1.0.1 2023-10-09

+
    +
  • Add AzureAD code flow provider
  • +
+
+
+

16.0.1.0.0 2023-01-27

+
    +
  • Odoo 16 migration
  • +
+
+
+

15.0.1.0.0 2023-01-06

+
    +
  • Odoo 15 migration
  • +
+
+
+

14.0.1.0.0 2021-12-10

+
    +
  • Odoo 14 migration
  • +
+
+
+

13.0.1.0.0 2020-04-10

+
    +
  • Odoo 13 migration, add authorization code flow.
  • +
+
+
+

10.0.1.0.0 2018-10-05

+
    +
  • Initial implementation
  • +
+
+
+
+

Bug Tracker

+

Bugs are tracked on GitHub Issues. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +feedback.

+

Do not contact contributors directly about support or help with technical issues.

+
+
+

Credits

+
+

Authors

+
    +
  • ICTSTUDIO
  • +
  • André Schenkels
  • +
  • ACSONE SA/NV
  • +
+
+
+

Contributors

+ +
+
+

Maintainers

+

This module is maintained by the OCA.

+ +Odoo Community Association + +

OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use.

+

Current maintainer:

+

sbidoul

+

This module is part of the OCA/server-auth project on GitHub.

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
+
+
+ + diff --git a/static/description/oauth-microsoft_azure-api_permissions.png b/static/description/oauth-microsoft_azure-api_permissions.png new file mode 100644 index 0000000000000000000000000000000000000000..7f28000a955b9b65f95375434bb005df292dface GIT binary patch literal 59625 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 XuS&YCK*%*a)tVSv{Jrc~*WdpG4xM*q literal 0 HcmV?d00001 diff --git a/static/description/odoo-azure_ad_multitenant.png b/static/description/odoo-azure_ad_multitenant.png new file mode 100644 index 0000000000000000000000000000000000000000..31033f38161a9641286ed9e5371f2fc8a0d1142e GIT binary patch literal 37344 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%0AUJjV7pxVH{>r*b|-qoS&+x1@btmT5=g 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 literal 0 HcmV?d00001 diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e603d99 --- /dev/null +++ b/tests/__init__.py @@ -0,0 +1 @@ +from . import test_auth_oidc_auth_code diff --git a/tests/keycloak/keycloak-config.json b/tests/keycloak/keycloak-config.json new file mode 100644 index 0000000..5a0456b --- /dev/null +++ b/tests/keycloak/keycloak-config.json @@ -0,0 +1,1997 @@ +{ + "id": "master", + "realm": "master", + "displayName": "Keycloak", + "displayNameHtml": "

Keycloak
", + "notBefore": 0, + "revokeRefreshToken": false, + "refreshTokenMaxReuse": 0, + "accessTokenLifespan": 60, + "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, + "enabled": true, + "sslRequired": "external", + "registrationAllowed": false, + "registrationEmailAsUsername": false, + "rememberMe": false, + "verifyEmail": false, + "loginWithEmailAllowed": true, + "duplicateEmailsAllowed": false, + "resetPasswordAllowed": false, + "editUsernameAllowed": false, + "bruteForceProtected": false, + "permanentLockout": false, + "maxFailureWaitSeconds": 900, + "minimumQuickLoginWaitSeconds": 60, + "waitIncrementSeconds": 60, + "quickLoginCheckMilliSeconds": 1000, + "maxDeltaTimeSeconds": 43200, + "failureFactor": 30, + "roles": { + "realm": [ + { + "id": "39f27ebe-139e-435b-840a-beb824d5d355", + "name": "admin", + "description": "${role_admin}", + "composite": true, + "composites": { + "realm": ["create-realm"], + "client": { + "master-realm": [ + "create-client", + "view-realm", + "view-events", + "manage-clients", + "query-clients", + "view-identity-providers", + "impersonation", + "manage-events", + "query-realms", + "query-groups", + "manage-authorization", + "query-users", + "view-authorization", + "manage-identity-providers", + "manage-users", + "view-clients", + "view-users", + "manage-realm" + ] + } + }, + "clientRole": false, + "containerId": "master", + "attributes": {} + }, + { + "id": "3fd38fac-f708-4783-b8e9-4e47963fc4bf", + "name": "offline_access", + "description": "${role_offline-access}", + "composite": false, + "clientRole": false, + "containerId": "master", + "attributes": {} + }, + { + "id": "4ac4a81b-0a30-41db-94ce-dbd621c331d2", + "name": "uma_authorization", + "description": "${role_uma_authorization}", + "composite": false, + "clientRole": false, + "containerId": "master", + "attributes": {} + }, + { + "id": "20b16986-2361-454c-af0b-81f403152ef8", + "name": "create-realm", + "description": "${role_create-realm}", + "composite": false, + "clientRole": false, + "containerId": "master", + "attributes": {} + } + ], + "client": { + "auth_oidc-test": [], + "security-admin-console": [], + "admin-cli": [], + "account-console": [], + "broker": [ + { + "id": "5fa108a0-2e5e-4e2e-8ee3-1317592517f8", + "name": "read-token", + "description": "${role_read-token}", + "composite": false, + "clientRole": true, + "containerId": "d1dd5ade-20bf-4d53-a371-a33f10bc1087", + "attributes": {} + } + ], + "master-realm": [ + { + "id": "0d062a1c-5165-4ea5-b550-55d02ca86226", + "name": "create-client", + "description": "${role_create-client}", + "composite": false, + "clientRole": true, + "containerId": "4e03d0f9-4c56-42c8-958c-337f6eede3ac", + "attributes": {} + }, + { + "id": "f5795bcb-ab2d-4a74-b954-51f335c21198", + "name": "view-realm", + "description": "${role_view-realm}", + "composite": false, + "clientRole": true, + "containerId": "4e03d0f9-4c56-42c8-958c-337f6eede3ac", + "attributes": {} + }, + { + "id": "1c0e3231-db03-4b03-961f-32308318f4f1", + "name": "view-events", + "description": "${role_view-events}", + "composite": false, + "clientRole": true, + "containerId": "4e03d0f9-4c56-42c8-958c-337f6eede3ac", + "attributes": {} + }, + { + "id": "ed9949d1-b11d-4742-b259-ee260f62f111", + "name": "manage-clients", + "description": "${role_manage-clients}", + "composite": false, + "clientRole": true, + "containerId": "4e03d0f9-4c56-42c8-958c-337f6eede3ac", + "attributes": {} + }, + { + "id": "b29b494a-9cd4-4410-8d16-207a3bb2e528", + "name": "query-clients", + "description": "${role_query-clients}", + "composite": false, + "clientRole": true, + "containerId": "4e03d0f9-4c56-42c8-958c-337f6eede3ac", + "attributes": {} + }, + { + "id": "8afdd284-070b-4d6f-9d21-d9917d8827af", + "name": "view-identity-providers", + "description": "${role_view-identity-providers}", + "composite": false, + "clientRole": true, + "containerId": "4e03d0f9-4c56-42c8-958c-337f6eede3ac", + "attributes": {} + }, + { + "id": "b5807416-f8f3-41ae-a29e-298ec3aae028", + "name": "impersonation", + "description": "${role_impersonation}", + "composite": false, + "clientRole": true, + "containerId": "4e03d0f9-4c56-42c8-958c-337f6eede3ac", + "attributes": {} + }, + { + "id": "6400b20c-a72b-4228-87d1-01b0d1315026", + "name": "manage-events", + "description": "${role_manage-events}", + "composite": false, + "clientRole": true, + "containerId": "4e03d0f9-4c56-42c8-958c-337f6eede3ac", + "attributes": {} + }, + { + "id": "6c3b14c3-0797-4e39-b5fa-b07d0e073e0e", + "name": "query-realms", + "description": "${role_query-realms}", + "composite": false, + "clientRole": true, + "containerId": "4e03d0f9-4c56-42c8-958c-337f6eede3ac", + "attributes": {} + }, + { + "id": "3fbf2279-9661-4cfc-b381-c7e4a8c459dc", + "name": "query-groups", + "description": "${role_query-groups}", + "composite": false, + "clientRole": true, + "containerId": "4e03d0f9-4c56-42c8-958c-337f6eede3ac", + "attributes": {} + }, + { + "id": "1b9e5572-34d5-4284-897c-0471544cf813", + "name": "manage-authorization", + "description": "${role_manage-authorization}", + "composite": false, + "clientRole": true, + "containerId": "4e03d0f9-4c56-42c8-958c-337f6eede3ac", + "attributes": {} + }, + { + "id": "a226e0fa-aa45-490d-9d64-78e88c3152cb", + "name": "query-users", + "description": "${role_query-users}", + "composite": false, + "clientRole": true, + "containerId": "4e03d0f9-4c56-42c8-958c-337f6eede3ac", + "attributes": {} + }, + { + "id": "d78736d4-250c-4012-a8ad-55b5c718a57a", + "name": "manage-identity-providers", + "description": "${role_manage-identity-providers}", + "composite": false, + "clientRole": true, + "containerId": "4e03d0f9-4c56-42c8-958c-337f6eede3ac", + "attributes": {} + }, + { + "id": "68e9559f-d467-46c3-ae48-d67c74582ca8", + "name": "view-authorization", + "description": "${role_view-authorization}", + "composite": false, + "clientRole": true, + "containerId": "4e03d0f9-4c56-42c8-958c-337f6eede3ac", + "attributes": {} + }, + { + "id": "908beec6-d8d1-441a-a5ad-f45d39df6b43", + "name": "manage-users", + "description": "${role_manage-users}", + "composite": false, + "clientRole": true, + "containerId": "4e03d0f9-4c56-42c8-958c-337f6eede3ac", + "attributes": {} + }, + { + "id": "5c5d56a2-e2ea-4b4f-9bb1-f40e18082932", + "name": "view-clients", + "description": "${role_view-clients}", + "composite": true, + "composites": { + "client": { + "master-realm": ["query-clients"] + } + }, + "clientRole": true, + "containerId": "4e03d0f9-4c56-42c8-958c-337f6eede3ac", + "attributes": {} + }, + { + "id": "561ec0f4-bd97-4c41-a825-918002afb307", + "name": "view-users", + "description": "${role_view-users}", + "composite": true, + "composites": { + "client": { + "master-realm": ["query-groups", "query-users"] + } + }, + "clientRole": true, + "containerId": "4e03d0f9-4c56-42c8-958c-337f6eede3ac", + "attributes": {} + }, + { + "id": "8ae350ac-19cd-431b-80fb-aee88316219e", + "name": "manage-realm", + "description": "${role_manage-realm}", + "composite": false, + "clientRole": true, + "containerId": "4e03d0f9-4c56-42c8-958c-337f6eede3ac", + "attributes": {} + } + ], + "account": [ + { + "id": "0a6ac4dd-afdc-4b7b-b16a-ef2ca3b8e396", + "name": "delete-account", + "description": "${role_delete-account}", + "composite": false, + "clientRole": true, + "containerId": "45ef915f-ca72-4bcc-a79b-d2b83ca4be2f", + "attributes": {} + }, + { + "id": "1a30b2d0-9d49-4a09-9769-ea7f3142e715", + "name": "manage-consent", + "description": "${role_manage-consent}", + "composite": true, + "composites": { + "client": { + "account": ["view-consent"] + } + }, + "clientRole": true, + "containerId": "45ef915f-ca72-4bcc-a79b-d2b83ca4be2f", + "attributes": {} + }, + { + "id": "e065f4ba-d97c-4219-b56e-edbe945e14bf", + "name": "view-consent", + "description": "${role_view-consent}", + "composite": false, + "clientRole": true, + "containerId": "45ef915f-ca72-4bcc-a79b-d2b83ca4be2f", + "attributes": {} + }, + { + "id": "6e5b9a43-0f82-4669-a821-a1d449e4a2be", + "name": "view-applications", + "description": "${role_view-applications}", + "composite": false, + "clientRole": true, + "containerId": "45ef915f-ca72-4bcc-a79b-d2b83ca4be2f", + "attributes": {} + }, + { + "id": "c386c8c5-bdee-4d52-b124-94379799d5d9", + "name": "manage-account-links", + "description": "${role_manage-account-links}", + "composite": false, + "clientRole": true, + "containerId": "45ef915f-ca72-4bcc-a79b-d2b83ca4be2f", + "attributes": {} + }, + { + "id": "bcff49f4-7f83-4ec6-9a51-271fc9cbb302", + "name": "view-profile", + "description": "${role_view-profile}", + "composite": false, + "clientRole": true, + "containerId": "45ef915f-ca72-4bcc-a79b-d2b83ca4be2f", + "attributes": {} + }, + { + "id": "4e36a4bf-80ab-404b-854e-7d593e9248ca", + "name": "manage-account", + "description": "${role_manage-account}", + "composite": true, + "composites": { + "client": { + "account": ["manage-account-links"] + } + }, + "clientRole": true, + "containerId": "45ef915f-ca72-4bcc-a79b-d2b83ca4be2f", + "attributes": {} + } + ] + } + }, + "groups": [], + "defaultRoles": ["offline_access", "uma_authorization"], + "requiredCredentials": ["password"], + "otpPolicyType": "totp", + "otpPolicyAlgorithm": "HmacSHA1", + "otpPolicyInitialCounter": 0, + "otpPolicyDigits": 6, + "otpPolicyLookAheadWindow": 1, + "otpPolicyPeriod": 30, + "otpSupportedApplications": ["FreeOTP", "Google Authenticator"], + "webAuthnPolicyRpEntityName": "keycloak", + "webAuthnPolicySignatureAlgorithms": ["ES256"], + "webAuthnPolicyRpId": "", + "webAuthnPolicyAttestationConveyancePreference": "not specified", + "webAuthnPolicyAuthenticatorAttachment": "not specified", + "webAuthnPolicyRequireResidentKey": "not specified", + "webAuthnPolicyUserVerificationRequirement": "not specified", + "webAuthnPolicyCreateTimeout": 0, + "webAuthnPolicyAvoidSameAuthenticatorRegister": false, + "webAuthnPolicyAcceptableAaguids": [], + "webAuthnPolicyPasswordlessRpEntityName": "keycloak", + "webAuthnPolicyPasswordlessSignatureAlgorithms": ["ES256"], + "webAuthnPolicyPasswordlessRpId": "", + "webAuthnPolicyPasswordlessAttestationConveyancePreference": "not specified", + "webAuthnPolicyPasswordlessAuthenticatorAttachment": "not specified", + "webAuthnPolicyPasswordlessRequireResidentKey": "not specified", + "webAuthnPolicyPasswordlessUserVerificationRequirement": "not specified", + "webAuthnPolicyPasswordlessCreateTimeout": 0, + "webAuthnPolicyPasswordlessAvoidSameAuthenticatorRegister": false, + "webAuthnPolicyPasswordlessAcceptableAaguids": [], + "users": [ + { + "id": "ad01a4d9-c919-4bc6-8b48-b3e3bcbb4149", + "createdTimestamp": 1618140941731, + "username": "admin", + "enabled": true, + "totp": false, + "emailVerified": false, + "credentials": [ + { + "id": "596b17bb-199c-4a23-9c48-4620c0ecfd7a", + "type": "password", + "createdDate": 1618140941876, + "secretData": "{\"value\":\"PXx46hQETQuXQRUl9FvzEJdZtoL57qsad1dFQyOLzj/pNEmwldN54oxQh5p+QB0rNNJPI9ZiaAfZS90ZzJa6pQ==\",\"salt\":\"kiFQwyPm53MgwAByqTw5qQ==\",\"additionalParameters\":{}}", + "credentialData": "{\"hashIterations\":27500,\"algorithm\":\"pbkdf2-sha256\",\"additionalParameters\":{}}" + } + ], + "disableableCredentialTypes": [], + "requiredActions": [], + "realmRoles": ["offline_access", "admin", "uma_authorization"], + "clientRoles": { + "account": ["view-profile", "manage-account"] + }, + "notBefore": 0, + "groups": [] + }, + { + "id": "3752dfb8-d3b5-4597-b83c-fed005d2671c", + "createdTimestamp": 1618141153912, + "username": "demo", + "enabled": true, + "totp": false, + "emailVerified": false, + "credentials": [ + { + "id": "4e5b5a38-3fcb-4703-8b9c-075164dde145", + "type": "password", + "createdDate": 1618141311783, + "secretData": "{\"value\":\"upShAwzTaS89elSkEgK0Phs+XUP3Ya1pOUYtE8k4JmZEJnXWjdOy9brn4cpLKwjF6pZ3glxkJgjdLmDeWm9WwQ==\",\"salt\":\"RnaXCbRf4bw1lZmQX43cMg==\",\"additionalParameters\":{}}", + "credentialData": "{\"hashIterations\":27500,\"algorithm\":\"pbkdf2-sha256\",\"additionalParameters\":{}}" + } + ], + "disableableCredentialTypes": [], + "requiredActions": [], + "realmRoles": ["offline_access", "uma_authorization"], + "clientRoles": { + "account": ["view-profile", "manage-account"] + }, + "notBefore": 0, + "groups": [] + } + ], + "scopeMappings": [ + { + "clientScope": "offline_access", + "roles": ["offline_access"] + } + ], + "clientScopeMappings": { + "account": [ + { + "client": "account-console", + "roles": ["manage-account"] + } + ] + }, + "clients": [ + { + "id": "45ef915f-ca72-4bcc-a79b-d2b83ca4be2f", + "clientId": "account", + "name": "${client_account}", + "rootUrl": "${authBaseUrl}", + "baseUrl": "/realms/master/account/", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "secret": "055c06d1-ffcc-4762-b8eb-e9814a6995df", + "defaultRoles": ["view-profile", "manage-account"], + "redirectUris": ["/realms/master/account/*"], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": false, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": {}, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "defaultClientScopes": ["web-origins", "role_list", "roles", "profile", "email"], + "optionalClientScopes": ["address", "phone", "offline_access", "microprofile-jwt"] + }, + { + "id": "4308a071-7dbf-4d08-a987-b7dc2f42b86e", + "clientId": "account-console", + "name": "${client_account-console}", + "rootUrl": "${authBaseUrl}", + "baseUrl": "/realms/master/account/", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "secret": "ec31839f-7ffb-400d-9373-26be6706e619", + "redirectUris": ["/realms/master/account/*"], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": true, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "pkce.code.challenge.method": "S256" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "protocolMappers": [ + { + "id": "e36bba2d-7a07-4c83-a40e-14b4a8316ae9", + "name": "audience resolve", + "protocol": "openid-connect", + "protocolMapper": "oidc-audience-resolve-mapper", + "consentRequired": false, + "config": {} + } + ], + "defaultClientScopes": ["web-origins", "role_list", "roles", "profile", "email"], + "optionalClientScopes": ["address", "phone", "offline_access", "microprofile-jwt"] + }, + { + "id": "0915d9fc-102f-4033-b37e-832b89fee932", + "clientId": "admin-cli", + "name": "${client_admin-cli}", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "secret": "175303e4-f2d4-4ae9-8fb0-27a1337d3208", + "redirectUris": [], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": false, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": true, + "serviceAccountsEnabled": false, + "publicClient": true, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": {}, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "defaultClientScopes": ["web-origins", "role_list", "roles", "profile", "email"], + "optionalClientScopes": ["address", "phone", "offline_access", "microprofile-jwt"] + }, + { + "id": "8bf21eb5-63da-4da5-8c12-7a5bafda1bf5", + "clientId": "auth_oidc-test", + "rootUrl": "http://localhost:8069", + "adminUrl": "http://localhost:8069", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "secret": "20c0ad33-0200-43cd-9bd1-5dd1b22918e3", + "redirectUris": ["http://localhost:8069/*"], + "webOrigins": ["http://localhost:8069"], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": true, + "directAccessGrantsEnabled": false, + "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", + "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", "role_list", "roles", "profile", "email"], + "optionalClientScopes": ["address", "phone", "offline_access", "microprofile-jwt"] + }, + { + "id": "d1dd5ade-20bf-4d53-a371-a33f10bc1087", + "clientId": "broker", + "name": "${client_broker}", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "secret": "52aab659-5f2d-445a-a93e-6ab04de9db42", + "redirectUris": [], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": false, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": {}, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "defaultClientScopes": ["web-origins", "role_list", "roles", "profile", "email"], + "optionalClientScopes": ["address", "phone", "offline_access", "microprofile-jwt"] + }, + { + "id": "4e03d0f9-4c56-42c8-958c-337f6eede3ac", + "clientId": "master-realm", + "name": "master Realm", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "secret": "15ca5d76-d964-4761-85d1-8343748481ab", + "redirectUris": [], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": true, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": false, + "frontchannelLogout": false, + "attributes": {}, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": true, + "nodeReRegistrationTimeout": 0, + "defaultClientScopes": ["web-origins", "role_list", "roles", "profile", "email"], + "optionalClientScopes": ["address", "phone", "offline_access", "microprofile-jwt"] + }, + { + "id": "983b74a1-e7a0-4bc4-8481-0eb8ca4f12e0", + "clientId": "security-admin-console", + "name": "${client_security-admin-console}", + "rootUrl": "${authAdminUrl}", + "baseUrl": "/admin/master/console/", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "secret": "121beec3-2388-475b-b989-1ff85d25b4fd", + "redirectUris": ["/admin/master/console/*"], + "webOrigins": ["+"], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": true, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "pkce.code.challenge.method": "S256" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "protocolMappers": [ + { + "id": "21a0f25a-a2b7-415e-95a7-23886f00c83b", + "name": "locale", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "locale", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "locale", + "jsonType.label": "String" + } + } + ], + "defaultClientScopes": ["web-origins", "role_list", "roles", "profile", "email"], + "optionalClientScopes": ["address", "phone", "offline_access", "microprofile-jwt"] + } + ], + "clientScopes": [ + { + "id": "07bff9f2-498f-4f07-9fb9-019152141a0f", + "name": "address", + "description": "OpenID Connect built-in scope: address", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true", + "consent.screen.text": "${addressScopeConsentText}" + }, + "protocolMappers": [ + { + "id": "46333a31-1e88-4191-8d25-2f4d975af4db", + "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": "8c30d3a8-af56-407a-a622-df16e7c2b04b", + "name": "email", + "description": "OpenID Connect built-in scope: email", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true", + "consent.screen.text": "${emailScopeConsentText}" + }, + "protocolMappers": [ + { + "id": "db1117e1-6174-4934-99d0-ff51f319c6f5", + "name": "email", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "email", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "email", + "jsonType.label": "String" + } + }, + { + "id": "c1a7d21e-7f45-4559-b314-913cbb560967", + "name": "email verified", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "emailVerified", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "email_verified", + "jsonType.label": "boolean" + } + } + ] + }, + { + "id": "3d9f47ed-d2f0-474c-baa4-75e0e6619385", + "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": "e7c49c89-b513-4512-9410-bc0f39d4b4e5", + "name": "upn", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "username", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "upn", + "jsonType.label": "String" + } + }, + { + "id": "7b65fe0b-2974-445f-a7cd-ccec5141f560", + "name": "groups", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-realm-role-mapper", + "consentRequired": false, + "config": { + "multivalued": "true", + "user.attribute": "foo", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "groups", + "jsonType.label": "String" + } + } + ] + }, + { + "id": "a9ed1a18-0e05-4eeb-a247-c84296cfa653", + "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": "52f62f87-f914-4771-b79c-46a387797be7", + "name": "phone", + "description": "OpenID Connect built-in scope: phone", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true", + "consent.screen.text": "${phoneScopeConsentText}" + }, + "protocolMappers": [ + { + "id": "d8c485dc-7b8a-4469-8868-5bb73a6abafa", + "name": "phone number verified", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "phoneNumberVerified", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "phone_number_verified", + "jsonType.label": "boolean" + } + }, + { + "id": "b1b21cd7-06f7-4469-b11e-22bf9de1a3ce", + "name": "phone number", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "phoneNumber", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "phone_number", + "jsonType.label": "String" + } + } + ] + }, + { + "id": "311bf186-6cb2-4bdd-9da5-8ac30ff8b296", + "name": "profile", + "description": "OpenID Connect built-in scope: profile", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true", + "consent.screen.text": "${profileScopeConsentText}" + }, + "protocolMappers": [ + { + "id": "eda5d01f-6841-441e-b7d0-8fa48365a501", + "name": "middle name", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "middleName", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "middle_name", + "jsonType.label": "String" + } + }, + { + "id": "e131db35-e08b-4805-b11b-59a90650ee2a", + "name": "picture", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "picture", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "picture", + "jsonType.label": "String" + } + }, + { + "id": "6e311016-71e1-450a-8011-6f6ce9f7e365", + "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": "e0fc1d75-2b19-4fc8-8ea3-778c96b47321", + "name": "website", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "website", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "website", + "jsonType.label": "String" + } + }, + { + "id": "a94d4acf-0a46-44b0-a739-ed329dfa9f41", + "name": "profile", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "profile", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "profile", + "jsonType.label": "String" + } + }, + { + "id": "57d4c1ad-4507-4545-8efc-5f83c0dc0be6", + "name": "username", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "username", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "preferred_username", + "jsonType.label": "String" + } + }, + { + "id": "0b42528f-d116-4217-9fbe-fe469c80914d", + "name": "given name", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "firstName", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "given_name", + "jsonType.label": "String" + } + }, + { + "id": "f698a281-a28c-4b3e-ad73-524c556d1cc5", + "name": "locale", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "locale", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "locale", + "jsonType.label": "String" + } + }, + { + "id": "50b2c4f9-b4a3-470a-a8e4-af7384bd9536", + "name": "zoneinfo", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "zoneinfo", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "zoneinfo", + "jsonType.label": "String" + } + }, + { + "id": "12a21276-1f49-4b34-8bf7-df4dd7929ebf", + "name": "updated at", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "updatedAt", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "updated_at", + "jsonType.label": "String" + } + }, + { + "id": "4abce20a-c47d-430a-85c0-f65cb9ae7aa0", + "name": "birthdate", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "birthdate", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "birthdate", + "jsonType.label": "String" + } + }, + { + "id": "fa7c99b6-9cdf-4f8f-96af-45df5f2497e2", + "name": "nickname", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "nickname", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "nickname", + "jsonType.label": "String" + } + }, + { + "id": "983d7499-e23c-4971-9501-d404b405b484", + "name": "family name", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "lastName", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "family_name", + "jsonType.label": "String" + } + }, + { + "id": "f42b6bcb-4bac-44a8-8073-c3194b56029c", + "name": "gender", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "gender", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "gender", + "jsonType.label": "String" + } + } + ] + }, + { + "id": "6acc2950-2bbe-49d8-b266-42e3c518f46f", + "name": "role_list", + "description": "SAML role list", + "protocol": "saml", + "attributes": { + "consent.screen.text": "${samlRoleListScopeConsentText}", + "display.on.consent.screen": "true" + }, + "protocolMappers": [ + { + "id": "8b0f3195-f1c2-4188-8045-5d48ba0aeb30", + "name": "role list", + "protocol": "saml", + "protocolMapper": "saml-role-list-mapper", + "consentRequired": false, + "config": { + "single": "false", + "attribute.nameformat": "Basic", + "attribute.name": "Role" + } + } + ] + }, + { + "id": "437e71e0-7728-4763-82af-29fd14b1fa12", + "name": "roles", + "description": "OpenID Connect scope for add user roles to the access token", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "false", + "display.on.consent.screen": "true", + "consent.screen.text": "${rolesScopeConsentText}" + }, + "protocolMappers": [ + { + "id": "d255020d-b385-417d-bf27-b3a8c5911579", + "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": "5adb2a9c-5dbb-430c-89c0-9b464677245f", + "name": "realm roles", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-realm-role-mapper", + "consentRequired": false, + "config": { + "user.attribute": "foo", + "access.token.claim": "true", + "claim.name": "realm_access.roles", + "jsonType.label": "String", + "multivalued": "true" + } + }, + { + "id": "667e731f-4375-489f-bf03-566a7292719b", + "name": "audience resolve", + "protocol": "openid-connect", + "protocolMapper": "oidc-audience-resolve-mapper", + "consentRequired": false, + "config": {} + } + ] + }, + { + "id": "359211aa-1a6d-4441-a9bb-610acc6350db", + "name": "web-origins", + "description": "OpenID Connect scope for add allowed web origins to the access token", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "false", + "display.on.consent.screen": "false", + "consent.screen.text": "" + }, + "protocolMappers": [ + { + "id": "6646f9a5-0a1e-4bcc-b1d1-35b035b5aa55", + "name": "allowed web origins", + "protocol": "openid-connect", + "protocolMapper": "oidc-allowed-origins-mapper", + "consentRequired": false, + "config": {} + } + ] + } + ], + "defaultDefaultClientScopes": [ + "role_list", + "profile", + "email", + "roles", + "web-origins" + ], + "defaultOptionalClientScopes": [ + "offline_access", + "address", + "phone", + "microprofile-jwt" + ], + "browserSecurityHeaders": { + "contentSecurityPolicyReportOnly": "", + "xContentTypeOptions": "nosniff", + "xRobotsTag": "none", + "xFrameOptions": "SAMEORIGIN", + "xXSSProtection": "1; mode=block", + "contentSecurityPolicy": "frame-src 'self'; frame-ancestors 'self'; object-src 'none';", + "strictTransportSecurity": "max-age=31536000; includeSubDomains" + }, + "smtpServer": {}, + "eventsEnabled": false, + "eventsListeners": ["jboss-logging"], + "enabledEventTypes": [], + "adminEventsEnabled": false, + "adminEventsDetailsEnabled": false, + "identityProviders": [], + "identityProviderMappers": [], + "components": { + "org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy": [ + { + "id": "070f65ee-8d8f-4fff-ae32-a27016e7bf5c", + "name": "Allowed Client Scopes", + "providerId": "allowed-client-templates", + "subType": "authenticated", + "subComponents": {}, + "config": { + "allow-default-scopes": ["true"] + } + }, + { + "id": "81bc44f5-9bbd-4325-9f63-fc332e30e0e7", + "name": "Allowed Protocol Mapper Types", + "providerId": "allowed-protocol-mappers", + "subType": "anonymous", + "subComponents": {}, + "config": { + "allowed-protocol-mapper-types": [ + "oidc-usermodel-property-mapper", + "saml-user-property-mapper", + "oidc-full-name-mapper", + "oidc-sha256-pairwise-sub-mapper", + "oidc-usermodel-attribute-mapper", + "oidc-address-mapper", + "saml-user-attribute-mapper", + "saml-role-list-mapper" + ] + } + }, + { + "id": "f7d673f4-10ef-486d-a168-925b139abbc5", + "name": "Consent Required", + "providerId": "consent-required", + "subType": "anonymous", + "subComponents": {}, + "config": {} + }, + { + "id": "c103a051-9371-4021-8a34-8196a78c3638", + "name": "Allowed Client Scopes", + "providerId": "allowed-client-templates", + "subType": "anonymous", + "subComponents": {}, + "config": { + "allow-default-scopes": ["true"] + } + }, + { + "id": "a71eaf13-3e55-4aa6-9cf8-c1b74198d63a", + "name": "Trusted Hosts", + "providerId": "trusted-hosts", + "subType": "anonymous", + "subComponents": {}, + "config": { + "host-sending-registration-request-must-match": ["true"], + "client-uris-must-match": ["true"] + } + }, + { + "id": "fb7bdacb-46bc-4266-b217-a1705e87f957", + "name": "Allowed Protocol Mapper Types", + "providerId": "allowed-protocol-mappers", + "subType": "authenticated", + "subComponents": {}, + "config": { + "allowed-protocol-mapper-types": [ + "saml-user-attribute-mapper", + "oidc-sha256-pairwise-sub-mapper", + "oidc-usermodel-attribute-mapper", + "saml-user-property-mapper", + "oidc-full-name-mapper", + "oidc-usermodel-property-mapper", + "oidc-address-mapper", + "saml-role-list-mapper" + ] + } + }, + { + "id": "ec4f12ef-2fef-42b2-9cb8-9f251d8c3344", + "name": "Full Scope Disabled", + "providerId": "scope", + "subType": "anonymous", + "subComponents": {}, + "config": {} + }, + { + "id": "99778abc-51f8-4480-a778-59ef73a60f56", + "name": "Max Clients Limit", + "providerId": "max-clients", + "subType": "anonymous", + "subComponents": {}, + "config": { + "max-clients": ["200"] + } + } + ], + "org.keycloak.keys.KeyProvider": [ + { + "id": "052d41cb-6938-40f0-8872-8ab171ec27e9", + "name": "fallback-HS256", + "providerId": "hmac-generated", + "subComponents": {}, + "config": { + "kid": ["489fe44c-d70f-42f7-9a19-e3dc2a0df076"], + "secret": [ + "6dqvUhGU5rhuMOKNuOI4U7nPTcA9jeJJLpmoewnkw_PdFDSjy73iQkPt5hw_8qU34IIFGOM-LkJJ8VWihvwEwQ" + ], + "priority": ["-100"], + "algorithm": ["HS256"] + } + }, + { + "id": "53901ca8-2f9d-4f2e-804f-756204b9c1c5", + "name": "fallback-RS256", + "providerId": "rsa-generated", + "subComponents": {}, + "config": { + "privateKey": [ + "MIIEpAIBAAKCAQEAgYNR3Pgh/f1+DUcMBc9T6uT1MwC4oTthGbtJhmqQiawSWzUO8icSM4hFjiN2zqsKx7ofWmP3+ZRTq6fSEref+0tRRWafTq6LtDySa4DilqnQ/WBznnXML9hmsPBW3gNiZAKYbwvb/YE36L+a4nWcEc13jcXgMqLXUD7K/3YIYNT/S7xGgNKYfBmbTfS0A08ZITtyakaGYwLK6zwLYVAeUj1hZjVcz1926Zhhu4YznD6qMgCmBwlSD9lc6v0/RUNjo1NKSU0LXAeUEk1ynxFKJ+cUikHuvsIQvuXY5Sj+Y2tcWFpU51J01com69kdyYeelQv1n41yOB/U4bGmbhUctwIDAQABAoIBAH+RgwwdmRXeH9AiQBRk8Gq5lU/kkPe3TmCTGsv8oVwKEpamP4+Drqj1vFVSV08gKOEsUn+tYm8CjBvTlNd86WcT+/xZJefRg6hH1Y1wiUAQCtvYqmnV7Abgp933Dglm2f5alB0lWE5ufkySlpQjdlQOx4js9HXL8juHblqMv5noJNaDQSDh4UxtET+fVT8pvCL/MImG4C6BtULLDXLdH4pIvn0OIS788Xpc87uc3dSIfVxL1Oa0U1Qrxma5P8p9imremKLdA4iOzopyVsLo0uP2PrSLWD04I9kSwO0MHNDzbkMJiWXX5a7afzRY4g+zQL2STYsbD4B9KQnIpsQNrVkCgYEA4LbUejeexUBsjs3Whkn1BlUvxvDf2Vtudws1XSNmd5lneitfseDXCcH6p1TLHn0xoOmJDFGjLPEYFhqL0I5IyZP6zfiCJL88zFWVXlY8NsAoQeqvgnu5wHpIXEXCaJAAksWy/dmUZwTUyfIIxnLUQMPpJ1stu35e8DyNO4VadPMCgYEAk4teFNWkFBZmjSBUyo8Tw4TmJuCIRVH4FSaspUeVNhToKI3e5R/duz8rvqBj4tul5lyWq9FmcDaawE94jIa17XQnj/O767G72lHRuIlI+qftIca3r4/kDvy730yAOWl/1Su4SrX3t7WSBHIG2j7HMYIsj5xgBUvnbRQUtxByui0CgYEAsLe3YyHoj3D5rlg708HHmqJVf1sgfxvDRIUhA0z6oSWX1eDUUdvi4H6XMw6g6ipEZCokJ/bvn0E+0usvduTeYwAn5eD/4AwwsPTBEb45fkkhn60DN1c7nh3MWBxYJcjRWpt1BuMcLOQEv4fC1OWq+//VlKjEz0UzPjQwUVWu7HcCgYAo6uqhfootY/T2yHObZUiG3ZFyUKyaBNx3CS2x/IMd53hm3slk44x7hE5eZF6vKFj+5MiIR99P2WTbVm7JEgbcHm1mV6LS/4xoRG6T7cbGdNGnn1OLpa0Klv6HM9EPmvlvpdtLJOHZGcqv3uuVlPlq+n3fKe/bKCy7LGl+R1p51QKBgQCEmPS9A9y6YF7zRo8u7vUmJzGktdrSw65zYZVMzXY9A7uKU/OfpZ+papKDr1D9ApFgi5Ip1imirR7K9m4GImowZOTe/E6dT6nmrUtWUkaS4ghhwZ9Gh6kAOWoBYRB/Z4XIzJoSiet+PJ3p8SLhM7nETj7IDaeQgNudSK8/ohNPWQ==" + ], + "certificate": [ + "MIICmzCCAYMCBgF4wLeSOTANBgkqhkiG9w0BAQsFADARMQ8wDQYDVQQDDAZtYXN0ZXIwHhcNMjEwNDExMTEzNDE5WhcNMzEwNDExMTEzNTU5WjARMQ8wDQYDVQQDDAZtYXN0ZXIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCBg1Hc+CH9/X4NRwwFz1Pq5PUzALihO2EZu0mGapCJrBJbNQ7yJxIziEWOI3bOqwrHuh9aY/f5lFOrp9ISt5/7S1FFZp9Orou0PJJrgOKWqdD9YHOedcwv2Gaw8FbeA2JkAphvC9v9gTfov5ridZwRzXeNxeAyotdQPsr/dghg1P9LvEaA0ph8GZtN9LQDTxkhO3JqRoZjAsrrPAthUB5SPWFmNVzPX3bpmGG7hjOcPqoyAKYHCVIP2Vzq/T9FQ2OjU0pJTQtcB5QSTXKfEUon5xSKQe6+whC+5djlKP5ja1xYWlTnUnTVyibr2R3Jh56VC/WfjXI4H9ThsaZuFRy3AgMBAAEwDQYJKoZIhvcNAQELBQADggEBAHEq+7bncqOh0RJJj+6fSHsIlkRGOeX6djVIKi1/eAJCD61Et3MHKh4kbu4U6phNlnhW5IFYinchGXe1uoG18fWkUS6QJoxHIDLR+tub7NSMraYxK85VgyLHCHaaGX7Bz+sIM628th4LlQd/M2zL45rqlMvB1XLxsMpi9Pb0Zc7qWwrvE5Jfi99UDAi6ZV3OojR6YC79HVHyOVmBIdLrVtn5mQYKJ5tF5F8xSs4ng96IO8Sn8pbUuYG8SlEz6KMmGH1sczlPE/3kAdm9IF+fXpYywuhsRNJyDBVDGpcqHTW+UW+V5TWa/ucZ6cpr1dQP5/FpcHylSWoXJpCk01PXl/M=" + ], + "priority": ["-100"], + "algorithm": ["RS256"] + } + } + ] + }, + "internationalizationEnabled": false, + "supportedLocales": [], + "authenticationFlows": [ + { + "id": "815c3100-241b-4298-8039-54253c2c7e70", + "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", + "requirement": "ALTERNATIVE", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "requirement": "ALTERNATIVE", + "priority": 20, + "flowAlias": "Verify Existing Account by Re-authentication", + "userSetupAllowed": false, + "autheticatorFlow": true + } + ] + }, + { + "id": "e6a85d70-dc13-4705-9ba3-a980d548a430", + "alias": "Authentication Options", + "description": "Authentication options.", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "basic-auth", + "requirement": "REQUIRED", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "basic-auth-otp", + "requirement": "DISABLED", + "priority": 20, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "auth-spnego", + "requirement": "DISABLED", + "priority": 30, + "userSetupAllowed": false, + "autheticatorFlow": false + } + ] + }, + { + "id": "96b14b20-7305-4018-8e75-86b572e3ace0", + "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", + "requirement": "REQUIRED", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "auth-otp-form", + "requirement": "REQUIRED", + "priority": 20, + "userSetupAllowed": false, + "autheticatorFlow": false + } + ] + }, + { + "id": "a51abcaa-7495-4526-997f-55cd82f152e2", + "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", + "requirement": "REQUIRED", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "direct-grant-validate-otp", + "requirement": "REQUIRED", + "priority": 20, + "userSetupAllowed": false, + "autheticatorFlow": false + } + ] + }, + { + "id": "54fa7607-f4e5-4fe7-a8c3-4fad6ee376b8", + "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", + "requirement": "REQUIRED", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "auth-otp-form", + "requirement": "REQUIRED", + "priority": 20, + "userSetupAllowed": false, + "autheticatorFlow": false + } + ] + }, + { + "id": "31de3d9e-2f81-43cc-a4ad-1112cd4e9d71", + "alias": "Handle Existing Account", + "description": "Handle what to do if there is existing account with same email/username like authenticated identity provider", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "idp-confirm-link", + "requirement": "REQUIRED", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "requirement": "REQUIRED", + "priority": 20, + "flowAlias": "Account verification options", + "userSetupAllowed": false, + "autheticatorFlow": true + } + ] + }, + { + "id": "f499a904-41cc-41c1-bb43-688672d1533b", + "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", + "requirement": "REQUIRED", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "reset-otp", + "requirement": "REQUIRED", + "priority": 20, + "userSetupAllowed": false, + "autheticatorFlow": false + } + ] + }, + { + "id": "7cee4451-5b17-4742-8736-8fce85639409", + "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", + "requirement": "ALTERNATIVE", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "requirement": "ALTERNATIVE", + "priority": 20, + "flowAlias": "Handle Existing Account", + "userSetupAllowed": false, + "autheticatorFlow": true + } + ] + }, + { + "id": "dafe7404-bce7-4ab6-9e35-fd1aecb93545", + "alias": "Verify Existing Account by Re-authentication", + "description": "Reauthentication of existing account", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "idp-username-password-form", + "requirement": "REQUIRED", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "requirement": "CONDITIONAL", + "priority": 20, + "flowAlias": "First broker login - Conditional OTP", + "userSetupAllowed": false, + "autheticatorFlow": true + } + ] + }, + { + "id": "d34ac568-3793-4864-8df8-733fa2cd3554", + "alias": "browser", + "description": "browser based authentication", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "auth-cookie", + "requirement": "ALTERNATIVE", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "auth-spnego", + "requirement": "DISABLED", + "priority": 20, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "identity-provider-redirector", + "requirement": "ALTERNATIVE", + "priority": 25, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "requirement": "ALTERNATIVE", + "priority": 30, + "flowAlias": "forms", + "userSetupAllowed": false, + "autheticatorFlow": true + } + ] + }, + { + "id": "15a43900-948a-487d-a97a-444502dce766", + "alias": "clients", + "description": "Base authentication for clients", + "providerId": "client-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "client-secret", + "requirement": "ALTERNATIVE", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "client-jwt", + "requirement": "ALTERNATIVE", + "priority": 20, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "client-secret-jwt", + "requirement": "ALTERNATIVE", + "priority": 30, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "client-x509", + "requirement": "ALTERNATIVE", + "priority": 40, + "userSetupAllowed": false, + "autheticatorFlow": false + } + ] + }, + { + "id": "1865917e-f56d-453c-bb66-578e1955d199", + "alias": "direct grant", + "description": "OpenID Connect Resource Owner Grant", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "direct-grant-validate-username", + "requirement": "REQUIRED", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "direct-grant-validate-password", + "requirement": "REQUIRED", + "priority": 20, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "requirement": "CONDITIONAL", + "priority": 30, + "flowAlias": "Direct Grant - Conditional OTP", + "userSetupAllowed": false, + "autheticatorFlow": true + } + ] + }, + { + "id": "3e151380-0869-4bdb-b9d1-65a7bcfcb6ab", + "alias": "docker auth", + "description": "Used by Docker clients to authenticate against the IDP", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "docker-http-basic-authenticator", + "requirement": "REQUIRED", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + } + ] + }, + { + "id": "79369bf2-9434-4b93-94d9-74a08a361701", + "alias": "first broker login", + "description": "Actions taken after first broker login with identity provider account, which is not yet linked to any Keycloak account", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticatorConfig": "review profile config", + "authenticator": "idp-review-profile", + "requirement": "REQUIRED", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "requirement": "REQUIRED", + "priority": 20, + "flowAlias": "User creation or linking", + "userSetupAllowed": false, + "autheticatorFlow": true + } + ] + }, + { + "id": "322d8eb9-a9cc-422a-a6a2-065b3b863967", + "alias": "forms", + "description": "Username, password, otp and other auth forms.", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "auth-username-password-form", + "requirement": "REQUIRED", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "requirement": "CONDITIONAL", + "priority": 20, + "flowAlias": "Browser - Conditional OTP", + "userSetupAllowed": false, + "autheticatorFlow": true + } + ] + }, + { + "id": "0cf68f4f-332e-4f7d-a7a9-907189914191", + "alias": "http challenge", + "description": "An authentication flow based on challenge-response HTTP Authentication Schemes", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "no-cookie-redirect", + "requirement": "REQUIRED", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "requirement": "REQUIRED", + "priority": 20, + "flowAlias": "Authentication Options", + "userSetupAllowed": false, + "autheticatorFlow": true + } + ] + }, + { + "id": "d412906e-1aad-4707-ac40-95406aeed8d0", + "alias": "registration", + "description": "registration flow", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "registration-page-form", + "requirement": "REQUIRED", + "priority": 10, + "flowAlias": "registration form", + "userSetupAllowed": false, + "autheticatorFlow": true + } + ] + }, + { + "id": "d11ddacf-4fe8-4614-a9f1-fe5dcf621330", + "alias": "registration form", + "description": "registration form", + "providerId": "form-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "registration-user-creation", + "requirement": "REQUIRED", + "priority": 20, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "registration-profile-action", + "requirement": "REQUIRED", + "priority": 40, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "registration-password-action", + "requirement": "REQUIRED", + "priority": 50, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "registration-recaptcha-action", + "requirement": "DISABLED", + "priority": 60, + "userSetupAllowed": false, + "autheticatorFlow": false + } + ] + }, + { + "id": "48093169-4a24-499b-aad7-b87dcd32269d", + "alias": "reset credentials", + "description": "Reset credentials for a user if they forgot their password or something", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "reset-credentials-choose-user", + "requirement": "REQUIRED", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "reset-credential-email", + "requirement": "REQUIRED", + "priority": 20, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "reset-password", + "requirement": "REQUIRED", + "priority": 30, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "requirement": "CONDITIONAL", + "priority": 40, + "flowAlias": "Reset - Conditional OTP", + "userSetupAllowed": false, + "autheticatorFlow": true + } + ] + }, + { + "id": "f0a1d44c-2bab-408c-91b4-bb730bd65bc4", + "alias": "saml ecp", + "description": "SAML ECP Profile Authentication Flow", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "http-basic-authenticator", + "requirement": "REQUIRED", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + } + ] + } + ], + "authenticatorConfig": [ + { + "id": "173229da-6a52-4d54-8e88-cba503234cb4", + "alias": "create unique user config", + "config": { + "require.password.update.after.registration": "false" + } + }, + { + "id": "a72c2a36-a48a-440d-b52f-831c385287fc", + "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": "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", + "attributes": {}, + "keycloakVersion": "12.0.4", + "userManagedAccessAllowed": false +} diff --git a/tests/keycloak/keycloak.sh b/tests/keycloak/keycloak.sh new file mode 100644 index 0000000..7c81d56 --- /dev/null +++ b/tests/keycloak/keycloak.sh @@ -0,0 +1,10 @@ +#!/bin/sh +set -x +$(which docker || which podman) run --rm \ + -v $(dirname $0)/keycloak-config.json:/tmp/keycloak-config.json \ + -p 8080:8080 \ + quay.io/keycloak/keycloak:12.0.4 \ + -Dkeycloak.migration.action=import \ + -Dkeycloak.migration.provider=singleFile \ + -Dkeycloak.migration.file=/tmp/keycloak-config.json \ + -Dkeycloak.migration.strategy=OVERWRITE_EXISTING diff --git a/tests/test_auth_oidc_auth_code.py b/tests/test_auth_oidc_auth_code.py new file mode 100644 index 0000000..a833407 --- /dev/null +++ b/tests/test_auth_oidc_auth_code.py @@ -0,0 +1,321 @@ +# Copyright 2021 ACSONE SA/NV +# License: AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) + +import contextlib +import json +import logging +from urllib.parse import parse_qs, urlparse + +import responses +from cryptography.hazmat.primitives import serialization +from cryptography.hazmat.primitives.asymmetric import rsa +from jose import jwt +from jose.exceptions import JWTError +from jose.utils import long_to_base64 + +import odoo +from odoo.exceptions import AccessDenied +from odoo.tests import common + +from odoo.addons.website.tools import MockRequest as _MockRequest + +from ..controllers.main import OpenIDLogin + +BASE_URL = f"http://localhost:{odoo.tools.config['http_port']}" + + +@contextlib.contextmanager +def MockRequest(env): + with _MockRequest(env) as request: + request.httprequest.url_root = BASE_URL + "/" + request.params = {} + yield request + + +class TestAuthOIDCAuthorizationCodeFlow(common.HttpCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + ( + cls.rsa_key_pem, + cls.rsa_key_public_pem, + cls.rsa_key_public_jwk, + ) = cls._generate_key() + _, cls.second_key_public_pem, _ = cls._generate_key() + + @staticmethod + def _generate_key(): + rsa_key = rsa.generate_private_key( + public_exponent=65537, + key_size=4096, + ) + rsa_key_pem = rsa_key.private_bytes( + serialization.Encoding.PEM, + serialization.PrivateFormat.TraditionalOpenSSL, + serialization.NoEncryption(), + ).decode("utf8") + rsa_key_public = rsa_key.public_key() + rsa_key_public_pem = rsa_key_public.public_bytes( + serialization.Encoding.PEM, + serialization.PublicFormat.SubjectPublicKeyInfo, + ).decode("utf8") + jwk = { + # https://datatracker.ietf.org/doc/html/rfc7518#section-6.1 + "kty": "RSA", + "use": "sig", + "n": long_to_base64(rsa_key_public.public_numbers().n).decode("utf-8"), + "e": long_to_base64(rsa_key_public.public_numbers().e).decode("utf-8"), + } + return rsa_key_pem, rsa_key_public_pem, jwk + + def setUp(self): + super().setUp() + # search our test provider and bind the demo user to it + self.provider_rec = self.env["auth.oauth.provider"].search( + [("client_id", "=", "auth_oidc-test")] + ) + self.assertEqual(len(self.provider_rec), 1) + + def test_auth_link(self): + """Test that the authentication link is correct.""" + # disable existing providers except our test provider + self.env["auth.oauth.provider"].search( + [("client_id", "!=", "auth_oidc-test")] + ).write(dict(enabled=False)) + with MockRequest(self.env): + providers = OpenIDLogin().list_providers() + self.assertEqual(len(providers), 1) + auth_link = providers[0]["auth_link"] + assert auth_link.startswith(self.provider_rec.auth_endpoint) + params = parse_qs(urlparse(auth_link).query) + self.assertEqual(params["response_type"], ["code"]) + self.assertEqual(params["client_id"], [self.provider_rec.client_id]) + self.assertEqual(params["scope"], ["openid email"]) + self.assertTrue(params["code_challenge"]) + self.assertEqual(params["code_challenge_method"], ["S256"]) + self.assertTrue(params["nonce"]) + self.assertTrue(params["state"]) + self.assertEqual(params["redirect_uri"], [BASE_URL + "/auth_oauth/signin"]) + + def _prepare_login_test_user(self): + user = self.env.ref("base.user_demo") + user.write({"oauth_provider_id": self.provider_rec.id, "oauth_uid": user.login}) + return user + + def _prepare_login_test_responses( + self, access_token="42", id_token_body=None, id_token_headers=None, keys=None + ): + if id_token_body is None: + id_token_body = {} + if id_token_headers is None: + id_token_headers = {"kid": "the_key_id"} + responses.add( + responses.POST, + "http://localhost:8080/auth/realms/master/protocol/openid-connect/token", + json={ + "access_token": access_token, + "id_token": jwt.encode( + id_token_body, + self.rsa_key_pem, + algorithm="RS256", + headers=id_token_headers, + ), + }, + ) + if keys is None: + if "kid" in id_token_headers: + keys = [{"kid": "the_key_id", "keys": [self.rsa_key_public_pem]}] + else: + keys = [{"keys": [self.rsa_key_public_pem]}] + responses.add( + responses.GET, + "http://localhost:8080/auth/realms/master/protocol/openid-connect/certs", + json={"keys": keys}, + ) + + @responses.activate + def test_login(self): + """Test that login works""" + user = self._prepare_login_test_user() + self._prepare_login_test_responses(id_token_body={"user_id": user.login}) + + params = {"state": json.dumps({})} + with MockRequest(self.env): + db, login, token = self.env["res.users"].auth_oauth( + self.provider_rec.id, + params, + ) + self.assertEqual(token, "42") + self.assertEqual(login, user.login) + + @responses.activate + def test_login_without_kid(self): + """Test that login works when ID Token has no kid in header""" + user = self._prepare_login_test_user() + self._prepare_login_test_responses( + id_token_body={"user_id": user.login}, + id_token_headers={}, + access_token=chr(42), + ) + + params = {"state": json.dumps({})} + with MockRequest(self.env): + db, login, token = self.env["res.users"].auth_oauth( + self.provider_rec.id, + params, + ) + self.assertEqual(token, "*") + self.assertEqual(login, user.login) + + @responses.activate + def test_login_with_sub_claim(self): + """Test that login works when ID Token contains only standard claims""" + self.provider_rec.token_map = False + user = self._prepare_login_test_user() + self._prepare_login_test_responses( + id_token_body={"sub": user.login}, access_token="1764" + ) + + params = {"state": json.dumps({})} + with MockRequest(self.env): + db, login, token = self.env["res.users"].auth_oauth( + self.provider_rec.id, + params, + ) + self.assertEqual(token, "1764") + self.assertEqual(login, user.login) + + @responses.activate + def test_login_without_kid_multiple_keys_in_jwks(self): + """ + Test that login fails if no kid is provided in ID Token and JWKS has multiple + keys + """ + user = self._prepare_login_test_user() + self._prepare_login_test_responses( + id_token_body={"user_id": user.login}, + id_token_headers={}, + access_token="6*7", + keys=[ + {"kid": "other_key_id", "keys": [self.second_key_public_pem]}, + {"kid": "the_key_id", "keys": [self.rsa_key_public_pem]}, + ], + ) + + with self.assertRaises( + JWTError, + msg="OpenID Connect requires kid to be set if there is" + " more than one key in the JWKS", + ): + with MockRequest(self.env): + self.env["res.users"].auth_oauth( + self.provider_rec.id, + {"state": json.dumps({})}, + ) + + @responses.activate + def test_login_without_matching_key(self): + """Test that login fails if no matching key can be found""" + user = self._prepare_login_test_user() + self._prepare_login_test_responses( + id_token_body={"user_id": user.login}, + id_token_headers={}, + access_token="168/4", + keys=[{"kid": "other_key_id", "keys": [self.second_key_public_pem]}], + ) + + with self.assertRaises(JWTError): + with MockRequest(self.env): + self.env["res.users"].auth_oauth( + self.provider_rec.id, + {"state": json.dumps({})}, + ) + + @responses.activate + def test_login_without_any_key(self): + """Test that login fails if no key is provided by JWKS""" + user = self._prepare_login_test_user() + self._prepare_login_test_responses( + id_token_body={"user_id": user.login}, + id_token_headers={}, + access_token="168/4", + keys=[], + ) + + with ( + self.assertRaises(AccessDenied), + MockRequest(self.env), + self.assertLogs(level=logging.ERROR) as logs, + ): + self.env["res.users"].auth_oauth( + self.provider_rec.id, + {"state": json.dumps({})}, + ) + self.assertEqual(len(logs.records), 1) + self.assertEqual(logs.records[0].levelno, logging.ERROR) + self.assertEqual( + "ERROR:odoo.addons.auth_oidc.models.res_users:user_id claim not found in" + " id_token (after mapping).", + logs.output[0], + ) + + @responses.activate + def test_login_with_multiple_keys_in_jwks(self): + """Test that login works with multiple keys present in jwks""" + user = self._prepare_login_test_user() + self._prepare_login_test_responses( + id_token_body={"user_id": user.login}, + access_token="2*3*7", + keys=[ + {"kid": "other_key_id", "keys": [self.second_key_public_pem]}, + {"kid": "the_key_id", "keys": [self.rsa_key_public_pem]}, + ], + ) + + with MockRequest(self.env): + db, login, token = self.env["res.users"].auth_oauth( + self.provider_rec.id, + {"state": json.dumps({})}, + ) + self.assertEqual(token, "2*3*7") + self.assertEqual(login, user.login) + + @responses.activate + def test_login_with_multiple_keys_in_jwks_same_kid(self): + """Test that login works with multiple keys with the same kid present in jwks""" + user = self._prepare_login_test_user() + self._prepare_login_test_responses( + id_token_body={"user_id": user.login}, + access_token="84/2", + keys=[ + {"kid": "the_key_id", "keys": [self.second_key_public_pem]}, + {"kid": "the_key_id", "keys": [self.rsa_key_public_pem]}, + ], + ) + + with MockRequest(self.env): + db, login, token = self.env["res.users"].auth_oauth( + self.provider_rec.id, + {"state": json.dumps({})}, + ) + self.assertEqual(token, "84/2") + self.assertEqual(login, user.login) + + @responses.activate + def test_login_with_jwk_format(self): + """Test that login works with proper jwks format""" + user = self._prepare_login_test_user() + self.rsa_key_public_jwk["kid"] = "the_key_id" + self._prepare_login_test_responses( + id_token_body={"user_id": user.login}, + keys=[self.rsa_key_public_jwk], + access_token="122/3", + ) + + with MockRequest(self.env): + db, login, token = self.env["res.users"].auth_oauth( + self.provider_rec.id, + {"state": json.dumps({})}, + ) + self.assertEqual(token, "122/3") + self.assertEqual(login, user.login) diff --git a/views/auth_oauth_provider.xml b/views/auth_oauth_provider.xml new file mode 100644 index 0000000..90c931b --- /dev/null +++ b/views/auth_oauth_provider.xml @@ -0,0 +1,24 @@ + + + + auth.oidc.provider.form + auth.oauth.provider + + + + + + + + + + + + + + + +