[MERGE] auth_oauth: Migrate to new API

This commit is contained in:
Yannick Tivisse
2016-08-02 16:03:02 +02:00
19 changed files with 278 additions and 276 deletions
+4 -4
View File
@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import controllers
import auth_oauth
import res_users
import res_config
import ir_configparameter
import models
+8 -12
View File
@@ -1,26 +1,22 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
{
'name': 'OAuth2 Authentication',
'version': '1.0',
'category': 'Extra Tools',
'category': 'Tools',
'description': """
Allow users to login through OAuth2 Provider.
=============================================
""",
'maintainer': 'OpenERP s.a.',
'maintainer': 'Odoo S.A.',
'depends': ['base', 'web', 'base_setup', 'auth_signup'],
'data': [
'res_users.xml',
'auth_oauth_data.xml',
'auth_oauth_data.yml',
'auth_oauth_view.xml',
'data/auth_oauth_data.xml',
'data/auth_oauth_data.yml',
'views/auth_oauth_views.xml',
'views/res_users_views.xml',
'views/res_config_views.xml',
'views/auth_oauth_templates.xml',
'security/ir.model.access.csv',
'res_config.xml',
'views/auth_oauth_login.xml',
],
'installable': True,
'auto_install': False,
}
-25
View File
@@ -1,25 +0,0 @@
from openerp.osv import osv, fields
class auth_oauth_provider(osv.osv):
"""Class defining the configuration values of an OAuth2 provider"""
_name = 'auth.oauth.provider'
_description = 'OAuth2 provider'
_order = 'name'
_columns = {
'name' : fields.char('Provider name', required=True), # Name of the OAuth2 entity, Google, etc
'client_id' : fields.char('Client ID'), # Our identifier
'auth_endpoint' : fields.char('Authentication URL', required=True), # OAuth provider URL to authenticate users
'scope' : fields.char('Scope'), # OAUth user data desired to access
'validation_endpoint' : fields.char('Validation URL', required=True),# OAuth provider URL to validate tokens
'data_endpoint' : fields.char('Data URL'),
'enabled' : fields.boolean('Allowed'),
'css_class' : fields.char('CSS class'),
'body' : fields.char('Body', required=True),
'sequence' : fields.integer(),
}
_defaults = {
'enabled' : False,
'css_class' : "zocial",
}
@@ -1 +1,4 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import main
+23 -22
View File
@@ -1,19 +1,22 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import functools
import logging
import json
import urlparse
import werkzeug.utils
from werkzeug.exceptions import BadRequest
import openerp
from openerp import SUPERUSER_ID
from openerp import http
from openerp.http import request
from openerp.addons.web.controllers.main import db_monodb, ensure_db, set_cookie_and_redirect, login_and_redirect
from openerp.addons.auth_signup.controllers.main import AuthSignupHome as Home
from openerp.modules.registry import RegistryManager
from openerp.tools.translate import _
from odoo import api, http, SUPERUSER_ID, _
from odoo.exceptions import AccessDenied
from odoo.http import request
from odoo import registry as registry_get
from odoo.addons.auth_signup.controllers.main import AuthSignupHome as Home
from odoo.addons.web.controllers.main import db_monodb, ensure_db, set_cookie_and_redirect, login_and_redirect
_logger = logging.getLogger(__name__)
@@ -49,8 +52,7 @@ def fragment_to_query_string(func):
class OAuthLogin(Home):
def list_providers(self):
try:
provider_obj = request.registry.get('auth.oauth.provider')
providers = provider_obj.search_read(request.cr, SUPERUSER_ID, [('enabled', '=', True)])
providers = request.env['auth.oauth.provider'].sudo().search_read([('enabled', '=', True)])
except Exception:
providers = []
for provider in providers:
@@ -63,8 +65,7 @@ class OAuthLogin(Home):
scope=provider['scope'],
state=json.dumps(state),
)
provider['auth_link'] = provider['auth_endpoint'] + '?' + werkzeug.url_encode(params)
provider['auth_link'] = "%s?%s" % (provider['auth_endpoint'], werkzeug.url_encode(params))
return providers
def get_state(self, provider):
@@ -135,11 +136,11 @@ class OAuthController(http.Controller):
dbname = state['d']
provider = state['p']
context = state.get('c', {})
registry = RegistryManager.get(dbname)
registry = registry_get(dbname)
with registry.cursor() as cr:
try:
u = registry.get('res.users')
credentials = u.auth_oauth(cr, SUPERUSER_ID, provider, kw, context=context)
env = api.Environment(cr, SUPERUSER_ID, context)
credentials = env['res.users'].sudo().auth_oauth(provider, kw)
cr.commit()
action = state.get('a')
menu = state.get('m')
@@ -156,7 +157,7 @@ class OAuthController(http.Controller):
# auth_signup is not installed
_logger.error("auth_signup not installed on database %s: oauth sign up cancelled." % (dbname,))
url = "/web/login?oauth_error=1"
except openerp.exceptions.AccessDenied:
except AccessDenied:
# oauth credentials not valid, user could be on a temporary session
_logger.info('OAuth2: access denied, redirect to main page in case a valid session exists, without setting cookies')
url = "/web/login?oauth_error=3"
@@ -179,18 +180,18 @@ class OAuthController(http.Controller):
if not dbname:
return BadRequest()
registry = RegistryManager.get(dbname)
registry = registry_get(dbname)
with registry.cursor() as cr:
IMD = registry['ir.model.data']
try:
model, provider_id = IMD.get_object_reference(cr, SUPERUSER_ID, 'auth_oauth', 'provider_openerp')
env = api.Environment(cr, SUPERUSER_ID, {})
provider = env.ref('auth_oauth.provider_openerp')
except ValueError:
return set_cookie_and_redirect('/web?db=%s' % dbname)
assert model == 'auth.oauth.provider'
assert provider._name == 'auth.oauth.provider'
state = {
'd': dbname,
'p': provider_id,
'p': provider.id,
'c': {'no_user_creation': True},
}
@@ -1,5 +1,5 @@
<?xml version="1.0"?>
<openerp>
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<record id="provider_openerp" model="auth.oauth.provider">
<field name="name">Odoo.com Accounts</field>
@@ -30,4 +30,4 @@
<field name="body">Log in with Google</field>
</record>
</data>
</openerp>
</odoo>
-14
View File
@@ -1,14 +0,0 @@
# -*- coding: utf-8 -*-
from openerp import SUPERUSER_ID
from openerp.osv import osv
class ir_configparameter(osv.Model):
_inherit = 'ir.config_parameter'
def init(self, cr, force=False):
super(ir_configparameter, self).init(cr, force=force)
if force:
IMD = self.pool['ir.model.data']
oauth_oe = IMD.xmlid_to_object(cr, SUPERUSER_ID, 'auth_oauth.provider_openerp')
dbuuid = self.get_param(cr, SUPERUSER_ID, 'database.uuid')
oauth_oe.write({'client_id': dbuuid})
+7
View File
@@ -0,0 +1,7 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import auth_oauth
import ir_config_parameter
import res_config
import res_users
+23
View File
@@ -0,0 +1,23 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import fields, models
class AuthOAuthProvider(models.Model):
"""Class defining the configuration values of an OAuth2 provider"""
_name = 'auth.oauth.provider'
_description = 'OAuth2 provider'
_order = 'name'
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')
enabled = fields.Boolean(string='Allowed')
css_class = fields.Char(string='CSS class', default='zocial')
body = fields.Char(required=True)
sequence = fields.Integer()
@@ -0,0 +1,15 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import models
class IrConfigParameter(models.Model):
_inherit = 'ir.config_parameter'
def init(self, force=False):
super(IrConfigParameter, self).init(force=force)
if force:
oauth_oe = self.env.ref('auth_oauth.provider_openerp')
dbuuid = self.sudo().get_param('database.uuid')
oauth_oe.write({'client_id': dbuuid})
+51
View File
@@ -0,0 +1,51 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import logging
from odoo import api, fields, models
_logger = logging.getLogger(__name__)
class BaseConfigSettings(models.TransientModel):
_inherit = 'base.config.settings'
@api.model
def get_uri(self):
return "%s/auth_oauth/signin" % (self.env['ir.config_parameter'].get_param('web.base.url'))
def _compute_server_uri(self):
uri = self.get_uri()
for setting in self:
setting.server_uri_google = uri
auth_oauth_google_enabled = fields.Boolean(string='Allow users to sign in with Google')
auth_oauth_google_client_id = fields.Char(string='Client ID')
server_uri_google = fields.Char(compute='_compute_server_uri', string='Server uri')
auth_oauth_tutorial_enabled = fields.Boolean(string='Show tutorial')
@api.model
def default_get(self, fields):
settings = super(BaseConfigSettings, self).default_get(fields)
settings.update(self.get_oauth_providers(fields))
return settings
@api.model
def get_oauth_providers(self, fields):
google_provider = self.env.ref('auth_oauth.provider_google')
return {
'auth_oauth_google_enabled': google_provider.enabled,
'auth_oauth_google_client_id': google_provider.client_id,
'server_uri_google': self.get_uri()
}
@api.multi
def set_oauth_providers(self):
self.ensure_one()
google_provider = self.env.ref('auth_oauth.provider_google')
rg = {
'enabled': self.auth_oauth_google_enabled,
'client_id': self.auth_oauth_google_client_id,
}
google_provider.write(rg)
+125
View File
@@ -0,0 +1,125 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import werkzeug.urls
import urlparse
import urllib2
import json
from odoo import api, fields, models
from odoo.exceptions import AccessDenied
from odoo.addons.auth_signup.models.res_users import SignupError
class ResUsers(models.Model):
_inherit = 'res.users'
oauth_provider_id = fields.Many2one('auth.oauth.provider', string='OAuth Provider')
oauth_uid = fields.Char(string='OAuth User ID', help="Oauth Provider user_id", copy=False)
oauth_access_token = fields.Char(string='OAuth Access Token', readonly=True, copy=False)
_sql_constraints = [
('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):
params = werkzeug.url_encode({'access_token': access_token})
if urlparse.urlparse(endpoint)[4]:
url = endpoint + '&' + params
else:
url = endpoint + '?' + params
f = urllib2.urlopen(url)
response = f.read()
return json.loads(response)
@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)
validation = self._auth_oauth_rpc(oauth_provider.validation_endpoint, access_token)
if validation.get("error"):
raise Exception(validation['error'])
if oauth_provider.data_endpoint:
data = self._auth_oauth_rpc(oauth_provider.data_endpoint, access_token)
validation.update(data)
return validation
@api.model
def _generate_signup_values(self, provider, validation, params):
oauth_uid = validation['user_id']
email = validation.get('email', 'provider_%s_user_%s' % (provider, oauth_uid))
name = validation.get('name', email)
return {
'name': name,
'login': email,
'email': email,
'oauth_provider_id': provider,
'oauth_uid': oauth_uid,
'oauth_access_token': params['access_token'],
'active': True,
}
@api.model
def _auth_oauth_signin(self, provider, validation, params):
""" retrieve and sign in the user corresponding to provider and validated access token
:param provider: oauth provider id (int)
:param validation: result of validation of access token (dict)
:param params: oauth parameters (dict)
:return: user login (str)
:raise: AccessDenied if signin failed
This method can be overridden to add alternative signin methods.
"""
oauth_uid = validation['user_id']
try:
oauth_user = self.search([("oauth_uid", "=", oauth_uid), ('oauth_provider_id', '=', provider)])
if not oauth_user:
raise AccessDenied()
assert len(oauth_user) == 1
oauth_user.write({'oauth_access_token': params['access_token']})
return oauth_user.login
except AccessDenied, access_denied_exception:
if self.env.context.get('no_user_creation'):
return None
state = json.loads(params['state'])
token = state.get('t')
values = self._generate_signup_values(provider, validation, params)
try:
_, login, _ = self.signup(values, token)
return login
except SignupError:
raise access_denied_exception
@api.model
def auth_oauth(self, provider, params):
# Advice by Google (to avoid Confused Deputy Problem)
# if validation.audience != OUR_CLIENT_ID:
# abort()
# else:
# 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)
if not login:
raise AccessDenied()
# return user credentials
return (self.env.cr.dbname, login, access_token)
@api.model
def check_credentials(self, password):
try:
return super(ResUsers, self).check_credentials(password)
except AccessDenied:
res = self.sudo().search([('id', '=', self.env.uid), ('oauth_access_token', '=', password)])
if not res:
raise
-46
View File
@@ -1,46 +0,0 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from openerp.osv import osv, fields
import logging
_logger = logging.getLogger(__name__)
class base_config_settings(osv.TransientModel):
_inherit = 'base.config.settings'
def get_uri(self, cr, uid, context=None):
return "%s/auth_oauth/signin" % (self.pool['ir.config_parameter'].get_param(cr, uid, 'web.base.url', context=context))
def _get_server_uri(self, cr, uid, ids, name, arg, context=None):
return dict.fromkeys(ids, self.get_uri(cr, uid, context))
_columns = {
'auth_oauth_google_enabled' : fields.boolean('Allow users to sign in with Google'),
'auth_oauth_google_client_id' : fields.char('Client ID'),
'server_uri_google': fields.function(_get_server_uri, type='char', string='Server uri'),
'auth_oauth_tutorial_enabled': fields.boolean('Show tutorial'),
}
def default_get(self, cr, uid, fields, context=None):
res = super(base_config_settings, self).default_get(cr, uid, fields, context=context)
res.update(self.get_oauth_providers(cr, uid, fields, context=context))
return res
def get_oauth_providers(self, cr, uid, fields, context=None):
google_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'auth_oauth', 'provider_google')[1]
rg = self.pool.get('auth.oauth.provider').read(cr, uid, [google_id], ['enabled','client_id'], context=context)
return {
'auth_oauth_google_enabled': rg[0]['enabled'],
'auth_oauth_google_client_id': rg[0]['client_id'],
'server_uri_google': self.get_uri(cr, uid, context)
}
def set_oauth_providers(self, cr, uid, ids, context=None):
google_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'auth_oauth', 'provider_google')[1]
config = self.browse(cr, uid, ids[0], context=context)
rg = {
'enabled':config.auth_oauth_google_enabled,
'client_id':config.auth_oauth_google_client_id,
}
self.pool.get('auth.oauth.provider').write(cr, uid, [google_id], rg)
-125
View File
@@ -1,125 +0,0 @@
import logging
import werkzeug.urls
import urlparse
import urllib2
import json
import openerp
from openerp.addons.auth_signup.models.res_partner import SignupError
from openerp.osv import osv, fields
from openerp import SUPERUSER_ID
_logger = logging.getLogger(__name__)
class res_users(osv.Model):
_inherit = 'res.users'
_columns = {
'oauth_provider_id': fields.many2one('auth.oauth.provider', 'OAuth Provider'),
'oauth_uid': fields.char('OAuth User ID', help="Oauth Provider user_id", copy=False),
'oauth_access_token': fields.char('OAuth Access Token', readonly=True, copy=False),
}
_sql_constraints = [
('uniq_users_oauth_provider_oauth_uid', 'unique(oauth_provider_id, oauth_uid)', 'OAuth UID must be unique per provider'),
]
def _auth_oauth_rpc(self, cr, uid, endpoint, access_token, context=None):
params = werkzeug.url_encode({'access_token': access_token})
if urlparse.urlparse(endpoint)[4]:
url = endpoint + '&' + params
else:
url = endpoint + '?' + params
f = urllib2.urlopen(url)
response = f.read()
return json.loads(response)
def _auth_oauth_validate(self, cr, uid, provider, access_token, context=None):
""" return the validation data corresponding to the access token """
p = self.pool.get('auth.oauth.provider').browse(cr, uid, provider, context=context)
validation = self._auth_oauth_rpc(cr, uid, p.validation_endpoint, access_token, context=context)
if validation.get("error"):
raise Exception(validation['error'])
if p.data_endpoint:
data = self._auth_oauth_rpc(cr, uid, p.data_endpoint, access_token, context=context)
validation.update(data)
return validation
def _generate_signup_values(self, cr, uid, provider, validation, params, context=None):
oauth_uid = validation['user_id']
email = validation.get('email', 'provider_%s_user_%s' % (provider, oauth_uid))
name = validation.get('name', email)
return {
'name': name,
'login': email,
'email': email,
'oauth_provider_id': provider,
'oauth_uid': oauth_uid,
'oauth_access_token': params['access_token'],
'active': True,
}
def _auth_oauth_signin(self, cr, uid, provider, validation, params, context=None):
""" retrieve and sign in the user corresponding to provider and validated access token
:param provider: oauth provider id (int)
:param validation: result of validation of access token (dict)
:param params: oauth parameters (dict)
:return: user login (str)
:raise: openerp.exceptions.AccessDenied if signin failed
This method can be overridden to add alternative signin methods.
"""
try:
oauth_uid = validation['user_id']
user_ids = self.search(cr, uid, [("oauth_uid", "=", oauth_uid), ('oauth_provider_id', '=', provider)])
if not user_ids:
raise openerp.exceptions.AccessDenied()
assert len(user_ids) == 1
user = self.browse(cr, uid, user_ids[0], context=context)
user.write({'oauth_access_token': params['access_token']})
return user.login
except openerp.exceptions.AccessDenied, access_denied_exception:
if context and context.get('no_user_creation'):
return None
state = json.loads(params['state'])
token = state.get('t')
values = self._generate_signup_values(cr, uid, provider, validation, params, context=context)
try:
_, login, _ = self.signup(cr, uid, values, token, context=context)
return login
except SignupError:
raise access_denied_exception
def auth_oauth(self, cr, uid, provider, params, context=None):
# Advice by Google (to avoid Confused Deputy Problem)
# if validation.audience != OUR_CLIENT_ID:
# abort()
# else:
# continue with the process
access_token = params.get('access_token')
validation = self._auth_oauth_validate(cr, uid, provider, access_token, context=context)
# 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 openerp.exceptions.AccessDenied()
# retrieve and sign in user
login = self._auth_oauth_signin(cr, uid, provider, validation, params, context=context)
if not login:
raise openerp.exceptions.AccessDenied()
# return user credentials
return (cr.dbname, login, access_token)
def check_credentials(self, cr, uid, password):
try:
return super(res_users, self).check_credentials(cr, uid, password)
except openerp.exceptions.AccessDenied:
res = self.search(cr, SUPERUSER_ID, [('id', '=', uid), ('oauth_access_token', '=', password)])
if not res:
raise
#
@@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<odoo>
<template id="auth_oauth.providers" name="OAuth Providers">
<div t-foreach="providers" t-as="p">
<a t-att-href="p['auth_link']" class="btn btn-link">
@@ -33,5 +32,4 @@
</div>
</xpath>
</template>
</data>
</openerp>
</odoo>
@@ -1,7 +1,6 @@
<?xml version="1.0"?>
<openerp>
<data>
<record model="ir.ui.view" id="view_oauth_provider_form">
<odoo>
<record id="view_oauth_provider_form" model="ir.ui.view">
<field name="name">auth.oauth.provider.form</field>
<field name="model">auth.oauth.provider</field>
<field name="arch" type="xml">
@@ -23,8 +22,8 @@
</form>
</field>
</record>
<record model="ir.ui.view" id="view_oauth_provider_list">
<field name="name">auth.oauth.provider.list</field>
<record id="view_oauth_provider_tree" model="ir.ui.view">
<field name="name">auth.oauth.provider.tree</field>
<field name="model">auth.oauth.provider</field>
<field name="arch" type="xml">
<tree string="arch">
@@ -34,7 +33,7 @@
</tree>
</field>
</record>
<record model="ir.actions.act_window" id="action_oauth_provider">
<record id="action_oauth_provider" model="ir.actions.act_window">
<field name="name">Providers</field>
<field name="res_model">auth.oauth.provider</field>
<field name="view_type">form</field>
@@ -43,5 +42,4 @@
<menuitem id="menu_oauth_providers" name="OAuth Providers"
parent="base.menu_users" sequence="30"
action="action_oauth_provider" groups="base.group_no_one"/>
</data>
</openerp>
</odoo>
@@ -1,8 +1,7 @@
<?xml version="1.0"?>
<openerp>
<data>
<record model="ir.ui.view" id="view_general_configuration">
<field name="name">base.config.settings.oauth</field>
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="view_general_configuration" model="ir.ui.view">
<field name="name">base.config.settings.form.inherit</field>
<field name="model">base.config.settings</field>
<field name="inherit_id" ref="base_setup.view_general_configuration"/>
<field name="arch" type="xml">
@@ -107,5 +106,4 @@
</xpath>
</field>
</record>
</data>
</openerp>
</odoo>
@@ -1,8 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<odoo>
<record id="view_users_form" model="ir.ui.view">
<field name="name">res.users.form</field>
<field name="name">res.users.form.inherit</field>
<field name="model">res.users</field>
<field name="type">form</field>
<field name="inherit_id" ref="base.view_users_form"/>
@@ -18,6 +17,4 @@
</xpath>
</field>
</record>
</data>
</openerp>
</odoo>