From 4dca02ca9c5bc0cac9b66f6110fb53aec4524f84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Schenkels?= Date: Fri, 5 Oct 2018 15:43:47 +0200 Subject: [PATCH 01/33] [ADD] auth_oidc module --- auth_oidc/__init__.py | 6 +++ auth_oidc/__manifest__.py | 25 +++++++++ auth_oidc/controllers/__init__.py | 6 +++ auth_oidc/controllers/main.py | 25 +++++++++ auth_oidc/models/__init__.py | 6 +++ auth_oidc/models/auth_oauth_provider.py | 64 ++++++++++++++++++++++++ auth_oidc/models/res_users.py | 25 +++++++++ auth_oidc/static/description/icon.png | Bin 0 -> 2780 bytes auth_oidc/views/auth_oauth_provider.xml | 14 ++++++ 9 files changed, 171 insertions(+) create mode 100644 auth_oidc/__init__.py create mode 100644 auth_oidc/__manifest__.py create mode 100644 auth_oidc/controllers/__init__.py create mode 100644 auth_oidc/controllers/main.py create mode 100644 auth_oidc/models/__init__.py create mode 100644 auth_oidc/models/auth_oauth_provider.py create mode 100644 auth_oidc/models/res_users.py create mode 100755 auth_oidc/static/description/icon.png create mode 100644 auth_oidc/views/auth_oauth_provider.xml diff --git a/auth_oidc/__init__.py b/auth_oidc/__init__.py new file mode 100644 index 0000000000..000324e3e2 --- /dev/null +++ b/auth_oidc/__init__.py @@ -0,0 +1,6 @@ +# -*- coding: utf-8 -*- +# Copyright© 2016 ICTSTUDIO +# License: AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) + +from . import controllers +from . import models diff --git a/auth_oidc/__manifest__.py b/auth_oidc/__manifest__.py new file mode 100644 index 0000000000..bb96a321f1 --- /dev/null +++ b/auth_oidc/__manifest__.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# Copyright© 2016 ICTSTUDIO +# License: AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) + +{ + 'name': 'Authentication OpenID Connect', + 'version': '10.0.1.0.0', + 'license': 'AGPL-3', + 'author': 'ICTSTUDIO, André Schenkels', + 'website': 'https://www.ictstudio.eu', + 'description': """ +Allow users to login through OpenID Connect Provider. +===================================================== +- Keycloak with ClientID and Secret + Implicit Flow + +""", + 'external_dependencies': {'python': [ + 'jose', + 'cryptography' + ]}, + 'depends': ['auth_oauth'], + 'data': [ + 'views/auth_oauth_provider.xml', + ], +} diff --git a/auth_oidc/controllers/__init__.py b/auth_oidc/controllers/__init__.py new file mode 100644 index 0000000000..832d7f07a1 --- /dev/null +++ b/auth_oidc/controllers/__init__.py @@ -0,0 +1,6 @@ +# -*- coding: utf-8 -*- +# Copyright© 2016 ICTSTUDIO +# License: AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) + +import main + diff --git a/auth_oidc/controllers/main.py b/auth_oidc/controllers/main.py new file mode 100644 index 0000000000..7e99f1e930 --- /dev/null +++ b/auth_oidc/controllers/main.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# Copyright© 2016 ICTSTUDIO +# License: AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) + +import werkzeug.utils +import uuid + +from odoo.addons.auth_oauth.controllers.main import OAuthLogin + + +class OpenIDLogin(OAuthLogin): + + def list_providers(self): + providers = super(OpenIDLogin, self).list_providers() + for provider in providers: + if provider.get('flow') == 'id_token': + provider['nonce'] = uuid.uuid1().hex + params = werkzeug.url_decode(provider['auth_link'].split('?')[-1]) + params.pop('response_type') + params.update(dict(response_type='id_token', + nonce=provider['nonce'])) + if provider.get('scope'): + params['scope'] = provider['scope'] + provider['auth_link'] = "%s?%s" % (provider['auth_endpoint'], werkzeug.url_encode(params)) + return providers diff --git a/auth_oidc/models/__init__.py b/auth_oidc/models/__init__.py new file mode 100644 index 0000000000..37e1a690a6 --- /dev/null +++ b/auth_oidc/models/__init__.py @@ -0,0 +1,6 @@ +# -*- coding: utf-8 -*- +# 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/auth_oidc/models/auth_oauth_provider.py b/auth_oidc/models/auth_oauth_provider.py new file mode 100644 index 0000000000..a593736b9a --- /dev/null +++ b/auth_oidc/models/auth_oauth_provider.py @@ -0,0 +1,64 @@ +# -*- coding: utf-8 -*- +# Copyright© 2016 ICTSTUDIO +# License: AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) + +from odoo import models, fields, api +from jose import jwt +import urllib2 +import json + + +class AuthOauthProvider(models.Model): + _inherit = 'auth.oauth.provider' + + flow = fields.Selection([ + ('access_token', 'OAuth2'), + ('id_token', 'OpenID Connect') + ], + 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.") + + validation_endpoint = fields.Char( + help='For OpenID Connect this should be the location for public keys ') + + @api.model + def _get_key(self, header): + if self.flow != 'id_token': + return False + f = urllib2.urlopen(self.validation_endpoint) + response = json.loads(f.read()) + rsa_key = {} + for key in response["keys"]: + if key["kid"] == header.get('kid'): + rsa_key = key + return rsa_key + + @api.model + def map_token_values(self, res): + if self.token_map: + for pair in self.token_map.split(' '): + from_key, to_key = pair.split(':') + if to_key not in res: + res[to_key] = res.get(from_key, '') + return res + + @api.multi + def _parse_id_token(self, id_token): + self.ensure_one() + res = {} + header = jwt.get_unverified_header(id_token) + res.update(jwt.decode( + id_token, + self._get_key(header), + algorithms=['RS256'], + audience=self.client_id)) + + res.update(self.map_token_values(res)) + return res \ No newline at end of file diff --git a/auth_oidc/models/res_users.py b/auth_oidc/models/res_users.py new file mode 100644 index 0000000000..1c67ab2d43 --- /dev/null +++ b/auth_oidc/models/res_users.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# Copyright© 2016 ICTSTUDIO +# License: AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) + +from odoo import models, api + + +class ResUsers(models.Model): + _inherit = 'res.users' + + @api.model + def _auth_oauth_validate(self, provider, access_token): + """ return the validation data corresponding to the access token """ + oauth_provider = self.env['auth.oauth.provider'].browse(provider) + if oauth_provider.flow == 'id_token': + return oauth_provider._parse_id_token(access_token) + else: + return super(ResUsers, self)._auth_oauth_validate() + + @api.model + def auth_oauth(self, provider, params): + id_token = params.get('id_token') + if id_token: + params['access_token'] = id_token + return super(ResUsers, self).auth_oauth(provider, params) diff --git a/auth_oidc/static/description/icon.png b/auth_oidc/static/description/icon.png new file mode 100755 index 0000000000000000000000000000000000000000..06a05ebd480fdd359c2600a27660aced7a5a48a0 GIT binary patch literal 2780 zcma)8_gfN-7Dhxcw`q!d?}`e^kpow1&aAX7Z>S}1Gc_lPBhy4$_&E?q!;xw3G?Srz zWrZVirKmX)Ck`xy9M|vu1K)klc%SDz=lt?M=Z81L)!9~9Kvn<%00`ULS-Tw|_MhSh zAM_40?=J@c_HnSa2JHV6#cgFz512!@?5;!{TnztHJeJzTm;(|LY42nM`2hh59zJ#T zh5o@zAi&<*;$qC`3TgEr!Q;qB3KKZ?B16;b0ZKrQG2}ygCh2bJjkbw)$nW#SW!NFq z0~7x43FeB<3;J$e%A;9N`Uie9$Zt45CjI$n0Q!#3*QiS2ORrD%1s-{c{7U`HixHo& z&M1QSwzrG0>GknuVW=XK(jNx9jaeAM!m#*27GnRJsn;y8U{1m#h-9sn^Qr(L@c5<^ z9G>2_8$XuSkB6Ns|GP4zscHcS@l6m(R7&sky1Dwz9@ih zY%fIL3SY!*<+mwOCMlWPTyulwFLz>rGC-+$t_-#gJb;TXtDX+(-4}rEuB=k&j`I;o zNA%V%_ZlZeyYR>jzXk#Pt4auUO8lCE4k?Wh{FmPY`($58>cb_)3wKwR47ZnLZ>{8q zuY+p9gmZVBLV9o=DZJWJOk94zN~!h`Wsb0fb8Adf`!#&6ro0i)rxt)>#$(Tg z0AGIfoSk5Xf{PHTZ}dckQO${Z>4X?;&*G1Q(WRAN=f>A#cWOgNFn0BwtR0R7TKN+ z(jL})?+nc>b*}MG1NaH6-i*6z%K9`^C4u9I{w(2<6vUwX3SyR&MmP*XDjk6}oc|%O zFJ59Y@^G43_N=nR-2V~@nTEXz1*c2m2b>yulQPotKX5;*wVpp3>>y;Hs<5<5+s-i; zjjZxtJFFWQ6c9;|PgXvm4!E%>63Yg)NCCu+)GjVcXuDsx7Ybe6t7f)N^d2vFm;>#Y z(0YDtp>{rL9Tsoy?Mn)M;@vI+q08zDL3y7ub);O4SbNoO%s8^}bOEhH1)wde89Mm~ z_<*t9(Pj!wyscD|(83YAN5~abdybHoA?kZgW)>^hsEQ*|CthA;u;s3!Y=geBGkGzk z(YLJt1ZWmudD-j!`kMrv`9%Q%WVh94p9+atV}2ZvoVH^cH?G!tl0j)yshJrVd+YJA zC>GLM7_X8CZ|*@);*FC)N$hTR2R00ELNU?DTD3N%DusKkj8&d7ualaA{El@IlfnW2 z5MCh?5_h(;@_a)VK?-_U_gPMMiTbEk-?5f?(pGiHc9ItM4C}<|P4g;wyV97=n+MV) zQ8(%fi=X=fPF<+|lq|CHVW;1}tp(Ll0PWhG+UNtM35vE9bp(tPGvJCWugr7a_qPCa z+WeHMWre5J%hTSt`2 z(n*2+R76n1i)PP!+rml4OjwK8g$ZU_ind|^InS*zbVvf!t7RtypqRnLcx5~eKEF8f zwPGtL&5m*;cFz({@F@}fNoyBQ(DR+WJbp~qzB(P?Sm;8ojqE?FHDM!8iv)tJEtIHtw zwW`~3O>I#rdOcVmmy`uh&6C+dCAfi5@xqQRzld*0jyk{9+x+5tJ%yyX*8;N3z3Vhm z7To>tnw77$>c7)`mD*sjoAgr5=d#4fxZQ`U*b}mQYzhqEQQEH-;I|XAimoO;MV}nc z=X%YZj|6N_6yDW$y8Jy6cHNm}q!ARpuUy2XRd8rn5*A2yZ;uhkCfF?V*yzQJklRQ44;b%Fe9TQ{ASmfbp^ zyxr?%1k;f}_Tdq-bmO>&*W;Nsr?Pl<2FmRr63FKT?c`Ulm~Sf07(YWQQ@w2p>$G8O#*uSyQZ!KP`3_{qTyjQ6qfRUnfeU zhXHN6GO#BzGYes7b`{;JQlp7&B4v(`J86?vl$;otDbe*M#_m+R4wev~qQky}&>jMX$k?JLfd& z;Yg|I>h$QIMq1WwjtqTR(@Xf@%%#Vq`ed;dw6W^wIH75#28yU2cUfl!)CEWg{ z8_lRP?d*?x_c9x-+b;b!*ipnU(3q16DEw7@A;KB=kh=ZOoBj>yA^kh-`|N#0tJm?0 zmyI^Kkf(!6%@S&&q55Y<5-U?odAlGNS~5T@I=)lur>I?XPM3FSXR;v0^Wx#3i+Af~ z_l-ieljFFyKQdR3vD&|Qjpt$(D-C32b3V`>%eS2SU{2Z7C}**j8Xe85LZi>LRLY?# zwUimvY{x$OvO)BaJ@iKE+5ikcwI)}FT?n8sLu;yz8lp8ys*mG>CsNDIx4v`09~-v$ ziqS?glZfrgwCA>37 z2p{2$>2;0Y&j@dS+EtyyI+h2;+94F4N#-Q%Ua_3I^*Bd`dL*`Ly-M47zAKlmmn6v- z9j>AtnG<|1Pzy1UwF=7!L28m0kg&gMA=seYS^sV2W5M$}gw~_fbOdAd5#nDR9H`{p aj#6WMOub(s7;@l40rob|*437% + + + auth.oidc.provider.form + auth.oauth.provider + + + + + + + + + From 4b69d7869985270b22d887fadb1370bd9b8273cb Mon Sep 17 00:00:00 2001 From: Alexandre Fayolle Date: Thu, 19 Nov 2020 18:15:08 +0100 Subject: [PATCH 02/33] Adapt to OCA standards update manifest, add README, update requirements.txt --- auth_oidc/README.rst | 57 +++++++++++++++++++++++++ auth_oidc/__init__.py | 2 +- auth_oidc/__manifest__.py | 13 ++---- auth_oidc/controllers/__init__.py | 5 +-- auth_oidc/controllers/main.py | 13 ++++-- auth_oidc/models/__init__.py | 2 +- auth_oidc/models/auth_oauth_provider.py | 11 +++-- auth_oidc/models/res_users.py | 4 +- auth_oidc/readme/CONFIGURE.rst | 28 ++++++++++++ auth_oidc/readme/CONTRIBUTORS.rst | 1 + auth_oidc/readme/DESCRIPTION.rst | 6 +++ auth_oidc/readme/HISTORY.rst | 4 ++ auth_oidc/readme/ROADMAP.rst | 3 ++ auth_oidc/readme/USAGE.rst | 1 + 14 files changed, 128 insertions(+), 22 deletions(-) create mode 100644 auth_oidc/README.rst create mode 100644 auth_oidc/readme/CONFIGURE.rst create mode 100644 auth_oidc/readme/CONTRIBUTORS.rst create mode 100644 auth_oidc/readme/DESCRIPTION.rst create mode 100644 auth_oidc/readme/HISTORY.rst create mode 100644 auth_oidc/readme/ROADMAP.rst create mode 100644 auth_oidc/readme/USAGE.rst diff --git a/auth_oidc/README.rst b/auth_oidc/README.rst new file mode 100644 index 0000000000..2bcb185e11 --- /dev/null +++ b/auth_oidc/README.rst @@ -0,0 +1,57 @@ +================================= +Authentication via OpenID Connect +================================= + +This module allows users to login through an OpenID Connect provider. + +This includes: + +- Keycloak with ClientID and secret + Implicit Flow +- Microsoft Azure + + +Usage +===== + +Setup for Microsoft Azure +~~~~~~~~~~~~~~~~~~~~~~~~~ + +# configure a new web application in Azure with OpenID and implicit flow (see + the `provider documentation + `_) +# in this application the redirect url must be be "/auth_oauth/signin" and of course this URL should be reachable from + Azure +# create a new authentication provider in Odoo with the following + parameters (see the `portal documentation + `_ + for more information): + +* Provider Name: Azure +* Auth Flow: OpenID Connect +* Client ID: use the value of the OAuth2 autorization endoing (v2) from the Azure Endpoints list +* Body: Azure SSO +* Authentication URL: use the value of "OAuth2 autorization endpoint (v2)" from the Azure endpoints list +* Scope: openid email +* Validation URL: use the value of "OAuth2 token endpoint (v2)" from the Azure endpoints list +* Allowed: yes + + +Setup for Keycloak +~~~~~~~~~~~~~~~~~~ + +write me... + + +Credits +======= + +Authors +~~~~~~~ + +* ICTSTUDIO, André Schenkels + +Contributors +~~~~~~~~~~~~ + +* Alexandre Fayolle diff --git a/auth_oidc/__init__.py b/auth_oidc/__init__.py index 000324e3e2..19700dee41 100644 --- a/auth_oidc/__init__.py +++ b/auth_oidc/__init__.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright© 2016 ICTSTUDIO +# Copyright 2016 ICTSTUDIO # License: AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) from . import controllers diff --git a/auth_oidc/__manifest__.py b/auth_oidc/__manifest__.py index bb96a321f1..98e4966a01 100644 --- a/auth_oidc/__manifest__.py +++ b/auth_oidc/__manifest__.py @@ -1,19 +1,14 @@ # -*- coding: utf-8 -*- -# Copyright© 2016 ICTSTUDIO +# Copyright 2016 ICTSTUDIO # License: AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) { 'name': 'Authentication OpenID Connect', 'version': '10.0.1.0.0', 'license': 'AGPL-3', - 'author': 'ICTSTUDIO, André Schenkels', - 'website': 'https://www.ictstudio.eu', - 'description': """ -Allow users to login through OpenID Connect Provider. -===================================================== -- Keycloak with ClientID and Secret + Implicit Flow - -""", + 'author': 'ICTSTUDIO, André Schenkels, Odoo Community Association (OCA)', + 'website': 'https://github.com/OCA/server-auth', + 'summary': "Allow users to login through OpenID Connect Provider", 'external_dependencies': {'python': [ 'jose', 'cryptography' diff --git a/auth_oidc/controllers/__init__.py b/auth_oidc/controllers/__init__.py index 832d7f07a1..ed684a50f9 100644 --- a/auth_oidc/controllers/__init__.py +++ b/auth_oidc/controllers/__init__.py @@ -1,6 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright© 2016 ICTSTUDIO +# Copyright 2016 ICTSTUDIO # License: AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) -import main - +from . import main diff --git a/auth_oidc/controllers/main.py b/auth_oidc/controllers/main.py index 7e99f1e930..135ea9a005 100644 --- a/auth_oidc/controllers/main.py +++ b/auth_oidc/controllers/main.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright© 2016 ICTSTUDIO +# Copyright 2016 ICTSTUDIO # License: AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) import werkzeug.utils @@ -15,11 +15,18 @@ def list_providers(self): for provider in providers: if provider.get('flow') == 'id_token': provider['nonce'] = uuid.uuid1().hex - params = werkzeug.url_decode(provider['auth_link'].split('?')[-1]) + params = werkzeug.url_decode( + provider['auth_link'].split('?')[-1] + ) params.pop('response_type') params.update(dict(response_type='id_token', nonce=provider['nonce'])) if provider.get('scope'): params['scope'] = provider['scope'] - provider['auth_link'] = "%s?%s" % (provider['auth_endpoint'], werkzeug.url_encode(params)) + provider['auth_link'] = ( + "%s?%s" % ( + provider['auth_endpoint'], + werkzeug.url_encode(params) + ) + ) return providers diff --git a/auth_oidc/models/__init__.py b/auth_oidc/models/__init__.py index 37e1a690a6..b32fa1a4d3 100644 --- a/auth_oidc/models/__init__.py +++ b/auth_oidc/models/__init__.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright© 2016 ICTSTUDIO +# Copyright 2016 ICTSTUDIO # License: AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) from . import auth_oauth_provider diff --git a/auth_oidc/models/auth_oauth_provider.py b/auth_oidc/models/auth_oauth_provider.py index a593736b9a..7d2a0040f0 100644 --- a/auth_oidc/models/auth_oauth_provider.py +++ b/auth_oidc/models/auth_oauth_provider.py @@ -1,11 +1,16 @@ # -*- coding: utf-8 -*- -# Copyright© 2016 ICTSTUDIO +# Copyright 2016 ICTSTUDIO # License: AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) from odoo import models, fields, api -from jose import jwt import urllib2 import json +import logging + +try: + from jose import jwt +except ImportError: + logging.getLogger(__name__).debug('jose library not installed') class AuthOauthProvider(models.Model): @@ -61,4 +66,4 @@ def _parse_id_token(self, id_token): audience=self.client_id)) res.update(self.map_token_values(res)) - return res \ No newline at end of file + return res diff --git a/auth_oidc/models/res_users.py b/auth_oidc/models/res_users.py index 1c67ab2d43..7ebbce2446 100644 --- a/auth_oidc/models/res_users.py +++ b/auth_oidc/models/res_users.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright© 2016 ICTSTUDIO +# Copyright 2016 ICTSTUDIO # License: AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) from odoo import models, api @@ -16,7 +16,7 @@ def _auth_oauth_validate(self, provider, access_token): return oauth_provider._parse_id_token(access_token) else: return super(ResUsers, self)._auth_oauth_validate() - + @api.model def auth_oauth(self, provider, params): id_token = params.get('id_token') diff --git a/auth_oidc/readme/CONFIGURE.rst b/auth_oidc/readme/CONFIGURE.rst new file mode 100644 index 0000000000..f2ac933218 --- /dev/null +++ b/auth_oidc/readme/CONFIGURE.rst @@ -0,0 +1,28 @@ +Setup for Microsoft Azure +~~~~~~~~~~~~~~~~~~~~~~~~~ + +# configure a new web application in Azure with OpenID and implicit flow (see + the `provider documentation + `_) +# in this application the redirect url must be be "/auth_oauth/signin" and of course this URL should be reachable from + Azure +# create a new authentication provider in Odoo with the following + parameters (see the `portal documentation + `_ + for more information): + +* Provider Name: Azure +* Auth Flow: OpenID Connect +* Client ID: use the value of the OAuth2 autorization endoing (v2) from the Azure Endpoints list +* Body: Azure SSO +* Authentication URL: use the value of "OAuth2 autorization endpoint (v2)" from the Azure endpoints list +* Scope: openid email +* Validation URL: use the value of "OAuth2 token endpoint (v2)" from the Azure endpoints list +* Allowed: yes + + +Setup for Keycloak +~~~~~~~~~~~~~~~~~~ + +write me... diff --git a/auth_oidc/readme/CONTRIBUTORS.rst b/auth_oidc/readme/CONTRIBUTORS.rst new file mode 100644 index 0000000000..8785f72c06 --- /dev/null +++ b/auth_oidc/readme/CONTRIBUTORS.rst @@ -0,0 +1 @@ +* Alexandre Fayolle diff --git a/auth_oidc/readme/DESCRIPTION.rst b/auth_oidc/readme/DESCRIPTION.rst new file mode 100644 index 0000000000..540c7c2c95 --- /dev/null +++ b/auth_oidc/readme/DESCRIPTION.rst @@ -0,0 +1,6 @@ +This module allows users to login through an OpenID Connect provider. + +This includes: + +- Keycloak with ClientID and secret + Implicit Flow +- Microsoft Azure diff --git a/auth_oidc/readme/HISTORY.rst b/auth_oidc/readme/HISTORY.rst new file mode 100644 index 0000000000..5aee5128a7 --- /dev/null +++ b/auth_oidc/readme/HISTORY.rst @@ -0,0 +1,4 @@ +10.0.1.0.0 2018-10-05 +~~~~~~~~~~~~~~~~~~~~~ + +* Initial implementation diff --git a/auth_oidc/readme/ROADMAP.rst b/auth_oidc/readme/ROADMAP.rst new file mode 100644 index 0000000000..c99e29f2c9 --- /dev/null +++ b/auth_oidc/readme/ROADMAP.rst @@ -0,0 +1,3 @@ + +* 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/auth_oidc/readme/USAGE.rst b/auth_oidc/readme/USAGE.rst new file mode 100644 index 0000000000..bb66969269 --- /dev/null +++ b/auth_oidc/readme/USAGE.rst @@ -0,0 +1 @@ +On the login page, click on the authentication provider you configured. From 14a927f500e89df7a21ca9a3c307c12cf929a966 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Bidoul?= Date: Wed, 24 Mar 2021 19:16:25 +0100 Subject: [PATCH 03/33] auth_oidc: improve docs Add some install instructions and configuration instructions for keycloak. --- auth_oidc/readme/CONFIGURE.rst | 20 +++++++++++++++++++- auth_oidc/readme/INSTALL.rst | 2 ++ 2 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 auth_oidc/readme/INSTALL.rst diff --git a/auth_oidc/readme/CONFIGURE.rst b/auth_oidc/readme/CONFIGURE.rst index f2ac933218..c2ea288891 100644 --- a/auth_oidc/readme/CONFIGURE.rst +++ b/auth_oidc/readme/CONFIGURE.rst @@ -25,4 +25,22 @@ Setup for Microsoft Azure Setup for Keycloak ~~~~~~~~~~~~~~~~~~ -write me... +In Keycloak: + +# configure a new Client +# make sure Implicit Flow is Enabled. +# 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 +* Client ID: the same Client ID you entered when configuring the client in Keycloak +* Allowed: yes +* Body: the link text to appear on the login page, such as Login with Keycloak +* Authentication URL: The "authorization_endpoint" URL found in the + OpenID Endpoint Configuration of your Keycloak realm +* Scope: email +* Validation URL: The "jwks_uri" URL found in the + OpenID Endpoint Configuration of your Keycloak realm diff --git a/auth_oidc/readme/INSTALL.rst b/auth_oidc/readme/INSTALL.rst new file mode 100644 index 0000000000..bccbbbad79 --- /dev/null +++ b/auth_oidc/readme/INSTALL.rst @@ -0,0 +1,2 @@ +This module depends on the `python-jose `__ +library, not to be confused with ``jose`` which is also available on PyPI. From 48b395e88861fe6a393e8d66efa8ad405f20e2b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Bidoul?= Date: Fri, 9 Apr 2021 16:07:29 +0200 Subject: [PATCH 04/33] [MIG] auth_oidc: update dotfiles --- auth_oidc/__init__.py | 1 - auth_oidc/__manifest__.py | 24 +++++------ auth_oidc/controllers/__init__.py | 1 - auth_oidc/controllers/main.py | 29 +++++-------- auth_oidc/models/__init__.py | 1 - auth_oidc/models/auth_oauth_provider.py | 54 +++++++++++++------------ auth_oidc/models/res_users.py | 13 +++--- auth_oidc/readme/CONFIGURE.rst | 2 +- auth_oidc/readme/USAGE.rst | 2 +- auth_oidc/views/auth_oauth_provider.xml | 9 +++-- 10 files changed, 63 insertions(+), 73 deletions(-) diff --git a/auth_oidc/__init__.py b/auth_oidc/__init__.py index 19700dee41..8b36099f76 100644 --- a/auth_oidc/__init__.py +++ b/auth_oidc/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2016 ICTSTUDIO # License: AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) diff --git a/auth_oidc/__manifest__.py b/auth_oidc/__manifest__.py index 98e4966a01..e96ca28480 100644 --- a/auth_oidc/__manifest__.py +++ b/auth_oidc/__manifest__.py @@ -1,20 +1,14 @@ -# -*- coding: utf-8 -*- # Copyright 2016 ICTSTUDIO # License: AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) { - 'name': 'Authentication OpenID Connect', - 'version': '10.0.1.0.0', - 'license': 'AGPL-3', - 'author': 'ICTSTUDIO, André Schenkels, Odoo Community Association (OCA)', - 'website': 'https://github.com/OCA/server-auth', - 'summary': "Allow users to login through OpenID Connect Provider", - 'external_dependencies': {'python': [ - 'jose', - 'cryptography' - ]}, - 'depends': ['auth_oauth'], - 'data': [ - 'views/auth_oauth_provider.xml', - ], + "name": "Authentication OpenID Connect", + "version": "13.0.1.0.0", + "license": "AGPL-3", + "author": "ICTSTUDIO, André Schenkels, Odoo Community Association (OCA)", + "website": "https://github.com/OCA/server-auth", + "summary": "Allow users to login through OpenID Connect Provider", + "external_dependencies": {"python": ["jose", "cryptography"]}, + "depends": ["auth_oauth"], + "data": ["views/auth_oauth_provider.xml"], } diff --git a/auth_oidc/controllers/__init__.py b/auth_oidc/controllers/__init__.py index ed684a50f9..eaa83817e9 100644 --- a/auth_oidc/controllers/__init__.py +++ b/auth_oidc/controllers/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2016 ICTSTUDIO # License: AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) diff --git a/auth_oidc/controllers/main.py b/auth_oidc/controllers/main.py index 135ea9a005..466a09c89a 100644 --- a/auth_oidc/controllers/main.py +++ b/auth_oidc/controllers/main.py @@ -1,32 +1,25 @@ -# -*- coding: utf-8 -*- # Copyright 2016 ICTSTUDIO # License: AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) -import werkzeug.utils import uuid +import werkzeug.utils + from odoo.addons.auth_oauth.controllers.main import OAuthLogin class OpenIDLogin(OAuthLogin): - def list_providers(self): providers = super(OpenIDLogin, self).list_providers() for provider in providers: - if provider.get('flow') == 'id_token': - provider['nonce'] = uuid.uuid1().hex - params = werkzeug.url_decode( - provider['auth_link'].split('?')[-1] - ) - params.pop('response_type') - params.update(dict(response_type='id_token', - nonce=provider['nonce'])) - if provider.get('scope'): - params['scope'] = provider['scope'] - provider['auth_link'] = ( - "%s?%s" % ( - provider['auth_endpoint'], - werkzeug.url_encode(params) - ) + if provider.get("flow") == "id_token": + provider["nonce"] = uuid.uuid1().hex + params = werkzeug.url_decode(provider["auth_link"].split("?")[-1]) + params.pop("response_type") + params.update(dict(response_type="id_token", nonce=provider["nonce"])) + if provider.get("scope"): + params["scope"] = provider["scope"] + provider["auth_link"] = "{}?{}".format( + provider["auth_endpoint"], werkzeug.url_encode(params) ) return providers diff --git a/auth_oidc/models/__init__.py b/auth_oidc/models/__init__.py index b32fa1a4d3..ebc8e5bfda 100644 --- a/auth_oidc/models/__init__.py +++ b/auth_oidc/models/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2016 ICTSTUDIO # License: AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) diff --git a/auth_oidc/models/auth_oauth_provider.py b/auth_oidc/models/auth_oauth_provider.py index 7d2a0040f0..5ca62b58d6 100644 --- a/auth_oidc/models/auth_oauth_provider.py +++ b/auth_oidc/models/auth_oauth_provider.py @@ -1,57 +1,58 @@ -# -*- coding: utf-8 -*- # Copyright 2016 ICTSTUDIO # License: AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) -from odoo import models, fields, api -import urllib2 import json import logging +import urllib2 + +from odoo import api, fields, models try: from jose import jwt except ImportError: - logging.getLogger(__name__).debug('jose library not installed') + logging.getLogger(__name__).debug("jose library not installed") class AuthOauthProvider(models.Model): - _inherit = 'auth.oauth.provider' + _inherit = "auth.oauth.provider" - flow = fields.Selection([ - ('access_token', 'OAuth2'), - ('id_token', 'OpenID Connect') - ], - string='Auth Flow', + flow = fields.Selection( + [("access_token", "OAuth2"), ("id_token", "OpenID Connect")], + string="Auth Flow", required=True, - default='access_token') + 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.") + "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." + ) validation_endpoint = fields.Char( - help='For OpenID Connect this should be the location for public keys ') + help="For OpenID Connect this should be the location for public keys " + ) @api.model def _get_key(self, header): - if self.flow != 'id_token': + if self.flow != "id_token": return False f = urllib2.urlopen(self.validation_endpoint) response = json.loads(f.read()) rsa_key = {} for key in response["keys"]: - if key["kid"] == header.get('kid'): + if key["kid"] == header.get("kid"): rsa_key = key return rsa_key @api.model def map_token_values(self, res): if self.token_map: - for pair in self.token_map.split(' '): - from_key, to_key = pair.split(':') + for pair in self.token_map.split(" "): + from_key, to_key = pair.split(":") if to_key not in res: - res[to_key] = res.get(from_key, '') + res[to_key] = res.get(from_key, "") return res @api.multi @@ -59,11 +60,14 @@ def _parse_id_token(self, id_token): self.ensure_one() res = {} header = jwt.get_unverified_header(id_token) - res.update(jwt.decode( - id_token, - self._get_key(header), - algorithms=['RS256'], - audience=self.client_id)) + res.update( + jwt.decode( + id_token, + self._get_key(header), + algorithms=["RS256"], + audience=self.client_id, + ) + ) res.update(self.map_token_values(res)) return res diff --git a/auth_oidc/models/res_users.py b/auth_oidc/models/res_users.py index 7ebbce2446..c72bcd2af8 100644 --- a/auth_oidc/models/res_users.py +++ b/auth_oidc/models/res_users.py @@ -1,25 +1,24 @@ -# -*- coding: utf-8 -*- # Copyright 2016 ICTSTUDIO # License: AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) -from odoo import models, api +from odoo import api, models class ResUsers(models.Model): - _inherit = 'res.users' + _inherit = "res.users" @api.model def _auth_oauth_validate(self, provider, access_token): """ return the validation data corresponding to the access token """ - oauth_provider = self.env['auth.oauth.provider'].browse(provider) - if oauth_provider.flow == 'id_token': + oauth_provider = self.env["auth.oauth.provider"].browse(provider) + if oauth_provider.flow == "id_token": return oauth_provider._parse_id_token(access_token) else: return super(ResUsers, self)._auth_oauth_validate() @api.model def auth_oauth(self, provider, params): - id_token = params.get('id_token') + id_token = params.get("id_token") if id_token: - params['access_token'] = id_token + params["access_token"] = id_token return super(ResUsers, self).auth_oauth(provider, params) diff --git a/auth_oidc/readme/CONFIGURE.rst b/auth_oidc/readme/CONFIGURE.rst index c2ea288891..4993f4aea6 100644 --- a/auth_oidc/readme/CONFIGURE.rst +++ b/auth_oidc/readme/CONFIGURE.rst @@ -34,7 +34,7 @@ In Keycloak: In Odoo, create a new Oauth Provider with the following parameters: * Provider name: Keycloak (or any name you like that identify your keycloak -provider) + provider) * Auth Flow: OpenID Connect * Client ID: the same Client ID you entered when configuring the client in Keycloak * Allowed: yes diff --git a/auth_oidc/readme/USAGE.rst b/auth_oidc/readme/USAGE.rst index bb66969269..0fa74256b4 100644 --- a/auth_oidc/readme/USAGE.rst +++ b/auth_oidc/readme/USAGE.rst @@ -1 +1 @@ -On the login page, click on the authentication provider you configured. +On the login page, click on the authentication provider you configured. diff --git a/auth_oidc/views/auth_oauth_provider.xml b/auth_oidc/views/auth_oauth_provider.xml index 0cfe735814..bf7b20ddc4 100644 --- a/auth_oidc/views/auth_oauth_provider.xml +++ b/auth_oidc/views/auth_oauth_provider.xml @@ -1,4 +1,4 @@ - + auth.oidc.provider.form @@ -6,8 +6,11 @@ - - + + From ca377ba7211c91876ea950c734fd371395200431 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Bidoul?= Date: Fri, 9 Apr 2021 17:56:49 +0200 Subject: [PATCH 05/33] [MIG] auth_oidc from 10.0 --- auth_oidc/__manifest__.py | 2 +- auth_oidc/models/auth_oauth_provider.py | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/auth_oidc/__manifest__.py b/auth_oidc/__manifest__.py index e96ca28480..78b15bf229 100644 --- a/auth_oidc/__manifest__.py +++ b/auth_oidc/__manifest__.py @@ -8,7 +8,7 @@ "author": "ICTSTUDIO, André Schenkels, Odoo Community Association (OCA)", "website": "https://github.com/OCA/server-auth", "summary": "Allow users to login through OpenID Connect Provider", - "external_dependencies": {"python": ["jose", "cryptography"]}, + "external_dependencies": {"python": ["python-jose"]}, "depends": ["auth_oauth"], "data": ["views/auth_oauth_provider.xml"], } diff --git a/auth_oidc/models/auth_oauth_provider.py b/auth_oidc/models/auth_oauth_provider.py index 5ca62b58d6..93cf1820a5 100644 --- a/auth_oidc/models/auth_oauth_provider.py +++ b/auth_oidc/models/auth_oauth_provider.py @@ -1,9 +1,9 @@ # Copyright 2016 ICTSTUDIO # License: AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) -import json import logging -import urllib2 + +import requests from odoo import api, fields, models @@ -38,8 +38,9 @@ class AuthOauthProvider(models.Model): def _get_key(self, header): if self.flow != "id_token": return False - f = urllib2.urlopen(self.validation_endpoint) - response = json.loads(f.read()) + r = requests.get(self.validation_endpoint) + r.raise_for_status() + response = r.json() rsa_key = {} for key in response["keys"]: if key["kid"] == header.get("kid"): @@ -47,7 +48,7 @@ def _get_key(self, header): return rsa_key @api.model - def map_token_values(self, res): + def _map_token_values(self, res): if self.token_map: for pair in self.token_map.split(" "): from_key, to_key = pair.split(":") @@ -55,7 +56,6 @@ def map_token_values(self, res): res[to_key] = res.get(from_key, "") return res - @api.multi def _parse_id_token(self, id_token): self.ensure_one() res = {} @@ -69,5 +69,5 @@ def _parse_id_token(self, id_token): ) ) - res.update(self.map_token_values(res)) + res.update(self._map_token_values(res)) return res From 70b7df15d11aa14276323c1bd62ecd581af66a34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Bidoul?= Date: Fri, 9 Apr 2021 20:03:21 +0200 Subject: [PATCH 06/33] auth_oidc: simplify and use "id_token token" Avoid replacing the access token by the id token. This may cause confusion. Copy a little piece of code from auth_oauth() method, to make the code easier to follow, and prepare for implementing the authorization code flow. --- auth_oidc/controllers/main.py | 7 ++++-- auth_oidc/models/auth_oauth_provider.py | 6 +++-- auth_oidc/models/res_users.py | 29 +++++++++++++++---------- 3 files changed, 26 insertions(+), 16 deletions(-) diff --git a/auth_oidc/controllers/main.py b/auth_oidc/controllers/main.py index 466a09c89a..378b59c1af 100644 --- a/auth_oidc/controllers/main.py +++ b/auth_oidc/controllers/main.py @@ -1,4 +1,5 @@ # Copyright 2016 ICTSTUDIO +# Copyright 2021 ACSONE SA/NV # License: AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) import uuid @@ -13,10 +14,12 @@ def list_providers(self): providers = super(OpenIDLogin, self).list_providers() for provider in providers: if provider.get("flow") == "id_token": - provider["nonce"] = uuid.uuid1().hex + provider["nonce"] = uuid.uuid1().hex # TODO Better nonce params = werkzeug.url_decode(provider["auth_link"].split("?")[-1]) params.pop("response_type") - params.update(dict(response_type="id_token", nonce=provider["nonce"])) + params.update( + dict(response_type="id_token token", nonce=provider["nonce"]) + ) if provider.get("scope"): params["scope"] = provider["scope"] provider["auth_link"] = "{}?{}".format( diff --git a/auth_oidc/models/auth_oauth_provider.py b/auth_oidc/models/auth_oauth_provider.py index 93cf1820a5..875600c6a8 100644 --- a/auth_oidc/models/auth_oauth_provider.py +++ b/auth_oidc/models/auth_oauth_provider.py @@ -1,4 +1,5 @@ # Copyright 2016 ICTSTUDIO +# Copyright 2021 ACSONE SA/NV # License: AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) import logging @@ -17,7 +18,7 @@ class AuthOauthProvider(models.Model): _inherit = "auth.oauth.provider" flow = fields.Selection( - [("access_token", "OAuth2"), ("id_token", "OpenID Connect")], + [("access_token", "OAuth2"), ("id_token", "OpenID Connect (implicit flow")], string="Auth Flow", required=True, default="access_token", @@ -56,7 +57,7 @@ def _map_token_values(self, res): res[to_key] = res.get(from_key, "") return res - def _parse_id_token(self, id_token): + def _parse_id_token(self, id_token, access_token): self.ensure_one() res = {} header = jwt.get_unverified_header(id_token) @@ -66,6 +67,7 @@ def _parse_id_token(self, id_token): self._get_key(header), algorithms=["RS256"], audience=self.client_id, + access_token=access_token, ) ) diff --git a/auth_oidc/models/res_users.py b/auth_oidc/models/res_users.py index c72bcd2af8..571c115618 100644 --- a/auth_oidc/models/res_users.py +++ b/auth_oidc/models/res_users.py @@ -1,24 +1,29 @@ # Copyright 2016 ICTSTUDIO +# Copyright 2021 ACSONE SA/NV # License: AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) from odoo import api, models +from odoo.exceptions import AccessDenied class ResUsers(models.Model): _inherit = "res.users" @api.model - def _auth_oauth_validate(self, provider, access_token): - """ return the validation data corresponding to the access token """ + def auth_oauth(self, provider, params): oauth_provider = self.env["auth.oauth.provider"].browse(provider) - if oauth_provider.flow == "id_token": - return oauth_provider._parse_id_token(access_token) - else: - return super(ResUsers, self)._auth_oauth_validate() + if oauth_provider.flow != "id_token": + return super(ResUsers, self).auth_oauth(provider, params) - @api.model - def auth_oauth(self, provider, params): - id_token = params.get("id_token") - if id_token: - params["access_token"] = id_token - return super(ResUsers, self).auth_oauth(provider, params) + access_token = params.get('access_token') + id_token = params.get('id_token') + validation = oauth_provider._parse_id_token(id_token, access_token) + # required check + if not validation.get('user_id'): + raise AccessDenied() + # retrieve and sign in user + login = self._auth_oauth_signin(provider, validation, params) + if not login: + raise AccessDenied() + # return user credentials + return (self.env.cr.dbname, login, access_token) From a86228acb00d420743ca4eb62aaca6cabb222b7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Bidoul?= Date: Fri, 9 Apr 2021 23:58:10 +0200 Subject: [PATCH 07/33] auth_oidc: _get_key and _map_token_values are not model methods --- auth_oidc/models/auth_oauth_provider.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/auth_oidc/models/auth_oauth_provider.py b/auth_oidc/models/auth_oauth_provider.py index 875600c6a8..8af713d94c 100644 --- a/auth_oidc/models/auth_oauth_provider.py +++ b/auth_oidc/models/auth_oauth_provider.py @@ -6,7 +6,7 @@ import requests -from odoo import api, fields, models +from odoo import fields, models try: from jose import jwt @@ -35,7 +35,6 @@ class AuthOauthProvider(models.Model): help="For OpenID Connect this should be the location for public keys " ) - @api.model def _get_key(self, header): if self.flow != "id_token": return False @@ -48,7 +47,6 @@ def _get_key(self, header): rsa_key = key return rsa_key - @api.model def _map_token_values(self, res): if self.token_map: for pair in self.token_map.split(" "): From e4835e472f69eaaeb985309b235614b623ba295a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Bidoul?= Date: Sat, 10 Apr 2021 00:42:10 +0200 Subject: [PATCH 08/33] auth_oidc: cache _get_key --- auth_oidc/models/auth_oauth_provider.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/auth_oidc/models/auth_oauth_provider.py b/auth_oidc/models/auth_oauth_provider.py index 8af713d94c..38699aec8a 100644 --- a/auth_oidc/models/auth_oauth_provider.py +++ b/auth_oidc/models/auth_oauth_provider.py @@ -6,7 +6,7 @@ import requests -from odoo import fields, models +from odoo import fields, models, tools try: from jose import jwt @@ -35,17 +35,17 @@ class AuthOauthProvider(models.Model): help="For OpenID Connect this should be the location for public keys " ) - def _get_key(self, header): + @tools.ormcache("self.validation_endpoint", "kid") + def _get_key(self, kid): if self.flow != "id_token": return False r = requests.get(self.validation_endpoint) r.raise_for_status() response = r.json() - rsa_key = {} for key in response["keys"]: - if key["kid"] == header.get("kid"): - rsa_key = key - return rsa_key + if key["kid"] == kid: + return key + return {} def _map_token_values(self, res): if self.token_map: @@ -62,7 +62,7 @@ def _parse_id_token(self, id_token, access_token): res.update( jwt.decode( id_token, - self._get_key(header), + self._get_key(header.get("kid")), algorithms=["RS256"], audience=self.client_id, access_token=access_token, From ebca73d8d2c42305ccd0e6225bb9d97514cb6187 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Bidoul?= Date: Sat, 10 Apr 2021 01:43:57 +0200 Subject: [PATCH 09/33] auth_oidc: add authorization code flow --- auth_oidc/controllers/main.py | 21 +++++++++++----- auth_oidc/models/auth_oauth_provider.py | 11 ++++++--- auth_oidc/models/res_users.py | 32 +++++++++++++++++++++---- auth_oidc/views/auth_oauth_provider.xml | 6 +++++ 4 files changed, 57 insertions(+), 13 deletions(-) diff --git a/auth_oidc/controllers/main.py b/auth_oidc/controllers/main.py index 378b59c1af..c4d4a610f7 100644 --- a/auth_oidc/controllers/main.py +++ b/auth_oidc/controllers/main.py @@ -2,25 +2,34 @@ # Copyright 2021 ACSONE SA/NV # License: AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) +import logging import uuid import werkzeug.utils from odoo.addons.auth_oauth.controllers.main import OAuthLogin +_logger = logging.getLogger(__name__) + class OpenIDLogin(OAuthLogin): def list_providers(self): providers = super(OpenIDLogin, self).list_providers() for provider in providers: - if provider.get("flow") == "id_token": - provider["nonce"] = uuid.uuid1().hex # TODO Better nonce + flow = provider.get("flow") + if flow in ("id_token", "id_token_code"): params = werkzeug.url_decode(provider["auth_link"].split("?")[-1]) - params.pop("response_type") - params.update( - dict(response_type="id_token token", nonce=provider["nonce"]) - ) + params["nonce"] = uuid.uuid1().hex # TODO Better nonce + 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" if provider.get("scope"): + if "openid" not in provider["scope"].split(): + _logger.error("openid connect scope must contain 'openid'") params["scope"] = provider["scope"] provider["auth_link"] = "{}?{}".format( provider["auth_endpoint"], werkzeug.url_encode(params) diff --git a/auth_oidc/models/auth_oauth_provider.py b/auth_oidc/models/auth_oauth_provider.py index 38699aec8a..40df2f4d64 100644 --- a/auth_oidc/models/auth_oauth_provider.py +++ b/auth_oidc/models/auth_oauth_provider.py @@ -18,7 +18,11 @@ class AuthOauthProvider(models.Model): _inherit = "auth.oauth.provider" flow = fields.Selection( - [("access_token", "OAuth2"), ("id_token", "OpenID Connect (implicit flow")], + [ + ("access_token", "OAuth2"), + ("id_token", "OpenID Connect (implicit flow)"), + ("id_token_code", "OpenID Connect (authorization code flow)"), + ], string="Auth Flow", required=True, default="access_token", @@ -35,10 +39,11 @@ class AuthOauthProvider(models.Model): help="For OpenID Connect this should be the location for public keys " ) + client_secret = fields.Char() + token_endpoint = fields.Char(string="Token URL") + @tools.ormcache("self.validation_endpoint", "kid") def _get_key(self, kid): - if self.flow != "id_token": - return False r = requests.get(self.validation_endpoint) r.raise_for_status() response = r.json() diff --git a/auth_oidc/models/res_users.py b/auth_oidc/models/res_users.py index 571c115618..f0074ac23e 100644 --- a/auth_oidc/models/res_users.py +++ b/auth_oidc/models/res_users.py @@ -2,8 +2,11 @@ # Copyright 2021 ACSONE SA/NV # License: AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) +import requests + from odoo import api, models from odoo.exceptions import AccessDenied +from odoo.http import request class ResUsers(models.Model): @@ -12,16 +15,37 @@ class ResUsers(models.Model): @api.model def auth_oauth(self, provider, params): oauth_provider = self.env["auth.oauth.provider"].browse(provider) - if oauth_provider.flow != "id_token": + if oauth_provider.flow not in ("id_token", "id_token_code"): return super(ResUsers, self).auth_oauth(provider, params) - access_token = params.get('access_token') - id_token = params.get('id_token') + if oauth_provider.flow == "id_token": + # https://openid.net/specs/openid-connect-core-1_0.html#ImplicitAuthResponse + access_token = params.get("access_token") + id_token = params.get("id_token") + elif oauth_provider.flow == "id_token_code": + # 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 + response = requests.post( + oauth_provider.token_endpoint, + data=dict( + grant_type="authorization_code", + code=code, + redirect_uri=request.httprequest.url_root + "auth_oauth/signin", + ), + auth=(oauth_provider.client_id, oauth_provider.client_secret), + ) + response.raise_for_status() + response_json = response.json() + # https://openid.net/specs/openid-connect-core-1_0.html#TokenResponse + access_token = response_json.get("access_token") + id_token = response_json.get("id_token") validation = oauth_provider._parse_id_token(id_token, access_token) # required check - if not validation.get('user_id'): + if not validation.get("user_id"): 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() diff --git a/auth_oidc/views/auth_oauth_provider.xml b/auth_oidc/views/auth_oauth_provider.xml index bf7b20ddc4..a86880ba2a 100644 --- a/auth_oidc/views/auth_oauth_provider.xml +++ b/auth_oidc/views/auth_oauth_provider.xml @@ -12,6 +12,12 @@ placeholder="e.g from:to upn:email sub:user_id" /> + + + + + + From fe5300bf8a847fbd2daff095dfda8a744e045819 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Bidoul?= Date: Sat, 10 Apr 2021 02:06:27 +0200 Subject: [PATCH 10/33] auth_oidc: separate field for jwks uri --- auth_oidc/models/auth_oauth_provider.py | 12 ++++-------- auth_oidc/views/auth_oauth_provider.xml | 3 ++- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/auth_oidc/models/auth_oauth_provider.py b/auth_oidc/models/auth_oauth_provider.py index 40df2f4d64..d8d0b94a01 100644 --- a/auth_oidc/models/auth_oauth_provider.py +++ b/auth_oidc/models/auth_oauth_provider.py @@ -27,24 +27,20 @@ class AuthOauthProvider(models.Model): 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." ) - - validation_endpoint = fields.Char( - help="For OpenID Connect this should be the location for public keys " - ) - client_secret = fields.Char() + validation_endpoint = fields.Char(required=False) token_endpoint = fields.Char(string="Token URL") + jwks_uri = fields.Char(string="JWKS URL") - @tools.ormcache("self.validation_endpoint", "kid") + @tools.ormcache("self.jwks_uri", "kid") def _get_key(self, kid): - r = requests.get(self.validation_endpoint) + r = requests.get(self.jwks_uri) r.raise_for_status() response = r.json() for key in response["keys"]: diff --git a/auth_oidc/views/auth_oauth_provider.xml b/auth_oidc/views/auth_oauth_provider.xml index a86880ba2a..90c931b417 100644 --- a/auth_oidc/views/auth_oauth_provider.xml +++ b/auth_oidc/views/auth_oauth_provider.xml @@ -15,8 +15,9 @@ - + + From 52df3af0011c2da33099cc871315b77b23da73dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Bidoul?= Date: Sat, 10 Apr 2021 11:11:01 +0200 Subject: [PATCH 11/33] auth_oidc: additional error logging --- auth_oidc/models/res_users.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/auth_oidc/models/res_users.py b/auth_oidc/models/res_users.py index f0074ac23e..b495ed87d2 100644 --- a/auth_oidc/models/res_users.py +++ b/auth_oidc/models/res_users.py @@ -2,12 +2,16 @@ # 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" @@ -40,9 +44,16 @@ def auth_oauth(self, provider, params): # https://openid.net/specs/openid-connect-core-1_0.html#TokenResponse access_token = response_json.get("access_token") id_token = response_json.get("id_token") + 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 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 From 42b361a1a95cdb7c33083894ea87752373640cfb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Bidoul?= Date: Sat, 10 Apr 2021 11:14:05 +0200 Subject: [PATCH 12/33] auth_oidc: add author and maintainer --- auth_oidc/__manifest__.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/auth_oidc/__manifest__.py b/auth_oidc/__manifest__.py index 78b15bf229..a2ff5cae5e 100644 --- a/auth_oidc/__manifest__.py +++ b/auth_oidc/__manifest__.py @@ -1,14 +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": "13.0.1.0.0", "license": "AGPL-3", - "author": "ICTSTUDIO, André Schenkels, Odoo Community Association (OCA)", + "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"], + "demo": ["demo/local_keycloak.xml"], } From 73d134483c674245bc3ab924c14afde1fd11a3c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Bidoul?= Date: Sat, 10 Apr 2021 11:22:49 +0200 Subject: [PATCH 13/33] auth_oidc: improve docs, mention implicit flow is not recommended --- auth_oidc/models/auth_oauth_provider.py | 12 ++++++++---- auth_oidc/readme/CONFIGURE.rst | 18 ++++++++++++++---- auth_oidc/readme/CONTRIBUTORS.rst | 1 + auth_oidc/readme/DESCRIPTION.rst | 9 ++++----- auth_oidc/readme/HISTORY.rst | 5 +++++ auth_oidc/readme/ROADMAP.rst | 1 - 6 files changed, 32 insertions(+), 14 deletions(-) diff --git a/auth_oidc/models/auth_oauth_provider.py b/auth_oidc/models/auth_oauth_provider.py index d8d0b94a01..43a0aa8781 100644 --- a/auth_oidc/models/auth_oauth_provider.py +++ b/auth_oidc/models/auth_oauth_provider.py @@ -20,8 +20,8 @@ class AuthOauthProvider(models.Model): flow = fields.Selection( [ ("access_token", "OAuth2"), - ("id_token", "OpenID Connect (implicit flow)"), ("id_token_code", "OpenID Connect (authorization code flow)"), + ("id_token", "OpenID Connect (implicit flow, not recommended)"), ], string="Auth Flow", required=True, @@ -33,10 +33,14 @@ class AuthOauthProvider(models.Model): "email at least are mapped. For OpenID Connect user_id is " "the sub key in the standard." ) - client_secret = fields.Char() + client_secret = fields.Char( + help="Required for OpenID Connect authorization code flow." + ) validation_endpoint = fields.Char(required=False) - token_endpoint = fields.Char(string="Token URL") - jwks_uri = fields.Char(string="JWKS URL") + 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_key(self, kid): diff --git a/auth_oidc/readme/CONFIGURE.rst b/auth_oidc/readme/CONFIGURE.rst index 4993f4aea6..74ef8ad38d 100644 --- a/auth_oidc/readme/CONFIGURE.rst +++ b/auth_oidc/readme/CONFIGURE.rst @@ -1,6 +1,10 @@ Setup for Microsoft Azure ~~~~~~~~~~~~~~~~~~~~~~~~~ +Example configuration with OpenID Connect implicit flow. +This configuration is not recommended because it exposes the access token +to the client, and in logs. + # configure a new web application in Azure with OpenID and implicit flow (see the `provider documentation `_) @@ -25,22 +29,28 @@ Setup for Microsoft Azure Setup for Keycloak ~~~~~~~~~~~~~~~~~~ +Example configuration with OpenID Connect authorization code flow. + In Keycloak: # configure a new Client -# make sure Implicit Flow is Enabled. +# make sure Authorization Code Flow is Enabled. +# configure the client Access Type as "confidential" and take note of the client secret in the Credentials tab # 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 +* 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 -* Scope: email -* Validation URL: The "jwks_uri" URL found in the +* 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/auth_oidc/readme/CONTRIBUTORS.rst b/auth_oidc/readme/CONTRIBUTORS.rst index 8785f72c06..d721552d86 100644 --- a/auth_oidc/readme/CONTRIBUTORS.rst +++ b/auth_oidc/readme/CONTRIBUTORS.rst @@ -1 +1,2 @@ * Alexandre Fayolle +* Stéphane Bidoul diff --git a/auth_oidc/readme/DESCRIPTION.rst b/auth_oidc/readme/DESCRIPTION.rst index 540c7c2c95..ae89dd9d73 100644 --- a/auth_oidc/readme/DESCRIPTION.rst +++ b/auth_oidc/readme/DESCRIPTION.rst @@ -1,6 +1,5 @@ -This module allows users to login through an OpenID Connect provider. +This module allows users to login through an OpenID Connect provider using the +authorization code flow or implicit flow. -This includes: - -- Keycloak with ClientID and secret + Implicit Flow -- Microsoft Azure +Note the implicit flow is not recommended because it exposes access tokens to +the browser and in http logs. diff --git a/auth_oidc/readme/HISTORY.rst b/auth_oidc/readme/HISTORY.rst index 5aee5128a7..f831ce0e6d 100644 --- a/auth_oidc/readme/HISTORY.rst +++ b/auth_oidc/readme/HISTORY.rst @@ -1,3 +1,8 @@ +13.0.1.0.0 2020-04-10 +~~~~~~~~~~~~~~~~~~~~~ + +* Odoo 13 migration, add authorization code flow. + 10.0.1.0.0 2018-10-05 ~~~~~~~~~~~~~~~~~~~~~ diff --git a/auth_oidc/readme/ROADMAP.rst b/auth_oidc/readme/ROADMAP.rst index c99e29f2c9..6a95f19054 100644 --- a/auth_oidc/readme/ROADMAP.rst +++ b/auth_oidc/readme/ROADMAP.rst @@ -1,3 +1,2 @@ - * 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. From 011ba18103fb2196e5d2f622fa13d7d5064cae0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Bidoul?= Date: Sat, 10 Apr 2021 11:30:10 +0200 Subject: [PATCH 14/33] auth_oidc: better nonce --- auth_oidc/controllers/main.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/auth_oidc/controllers/main.py b/auth_oidc/controllers/main.py index c4d4a610f7..9673cac151 100644 --- a/auth_oidc/controllers/main.py +++ b/auth_oidc/controllers/main.py @@ -3,7 +3,7 @@ # License: AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) import logging -import uuid +import secrets import werkzeug.utils @@ -19,7 +19,7 @@ def list_providers(self): flow = provider.get("flow") if flow in ("id_token", "id_token_code"): params = werkzeug.url_decode(provider["auth_link"].split("?")[-1]) - params["nonce"] = uuid.uuid1().hex # TODO Better nonce + params["nonce"] = secrets.token_urlsafe() if flow == "id_token": # https://openid.net/specs/openid-connect-core-1_0.html # #ImplicitAuthRequest From d990edba44863ac5c8eb46a4713e5dc60cfde440 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Bidoul?= Date: Sat, 10 Apr 2021 12:01:44 +0200 Subject: [PATCH 15/33] auth_oidc: make client secret optional This is not a recommended scenario, but this prepares the code for using PKCE --- auth_oidc/models/res_users.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/auth_oidc/models/res_users.py b/auth_oidc/models/res_users.py index b495ed87d2..43ab713887 100644 --- a/auth_oidc/models/res_users.py +++ b/auth_oidc/models/res_users.py @@ -30,14 +30,18 @@ def auth_oauth(self, 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, redirect_uri=request.httprequest.url_root + "auth_oauth/signin", ), - auth=(oauth_provider.client_id, oauth_provider.client_secret), + auth=auth, ) response.raise_for_status() response_json = response.json() From bc61d1468296d9f9a336fdd7d6fde5805702e30e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Bidoul?= Date: Sat, 10 Apr 2021 13:12:02 +0200 Subject: [PATCH 16/33] auth_oidc: add PKCE support --- auth_oidc/controllers/main.py | 13 +++++++++++++ auth_oidc/models/auth_oauth_provider.py | 6 +++++- auth_oidc/models/res_users.py | 1 + 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/auth_oidc/controllers/main.py b/auth_oidc/controllers/main.py index 9673cac151..dbc7dac69c 100644 --- a/auth_oidc/controllers/main.py +++ b/auth_oidc/controllers/main.py @@ -2,6 +2,8 @@ # 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 @@ -19,7 +21,9 @@ def list_providers(self): flow = provider.get("flow") if flow in ("id_token", "id_token_code"): params = werkzeug.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 @@ -27,10 +31,19 @@ def list_providers(self): 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"], werkzeug.url_encode(params) ) diff --git a/auth_oidc/models/auth_oauth_provider.py b/auth_oidc/models/auth_oauth_provider.py index 43a0aa8781..06f0dae545 100644 --- a/auth_oidc/models/auth_oauth_provider.py +++ b/auth_oidc/models/auth_oauth_provider.py @@ -3,6 +3,7 @@ # License: AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) import logging +import secrets import requests @@ -34,7 +35,10 @@ class AuthOauthProvider(models.Model): "the sub key in the standard." ) client_secret = fields.Char( - help="Required for OpenID Connect authorization code flow." + 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( diff --git a/auth_oidc/models/res_users.py b/auth_oidc/models/res_users.py index 43ab713887..8a0328c26a 100644 --- a/auth_oidc/models/res_users.py +++ b/auth_oidc/models/res_users.py @@ -39,6 +39,7 @@ def auth_oauth(self, provider, params): 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, From 3e26083630f66ca7fb760a33b40be17ede4a9933 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Bidoul?= Date: Sun, 11 Apr 2021 14:44:16 +0200 Subject: [PATCH 17/33] auth_oidc: demo data and basic test --- auth_oidc/demo/local_keycloak.xml | 20 + auth_oidc/tests/__init__.py | 1 + auth_oidc/tests/keycloak/keycloak-config.json | 1997 +++++++++++++++++ auth_oidc/tests/keycloak/keycloak.sh | 10 + auth_oidc/tests/test_auth_oidc_auth_code.py | 52 + 5 files changed, 2080 insertions(+) create mode 100644 auth_oidc/demo/local_keycloak.xml create mode 100644 auth_oidc/tests/__init__.py create mode 100644 auth_oidc/tests/keycloak/keycloak-config.json create mode 100755 auth_oidc/tests/keycloak/keycloak.sh create mode 100644 auth_oidc/tests/test_auth_oidc_auth_code.py diff --git a/auth_oidc/demo/local_keycloak.xml b/auth_oidc/demo/local_keycloak.xml new file mode 100644 index 0000000000..919754db99 --- /dev/null +++ b/auth_oidc/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/auth_oidc/tests/__init__.py b/auth_oidc/tests/__init__.py new file mode 100644 index 0000000000..e603d993b8 --- /dev/null +++ b/auth_oidc/tests/__init__.py @@ -0,0 +1 @@ +from . import test_auth_oidc_auth_code diff --git a/auth_oidc/tests/keycloak/keycloak-config.json b/auth_oidc/tests/keycloak/keycloak-config.json new file mode 100644 index 0000000000..5a0456b55c --- /dev/null +++ b/auth_oidc/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/auth_oidc/tests/keycloak/keycloak.sh b/auth_oidc/tests/keycloak/keycloak.sh new file mode 100755 index 0000000000..7c81d562e7 --- /dev/null +++ b/auth_oidc/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/auth_oidc/tests/test_auth_oidc_auth_code.py b/auth_oidc/tests/test_auth_oidc_auth_code.py new file mode 100644 index 0000000000..623fa4b2cf --- /dev/null +++ b/auth_oidc/tests/test_auth_oidc_auth_code.py @@ -0,0 +1,52 @@ +# Copyright 2021 ACSONE SA/NV +# License: AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) + +import contextlib +from urllib.parse import parse_qs, urlparse + +import odoo +from odoo.tests import common + +from odoo.addons.auth_oidc.controllers.main import OpenIDLogin +from odoo.addons.website.tools import MockRequest as _MockRequest + +BASE_URL = "http://localhost:%s" % 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): + 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"]) From 995d61daafa0d8923e143856b5608bc3cf745809 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Bidoul?= Date: Fri, 25 Jun 2021 16:03:09 +0200 Subject: [PATCH 18/33] auth_oidc: slightly more robust parsing of claim mapping --- auth_oidc/models/auth_oauth_provider.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/auth_oidc/models/auth_oauth_provider.py b/auth_oidc/models/auth_oauth_provider.py index 06f0dae545..6a40e87ed6 100644 --- a/auth_oidc/models/auth_oauth_provider.py +++ b/auth_oidc/models/auth_oauth_provider.py @@ -59,7 +59,7 @@ def _get_key(self, kid): def _map_token_values(self, res): if self.token_map: for pair in self.token_map.split(" "): - from_key, to_key = pair.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 From daf390ff40c2bbb8888e360d500c0e8a5e878479 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Bidoul?= Date: Fri, 25 Jun 2021 16:03:22 +0200 Subject: [PATCH 19/33] auth_oidc: split long method --- auth_oidc/models/res_users.py | 61 +++++++++++++++++++---------------- 1 file changed, 34 insertions(+), 27 deletions(-) diff --git a/auth_oidc/models/res_users.py b/auth_oidc/models/res_users.py index 8a0328c26a..c487504e2a 100644 --- a/auth_oidc/models/res_users.py +++ b/auth_oidc/models/res_users.py @@ -16,39 +16,46 @@ 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, + ) + 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 not in ("id_token", "id_token_code"): - return super(ResUsers, self).auth_oauth(provider, params) - if oauth_provider.flow == "id_token": - # https://openid.net/specs/openid-connect-core-1_0.html#ImplicitAuthResponse - access_token = params.get("access_token") - id_token = params.get("id_token") + access_token, id_token = self._auth_oauth_get_tokens_implicit_flow( + oauth_provider, params + ) elif oauth_provider.flow == "id_token_code": - # 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, + access_token, id_token = self._auth_oauth_get_tokens_auth_code_flow( + oauth_provider, params ) - response.raise_for_status() - response_json = response.json() - # https://openid.net/specs/openid-connect-core-1_0.html#TokenResponse - access_token = response_json.get("access_token") - id_token = response_json.get("id_token") + else: + return super(ResUsers, self).auth_oauth(provider, params) if not access_token: _logger.error("No access_token in response.") raise AccessDenied() From d32afc2c9adf7cc966c02a56bb0410921bac25a9 Mon Sep 17 00:00:00 2001 From: oca-travis Date: Fri, 25 Jun 2021 14:19:32 +0000 Subject: [PATCH 20/33] [UPD] Update auth_oidc.pot --- auth_oidc/README.rst | 143 ++++++- auth_oidc/__manifest__.py | 2 +- auth_oidc/i18n/auth_oidc.pot | 113 +++++ auth_oidc/static/description/index.html | 526 ++++++++++++++++++++++++ 4 files changed, 772 insertions(+), 12 deletions(-) create mode 100644 auth_oidc/i18n/auth_oidc.pot create mode 100644 auth_oidc/static/description/index.html diff --git a/auth_oidc/README.rst b/auth_oidc/README.rst index 2bcb185e11..932ffea7b0 100644 --- a/auth_oidc/README.rst +++ b/auth_oidc/README.rst @@ -1,21 +1,57 @@ -================================= -Authentication via OpenID Connect -================================= +============================= +Authentication OpenID Connect +============================= -This module allows users to login through an OpenID Connect provider. +.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -This includes: +.. |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/13.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-13-0/server-auth-13-0-auth_oidc + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png + :target: https://runbot.odoo-community.org/runbot/251/13.0 + :alt: Try me on Runbot -- Keycloak with ClientID and secret + Implicit Flow -- Microsoft Azure +|badge1| |badge2| |badge3| |badge4| |badge5| +This module allows users to login through an OpenID Connect provider using the +authorization code flow or implicit flow. -Usage -===== +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 implicit flow. +This configuration is not recommended because it exposes the access token +to the client, and in logs. + # configure a new web application in Azure with OpenID and implicit flow (see the `provider documentation `_) @@ -40,8 +76,65 @@ Setup for Microsoft Azure Setup for Keycloak ~~~~~~~~~~~~~~~~~~ -write me... +Example configuration with OpenID Connect authorization code flow. + +In Keycloak: + +# configure a new Client +# make sure Authorization Code Flow is Enabled. +# configure the client Access Type as "confidential" and take note of the client secret in the Credentials tab +# 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 + +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 +========= + +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 smashing it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. Credits ======= @@ -49,9 +142,37 @@ Credits Authors ~~~~~~~ -* ICTSTUDIO, André Schenkels +* ICTSTUDIO +* André Schenkels +* ACSONE SA/NV Contributors ~~~~~~~~~~~~ * Alexandre Fayolle +* Stéphane Bidoul + +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/auth_oidc/__manifest__.py b/auth_oidc/__manifest__.py index a2ff5cae5e..b30efbb0ca 100644 --- a/auth_oidc/__manifest__.py +++ b/auth_oidc/__manifest__.py @@ -4,7 +4,7 @@ { "name": "Authentication OpenID Connect", - "version": "13.0.1.0.0", + "version": "13.0.1.0.1", "license": "AGPL-3", "author": ( "ICTSTUDIO, André Schenkels, " diff --git a/auth_oidc/i18n/auth_oidc.pot b/auth_oidc/i18n/auth_oidc.pot new file mode 100644 index 0000000000..4150f1a674 --- /dev/null +++ b/auth_oidc/i18n/auth_oidc.pot @@ -0,0 +1,113 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * auth_oidc +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 13.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: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 "Users" +msgstr "" + +#. module: auth_oidc +#: model:ir.model.fields,field_description:auth_oidc.field_auth_oauth_provider__validation_endpoint +msgid "Validation 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/auth_oidc/static/description/index.html b/auth_oidc/static/description/index.html new file mode 100644 index 0000000000..a820198809 --- /dev/null +++ b/auth_oidc/static/description/index.html @@ -0,0 +1,526 @@ + + + + + + +Authentication OpenID Connect + + + +
+

Authentication OpenID Connect

+ + +

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

+

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 implicit flow. +This configuration is not recommended because it exposes the access token +to the client, and in logs.

+
+
# configure a new web application in Azure with OpenID and implicit flow (see
+
the provider documentation)
+
# 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
+
# create a new authentication provider in Odoo with the following
+
parameters (see the portal documentation +for more information):
+
+
    +
  • Provider Name: Azure
  • +
  • Auth Flow: OpenID Connect
  • +
  • Client ID: use the value of the OAuth2 autorization endoing (v2) from the Azure Endpoints list
  • +
  • Body: Azure SSO
  • +
  • Authentication URL: use the value of “OAuth2 autorization endpoint (v2)” from the Azure endpoints list
  • +
  • Scope: openid email
  • +
  • Validation URL: use the value of “OAuth2 token endpoint (v2)” from the Azure endpoints list
  • +
  • Allowed: yes
  • +
+
+
+

Setup for Keycloak

+

Example configuration with OpenID Connect authorization code flow.

+

In Keycloak:

+

# configure a new Client +# make sure Authorization Code Flow is Enabled. +# configure the client Access Type as “confidential” and take note of the client secret in the Credentials tab +# configure the redirect url to be “<url of your server>/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
  • +
+
+
+
+

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

+
+

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 smashing 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.

+
+
+
+ + From f707645c07fea1a45e8fa12f4d95f085ec119fda Mon Sep 17 00:00:00 2001 From: Chafique Date: Fri, 10 Dec 2021 16:07:12 +0100 Subject: [PATCH 21/33] [MIG] auth_oidc: Migration to 14.0 --- auth_oidc/README.rst | 15 +++-- auth_oidc/__manifest__.py | 2 +- auth_oidc/i18n/auth_oidc.pot | 20 +++++- auth_oidc/readme/HISTORY.rst | 5 ++ auth_oidc/static/description/index.html | 71 +++++++++++---------- auth_oidc/tests/test_auth_oidc_auth_code.py | 3 +- 6 files changed, 76 insertions(+), 40 deletions(-) diff --git a/auth_oidc/README.rst b/auth_oidc/README.rst index 932ffea7b0..04ab9a1a2e 100644 --- a/auth_oidc/README.rst +++ b/auth_oidc/README.rst @@ -14,13 +14,13 @@ Authentication OpenID Connect :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/13.0/auth_oidc + :target: https://github.com/OCA/server-auth/tree/14.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-13-0/server-auth-13-0-auth_oidc + :target: https://translation.odoo-community.org/projects/server-auth-14-0/server-auth-14-0-auth_oidc :alt: Translate me on Weblate .. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png - :target: https://runbot.odoo-community.org/runbot/251/13.0 + :target: https://runbot.odoo-community.org/runbot/251/14.0 :alt: Try me on Runbot |badge1| |badge2| |badge3| |badge4| |badge5| @@ -116,6 +116,11 @@ Known issues / Roadmap Changelog ========= +14.0.1.0.0 2021-12-10 +~~~~~~~~~~~~~~~~~~~~~ + +* Odoo 14 migration + 13.0.1.0.0 2020-04-10 ~~~~~~~~~~~~~~~~~~~~~ @@ -132,7 +137,7 @@ 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 smashing it by providing a detailed and welcomed -`feedback `_. +`feedback `_. Do not contact contributors directly about support or help with technical issues. @@ -173,6 +178,6 @@ Current `maintainer `__: |maintainer-sbidoul| -This module is part of the `OCA/server-auth `_ project on GitHub. +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/auth_oidc/__manifest__.py b/auth_oidc/__manifest__.py index b30efbb0ca..ebf94f0bbb 100644 --- a/auth_oidc/__manifest__.py +++ b/auth_oidc/__manifest__.py @@ -4,7 +4,7 @@ { "name": "Authentication OpenID Connect", - "version": "13.0.1.0.1", + "version": "14.0.1.0.1", "license": "AGPL-3", "author": ( "ICTSTUDIO, André Schenkels, " diff --git a/auth_oidc/i18n/auth_oidc.pot b/auth_oidc/i18n/auth_oidc.pot index 4150f1a674..a687a98816 100644 --- a/auth_oidc/i18n/auth_oidc.pot +++ b/auth_oidc/i18n/auth_oidc.pot @@ -4,7 +4,7 @@ # msgid "" msgstr "" -"Project-Id-Version: Odoo Server 13.0\n" +"Project-Id-Version: Odoo Server 14.0\n" "Report-Msgid-Bugs-To: \n" "Last-Translator: \n" "Language-Team: \n" @@ -28,11 +28,29 @@ msgstr "" msgid "Code Verifier" msgstr "" +#. module: auth_oidc +#: model:ir.model.fields,field_description:auth_oidc.field_auth_oauth_provider__display_name +#: model:ir.model.fields,field_description:auth_oidc.field_res_users__display_name +msgid "Display Name" +msgstr "" + +#. module: auth_oidc +#: model:ir.model.fields,field_description:auth_oidc.field_auth_oauth_provider__id +#: model:ir.model.fields,field_description:auth_oidc.field_res_users__id +msgid "ID" +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:ir.model.fields,field_description:auth_oidc.field_auth_oauth_provider____last_update +#: model:ir.model.fields,field_description:auth_oidc.field_res_users____last_update +msgid "Last Modified on" +msgstr "" + #. module: auth_oidc #: model:ir.model.fields.selection,name:auth_oidc.selection__auth_oauth_provider__flow__access_token msgid "OAuth2" diff --git a/auth_oidc/readme/HISTORY.rst b/auth_oidc/readme/HISTORY.rst index f831ce0e6d..33b336582e 100644 --- a/auth_oidc/readme/HISTORY.rst +++ b/auth_oidc/readme/HISTORY.rst @@ -1,3 +1,8 @@ +14.0.1.0.0 2021-12-10 +~~~~~~~~~~~~~~~~~~~~~ + +* Odoo 14 migration + 13.0.1.0.0 2020-04-10 ~~~~~~~~~~~~~~~~~~~~~ diff --git a/auth_oidc/static/description/index.html b/auth_oidc/static/description/index.html index a820198809..c744fd4f07 100644 --- a/auth_oidc/static/description/index.html +++ b/auth_oidc/static/description/index.html @@ -367,7 +367,7 @@

Authentication OpenID Connect

!! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! --> -

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

+

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

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 @@ -375,37 +375,38 @@

Authentication OpenID Connect

Table of contents

-

Installation

+

Installation

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

-

Configuration

+

Configuration

-

Setup for Microsoft Azure

+

Setup for Microsoft Azure

Example configuration with OpenID Connect implicit flow. This configuration is not recommended because it exposes the access token to the client, and in logs.

@@ -431,7 +432,7 @@

Setup for Microsoft Azure

-

Setup for Keycloak

+

Setup for Keycloak

Example configuration with OpenID Connect authorization code flow.

In Keycloak:

# configure a new Client @@ -458,43 +459,49 @@

Setup for Keycloak

-

Usage

+

Usage

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

-

Known issues / Roadmap

+

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

+

Changelog

-

13.0.1.0.0 2020-04-10

+

14.0.1.0.0 2021-12-10

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

10.0.1.0.0 2018-10-05

+

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

+

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 smashing it by providing a detailed and welcomed -feedback.

+feedback.

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

-

Credits

+

Credits

-

Authors

+

Authors

  • ICTSTUDIO
  • André Schenkels
  • @@ -502,14 +509,14 @@

    Authors

-

Maintainers

+

Maintainers

This module is maintained by the OCA.

Odoo Community Association

OCA, or the Odoo Community Association, is a nonprofit organization whose @@ -517,7 +524,7 @@

Maintainers

promote its widespread use.

Current maintainer:

sbidoul

-

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

+

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/auth_oidc/tests/test_auth_oidc_auth_code.py b/auth_oidc/tests/test_auth_oidc_auth_code.py index 623fa4b2cf..f608d02dde 100644 --- a/auth_oidc/tests/test_auth_oidc_auth_code.py +++ b/auth_oidc/tests/test_auth_oidc_auth_code.py @@ -7,9 +7,10 @@ import odoo from odoo.tests import common -from odoo.addons.auth_oidc.controllers.main import OpenIDLogin from odoo.addons.website.tools import MockRequest as _MockRequest +from ..controllers.main import OpenIDLogin + BASE_URL = "http://localhost:%s" % odoo.tools.config["http_port"] From 1ac1772859af75b86f940b32bb3ad6eb7392c88d Mon Sep 17 00:00:00 2001 From: Florian Mounier Date: Mon, 14 Mar 2022 10:43:33 +0100 Subject: [PATCH 22/33] [FIX] auth_oidc: Fix werkzeug deprecated warning for url_encode, url decode --- auth_oidc/__manifest__.py | 2 +- auth_oidc/controllers/main.py | 6 +++--- auth_oidc/i18n/auth_oidc.pot | 8 ++++---- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/auth_oidc/__manifest__.py b/auth_oidc/__manifest__.py index ebf94f0bbb..dcb25ec54f 100644 --- a/auth_oidc/__manifest__.py +++ b/auth_oidc/__manifest__.py @@ -4,7 +4,7 @@ { "name": "Authentication OpenID Connect", - "version": "14.0.1.0.1", + "version": "14.0.1.0.2", "license": "AGPL-3", "author": ( "ICTSTUDIO, André Schenkels, " diff --git a/auth_oidc/controllers/main.py b/auth_oidc/controllers/main.py index dbc7dac69c..0c0861e6da 100644 --- a/auth_oidc/controllers/main.py +++ b/auth_oidc/controllers/main.py @@ -7,7 +7,7 @@ import logging import secrets -import werkzeug.utils +from werkzeug.urls import url_decode, url_encode from odoo.addons.auth_oauth.controllers.main import OAuthLogin @@ -20,7 +20,7 @@ def list_providers(self): for provider in providers: flow = provider.get("flow") if flow in ("id_token", "id_token_code"): - params = werkzeug.url_decode(provider["auth_link"].split("?")[-1]) + params = url_decode(provider["auth_link"].split("?")[-1]) # nonce params["nonce"] = secrets.token_urlsafe() # response_type @@ -45,6 +45,6 @@ def list_providers(self): params["scope"] = provider["scope"] # auth link that the user will click provider["auth_link"] = "{}?{}".format( - provider["auth_endpoint"], werkzeug.url_encode(params) + provider["auth_endpoint"], url_encode(params) ) return providers diff --git a/auth_oidc/i18n/auth_oidc.pot b/auth_oidc/i18n/auth_oidc.pot index a687a98816..256f1ca81b 100644 --- a/auth_oidc/i18n/auth_oidc.pot +++ b/auth_oidc/i18n/auth_oidc.pot @@ -111,13 +111,13 @@ msgid "" msgstr "" #. module: auth_oidc -#: model:ir.model,name:auth_oidc.model_res_users -msgid "Users" +#: model:ir.model.fields,field_description:auth_oidc.field_auth_oauth_provider__validation_endpoint +msgid "UserInfo URL" msgstr "" #. module: auth_oidc -#: model:ir.model.fields,field_description:auth_oidc.field_auth_oauth_provider__validation_endpoint -msgid "Validation URL" +#: model:ir.model,name:auth_oidc.model_res_users +msgid "Users" msgstr "" #. module: auth_oidc From 2e9b09f1aa90d0e972e1d77db3df7f731c354869 Mon Sep 17 00:00:00 2001 From: Karl Southern Date: Thu, 7 Jul 2022 12:07:09 +0100 Subject: [PATCH 23/33] [MIG] auth_oidc: Migration to 15.0 --- auth_oidc/README.rst | 10 +++++----- auth_oidc/__manifest__.py | 2 +- auth_oidc/i18n/auth_oidc.pot | 20 +------------------- auth_oidc/static/description/index.html | 6 +++--- 4 files changed, 10 insertions(+), 28 deletions(-) diff --git a/auth_oidc/README.rst b/auth_oidc/README.rst index 04ab9a1a2e..3c6265151c 100644 --- a/auth_oidc/README.rst +++ b/auth_oidc/README.rst @@ -14,13 +14,13 @@ Authentication OpenID Connect :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/14.0/auth_oidc + :target: https://github.com/OCA/server-auth/tree/15.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-14-0/server-auth-14-0-auth_oidc + :target: https://translation.odoo-community.org/projects/server-auth-15-0/server-auth-15-0-auth_oidc :alt: Translate me on Weblate .. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png - :target: https://runbot.odoo-community.org/runbot/251/14.0 + :target: https://runbot.odoo-community.org/runbot/251/15.0 :alt: Try me on Runbot |badge1| |badge2| |badge3| |badge4| |badge5| @@ -137,7 +137,7 @@ 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 smashing it by providing a detailed and welcomed -`feedback `_. +`feedback `_. Do not contact contributors directly about support or help with technical issues. @@ -178,6 +178,6 @@ Current `maintainer `__: |maintainer-sbidoul| -This module is part of the `OCA/server-auth `_ project on GitHub. +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/auth_oidc/__manifest__.py b/auth_oidc/__manifest__.py index dcb25ec54f..cdbf492ec0 100644 --- a/auth_oidc/__manifest__.py +++ b/auth_oidc/__manifest__.py @@ -4,7 +4,7 @@ { "name": "Authentication OpenID Connect", - "version": "14.0.1.0.2", + "version": "15.0.1.0.0", "license": "AGPL-3", "author": ( "ICTSTUDIO, André Schenkels, " diff --git a/auth_oidc/i18n/auth_oidc.pot b/auth_oidc/i18n/auth_oidc.pot index 256f1ca81b..a6146631d4 100644 --- a/auth_oidc/i18n/auth_oidc.pot +++ b/auth_oidc/i18n/auth_oidc.pot @@ -4,7 +4,7 @@ # msgid "" msgstr "" -"Project-Id-Version: Odoo Server 14.0\n" +"Project-Id-Version: Odoo Server 15.0\n" "Report-Msgid-Bugs-To: \n" "Last-Translator: \n" "Language-Team: \n" @@ -28,29 +28,11 @@ msgstr "" msgid "Code Verifier" msgstr "" -#. module: auth_oidc -#: model:ir.model.fields,field_description:auth_oidc.field_auth_oauth_provider__display_name -#: model:ir.model.fields,field_description:auth_oidc.field_res_users__display_name -msgid "Display Name" -msgstr "" - -#. module: auth_oidc -#: model:ir.model.fields,field_description:auth_oidc.field_auth_oauth_provider__id -#: model:ir.model.fields,field_description:auth_oidc.field_res_users__id -msgid "ID" -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:ir.model.fields,field_description:auth_oidc.field_auth_oauth_provider____last_update -#: model:ir.model.fields,field_description:auth_oidc.field_res_users____last_update -msgid "Last Modified on" -msgstr "" - #. module: auth_oidc #: model:ir.model.fields.selection,name:auth_oidc.selection__auth_oauth_provider__flow__access_token msgid "OAuth2" diff --git a/auth_oidc/static/description/index.html b/auth_oidc/static/description/index.html index c744fd4f07..737081829c 100644 --- a/auth_oidc/static/description/index.html +++ b/auth_oidc/static/description/index.html @@ -367,7 +367,7 @@

Authentication OpenID Connect

!! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! --> -

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

+

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

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 @@ -495,7 +495,7 @@

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 smashing it by providing a detailed and welcomed -feedback.

+feedback.

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

@@ -524,7 +524,7 @@

Maintainers

promote its widespread use.

Current maintainer:

sbidoul

-

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

+

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.

From 2b696a81f6fd16a572d25a923c9602e4572ef818 Mon Sep 17 00:00:00 2001 From: djaen Date: Fri, 27 Jan 2023 17:54:08 +0100 Subject: [PATCH 24/33] [16.0][MIG] auth_oidc: Migration to 16.0 --- auth_oidc/README.rst | 24 ++++--- auth_oidc/__manifest__.py | 2 +- auth_oidc/i18n/auth_oidc.pot | 10 +-- auth_oidc/models/auth_oauth_provider.py | 2 +- auth_oidc/models/res_users.py | 1 + auth_oidc/readme/CONTRIBUTORS.rst | 1 + auth_oidc/static/description/index.html | 87 +++++++++++++------------ 7 files changed, 68 insertions(+), 59 deletions(-) diff --git a/auth_oidc/README.rst b/auth_oidc/README.rst index 3c6265151c..6cafbe6c07 100644 --- a/auth_oidc/README.rst +++ b/auth_oidc/README.rst @@ -2,10 +2,13 @@ Authentication OpenID Connect ============================= -.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:0e77943e35a7d7c6fb3b6f9e5753d5870e6023f5614e17f7bc0c32522086c49a + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! .. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png :target: https://odoo-community.org/page/development-status @@ -14,16 +17,16 @@ Authentication OpenID Connect :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/15.0/auth_oidc + :target: https://github.com/OCA/server-auth/tree/16.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-15-0/server-auth-15-0-auth_oidc + :target: https://translation.odoo-community.org/projects/server-auth-16-0/server-auth-16-0-auth_oidc :alt: Translate me on Weblate -.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png - :target: https://runbot.odoo-community.org/runbot/251/15.0 - :alt: Try me on Runbot +.. |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=16.0 + :alt: Try me on Runboat -|badge1| |badge2| |badge3| |badge4| |badge5| +|badge1| |badge2| |badge3| |badge4| |badge5| This module allows users to login through an OpenID Connect provider using the authorization code flow or implicit flow. @@ -136,8 +139,8 @@ 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 smashing it by providing a detailed and welcomed -`feedback `_. +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. @@ -156,6 +159,7 @@ Contributors * Alexandre Fayolle * Stéphane Bidoul +* David Jaen Maintainers ~~~~~~~~~~~ @@ -178,6 +182,6 @@ Current `maintainer `__: |maintainer-sbidoul| -This module is part of the `OCA/server-auth `_ project on GitHub. +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/auth_oidc/__manifest__.py b/auth_oidc/__manifest__.py index cdbf492ec0..845d7a27db 100644 --- a/auth_oidc/__manifest__.py +++ b/auth_oidc/__manifest__.py @@ -4,7 +4,7 @@ { "name": "Authentication OpenID Connect", - "version": "15.0.1.0.0", + "version": "16.0.1.0.0", "license": "AGPL-3", "author": ( "ICTSTUDIO, André Schenkels, " diff --git a/auth_oidc/i18n/auth_oidc.pot b/auth_oidc/i18n/auth_oidc.pot index a6146631d4..0dc7d5e576 100644 --- a/auth_oidc/i18n/auth_oidc.pot +++ b/auth_oidc/i18n/auth_oidc.pot @@ -4,7 +4,7 @@ # msgid "" msgstr "" -"Project-Id-Version: Odoo Server 15.0\n" +"Project-Id-Version: Odoo Server 16.0\n" "Report-Msgid-Bugs-To: \n" "Last-Translator: \n" "Language-Team: \n" @@ -93,13 +93,13 @@ msgid "" msgstr "" #. module: auth_oidc -#: model:ir.model.fields,field_description:auth_oidc.field_auth_oauth_provider__validation_endpoint -msgid "UserInfo URL" +#: model:ir.model,name:auth_oidc.model_res_users +msgid "User" msgstr "" #. module: auth_oidc -#: model:ir.model,name:auth_oidc.model_res_users -msgid "Users" +#: model:ir.model.fields,field_description:auth_oidc.field_auth_oauth_provider__validation_endpoint +msgid "UserInfo URL" msgstr "" #. module: auth_oidc diff --git a/auth_oidc/models/auth_oauth_provider.py b/auth_oidc/models/auth_oauth_provider.py index 6a40e87ed6..b969fa3e8d 100644 --- a/auth_oidc/models/auth_oauth_provider.py +++ b/auth_oidc/models/auth_oauth_provider.py @@ -48,7 +48,7 @@ class AuthOauthProvider(models.Model): @tools.ormcache("self.jwks_uri", "kid") def _get_key(self, kid): - r = requests.get(self.jwks_uri) + r = requests.get(self.jwks_uri, timeout=10) r.raise_for_status() response = r.json() for key in response["keys"]: diff --git a/auth_oidc/models/res_users.py b/auth_oidc/models/res_users.py index c487504e2a..a1be73ec88 100644 --- a/auth_oidc/models/res_users.py +++ b/auth_oidc/models/res_users.py @@ -37,6 +37,7 @@ def _auth_oauth_get_tokens_auth_code_flow(self, oauth_provider, params): redirect_uri=request.httprequest.url_root + "auth_oauth/signin", ), auth=auth, + timeout=10, ) response.raise_for_status() response_json = response.json() diff --git a/auth_oidc/readme/CONTRIBUTORS.rst b/auth_oidc/readme/CONTRIBUTORS.rst index d721552d86..303011adb2 100644 --- a/auth_oidc/readme/CONTRIBUTORS.rst +++ b/auth_oidc/readme/CONTRIBUTORS.rst @@ -1,2 +1,3 @@ * Alexandre Fayolle * Stéphane Bidoul +* David Jaen diff --git a/auth_oidc/static/description/index.html b/auth_oidc/static/description/index.html index 737081829c..384f6ddcff 100644 --- a/auth_oidc/static/description/index.html +++ b/auth_oidc/static/description/index.html @@ -1,20 +1,20 @@ - + - + Authentication OpenID Connect