diff --git a/addons/auth_oauth/controllers/main.py b/addons/auth_oauth/controllers/main.py index 4a378b0174b..799618d34ef 100644 --- a/addons/auth_oauth/controllers/main.py +++ b/addons/auth_oauth/controllers/main.py @@ -1,10 +1,10 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. - +import base64 import functools -import logging - import json +import logging +import os import werkzeug.urls import werkzeug.utils @@ -65,6 +65,7 @@ class OAuthLogin(Home): redirect_uri=return_url, scope=provider['scope'], state=json.dumps(state), + nonce=base64.urlsafe_b64encode(os.urandom(16)), ) provider['auth_link'] = "%s?%s" % (provider['auth_endpoint'], werkzeug.urls.url_encode(params)) return providers diff --git a/addons/auth_oauth/data/auth_oauth_data.xml b/addons/auth_oauth/data/auth_oauth_data.xml index b6579cd82ae..5eab16dc318 100644 --- a/addons/auth_oauth/data/auth_oauth_data.xml +++ b/addons/auth_oauth/data/auth_oauth_data.xml @@ -6,7 +6,6 @@ https://accounts.odoo.com/oauth2/auth userinfo https://accounts.odoo.com/oauth2/tokeninfo - fa fa-fw o_custom_icon Log in with Odoo.com @@ -23,9 +22,8 @@ Google OAuth2 https://accounts.google.com/o/oauth2/auth - https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile - https://www.googleapis.com/oauth2/v1/tokeninfo - https://www.googleapis.com/oauth2/v1/userinfo + openid profile email + https://www.googleapis.com/oauth2/v3/userinfo fa fa-fw fa-google Log in with Google diff --git a/addons/auth_oauth/i18n/auth_oauth.pot b/addons/auth_oauth/i18n/auth_oauth.pot index f3d45984724..b8bc9db53aa 100644 --- a/addons/auth_oauth/i18n/auth_oauth.pot +++ b/addons/auth_oauth/i18n/auth_oauth.pot @@ -48,12 +48,7 @@ msgstr "" #. module: auth_oauth #: model:ir.model.fields,field_description:auth_oauth.field_auth_oauth_provider__auth_endpoint -msgid "Authentication URL" -msgstr "" - -#. module: auth_oauth -#: model:ir.model.fields,field_description:auth_oauth.field_auth_oauth_provider__body -msgid "Body" +msgid "Authorization URL" msgstr "" #. module: auth_oauth @@ -89,7 +84,7 @@ msgstr "" #. module: auth_oauth #: model:ir.model.fields,field_description:auth_oauth.field_auth_oauth_provider__data_endpoint -msgid "Data URL" +msgid "Data Endpoint" msgstr "" #. module: auth_oauth @@ -127,11 +122,6 @@ msgstr "" msgid "Last Updated on" msgstr "" -#. module: auth_oauth -#: model:ir.model.fields,help:auth_oauth.field_auth_oauth_provider__body -msgid "Link text in Login Dialog" -msgstr "" - #. module: auth_oauth #: model:auth.oauth.provider,body:auth_oauth.provider_facebook msgid "Log in with Facebook" @@ -147,6 +137,11 @@ msgstr "" msgid "Log in with Odoo.com" msgstr "" +#. module: auth_oauth +#: model:ir.model.fields,field_description:auth_oauth.field_auth_oauth_provider__body +msgid "Login button label" +msgstr "" + #. module: auth_oauth #: model:ir.model.fields,field_description:auth_oauth.field_res_users__oauth_access_token msgid "OAuth Access Token" @@ -225,13 +220,13 @@ msgid "System Parameter" msgstr "" #. module: auth_oauth -#: model:ir.model,name:auth_oauth.model_res_users -msgid "Users" +#: model:ir.model.fields,field_description:auth_oauth.field_auth_oauth_provider__validation_endpoint +msgid "UserInfo URL" msgstr "" #. module: auth_oauth -#: model:ir.model.fields,field_description:auth_oauth.field_auth_oauth_provider__validation_endpoint -msgid "Validation URL" +#: model:ir.model,name:auth_oauth.model_res_users +msgid "Users" msgstr "" #. module: auth_oauth diff --git a/addons/auth_oauth/models/auth_oauth.py b/addons/auth_oauth/models/auth_oauth.py index cbee5ced816..6efb044b7ed 100644 --- a/addons/auth_oauth/models/auth_oauth.py +++ b/addons/auth_oauth/models/auth_oauth.py @@ -12,13 +12,13 @@ class AuthOAuthProvider(models.Model): name = fields.Char(string='Provider name', required=True) # Name of the OAuth2 entity, Google, etc client_id = fields.Char(string='Client ID') # Our identifier - auth_endpoint = fields.Char(string='Authentication URL', required=True) # OAuth provider URL to authenticate users - scope = fields.Char() # OAUth user data desired to access - validation_endpoint = fields.Char(string='Validation URL', required=True) # OAuth provider URL to validate tokens - data_endpoint = fields.Char(string='Data URL') + auth_endpoint = fields.Char(string='Authorization URL', required=True) # OAuth provider URL to authenticate users + scope = fields.Char(default='openid profile email') # OAUth user data desired to access + validation_endpoint = fields.Char(string='UserInfo URL', required=True) # OAuth provider URL to get user information + data_endpoint = fields.Char() enabled = fields.Boolean(string='Allowed') css_class = fields.Char(string='CSS class', default='fa fa-fw fa-sign-in text-primary') - body = fields.Char(required=True, help='Link text in Login Dialog', translate=True) + body = fields.Char(required=True, string="Login button label", help='Link text in Login Dialog', translate=True) sequence = fields.Integer(default=10) def _neutralize(self): diff --git a/addons/auth_oauth/models/res_users.py b/addons/auth_oauth/models/res_users.py index 56f78b8f535..ecaa68d0530 100644 --- a/addons/auth_oauth/models/res_users.py +++ b/addons/auth_oauth/models/res_users.py @@ -4,6 +4,7 @@ import json import requests +import werkzeug.http from odoo import api, fields, models from odoo.exceptions import AccessDenied, UserError @@ -23,9 +24,21 @@ class ResUsers(models.Model): ('uniq_users_oauth_provider_oauth_uid', 'unique(oauth_provider_id, oauth_uid)', 'OAuth UID must be unique per provider'), ] - @api.model def _auth_oauth_rpc(self, endpoint, access_token): - return requests.get(endpoint, params={'access_token': access_token}).json() + if self.env['ir.config_parameter'].sudo().get_param('auth_oauth.authorization_header'): + response = requests.get(endpoint, headers={'Authorization': 'Bearer %s' % access_token}, timeout=10) + else: + response = requests.get(endpoint, params={'access_token': access_token}, timeout=10) + + if response.ok: # nb: could be a successful failure + return response.json() + + auth_challenge = werkzeug.http.parse_www_authenticate_header( + response.headers.get('WWW-Authenticate')) + if auth_challenge.type == 'bearer' and 'error' in auth_challenge: + return dict(auth_challenge) + + return {'error': 'invalid_request'} @api.model def _auth_oauth_validate(self, provider, access_token): @@ -37,11 +50,26 @@ class ResUsers(models.Model): if oauth_provider.data_endpoint: data = self._auth_oauth_rpc(oauth_provider.data_endpoint, access_token) validation.update(data) + # unify subject key under standard (sub), pop all possible and get most sensible + subject = next(filter(None, [ + validation.pop(key, None) + for key in [ + 'sub', # standard + 'id', # google v1 userinfo, facebook opengraph + 'user_id', # google tokeninfo, odoo (tokeninfo) + ] + ]), None) + if not subject: + raise AccessDenied('Missing subject identity') + # also set on user_id for BC reasons, remove in master when the entire + # thing gets reworked + validation['sub'] = validation['user_id'] = subject + return validation @api.model def _generate_signup_values(self, provider, validation, params): - oauth_uid = validation['user_id'] + oauth_uid = validation['sub'] email = validation.get('email', 'provider_%s_user_%s' % (provider, oauth_uid)) name = validation.get('name', email) return { @@ -65,7 +93,7 @@ class ResUsers(models.Model): This method can be overridden to add alternative signin methods. """ - oauth_uid = validation['user_id'] + oauth_uid = validation['sub'] try: oauth_user = self.search([("oauth_uid", "=", oauth_uid), ('oauth_provider_id', '=', provider)]) if not oauth_user: @@ -94,13 +122,6 @@ class ResUsers(models.Model): # continue with the process access_token = params.get('access_token') validation = self._auth_oauth_validate(provider, access_token) - # required check - if not validation.get('user_id'): - # Workaround: facebook does not send 'user_id' in Open Graph Api - if validation.get('id'): - validation['user_id'] = validation['id'] - else: - raise AccessDenied() # retrieve and sign in user login = self._auth_oauth_signin(provider, validation, params)