1 # -*- coding: utf-8 -*-
2 ##############################################################################
4 # OpenERP, Open Source Management Solution
5 # Copyright (C) 2010-2012 OpenERP s.a. (<http://openerp.com>).
7 # This program is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU Affero General Public License as
9 # published by the Free Software Foundation, either version 3 of the
10 # License, or (at your option) any later version.
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU Affero General Public License for more details.
17 # You should have received a copy of the GNU Affero General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
20 ##############################################################################
29 import werkzeug.exceptions
31 from openid import oidutil
32 from openid.store import filestore
33 from openid.consumer import consumer
34 from openid.cryptutil import randomString
35 from openid.extensions import ax, sreg
38 from openerp import SUPERUSER_ID
39 from openerp.modules.registry import RegistryManager
40 from openerp.addons.web.controllers.main import login_and_redirect, set_cookie_and_redirect
44 _logger = logging.getLogger(__name__)
45 oidutil.log = _logger.debug
47 def get_system_user():
48 """Return system user info string, such as USERNAME-EUID"""
49 info = getpass.getuser()
50 euid = getattr(os, 'geteuid', None) # Non available on some platforms
52 info = '%s-%d' % (info, euid())
55 _storedir = os.path.join(tempfile.gettempdir(),
56 'openerp-auth_openid-%s-store' % get_system_user())
58 class GoogleAppsAwareConsumer(consumer.GenericConsumer):
59 def complete(self, message, endpoint, return_to):
60 if message.getOpenIDNamespace() == consumer.OPENID2_NS:
61 server_url = message.getArg(consumer.OPENID2_NS, 'op_endpoint', '')
62 if server_url.startswith('https://www.google.com/a/'):
63 assoc_handle = message.getArg(consumer.OPENID_NS, 'assoc_handle')
64 assoc = self.store.getAssociation(server_url, assoc_handle)
67 for attr in ['claimed_id', 'identity']:
68 value = message.getArg(consumer.OPENID2_NS, attr, '')
69 value = 'https://www.google.com/accounts/o8/user-xrds?uri=%s' % urllib.quote_plus(value)
70 message.setArg(consumer.OPENID2_NS, attr, value)
72 # now, resign the message
73 message.delArg(consumer.OPENID2_NS, 'sig')
74 message.delArg(consumer.OPENID2_NS, 'signed')
75 message = assoc.signMessage(message)
77 return super(GoogleAppsAwareConsumer, self).complete(message, endpoint, return_to)
80 class OpenIDController(openerp.addons.web.http.Controller):
81 _cp_path = '/auth_openid/login'
83 _store = filestore.FileOpenIDStore(_storedir)
85 _REQUIRED_ATTRIBUTES = ['email']
86 _OPTIONAL_ATTRIBUTES = 'nickname fullname postcode country language timezone'.split()
88 def _add_extensions(self, request):
89 """Add extensions to the request"""
91 sreg_request = sreg.SRegRequest(required=self._REQUIRED_ATTRIBUTES,
92 optional=self._OPTIONAL_ATTRIBUTES)
93 request.addExtension(sreg_request)
95 ax_request = ax.FetchRequest()
96 for alias in self._REQUIRED_ATTRIBUTES:
97 uri = utils.SREG2AX[alias]
98 ax_request.add(ax.AttrInfo(uri, required=True, alias=alias))
99 for alias in self._OPTIONAL_ATTRIBUTES:
100 uri = utils.SREG2AX[alias]
101 ax_request.add(ax.AttrInfo(uri, required=False, alias=alias))
103 request.addExtension(ax_request)
105 def _get_attributes_from_success_response(self, success_response):
108 all_attrs = self._REQUIRED_ATTRIBUTES + self._OPTIONAL_ATTRIBUTES
110 sreg_resp = sreg.SRegResponse.fromSuccessResponse(success_response)
112 for attr in all_attrs:
113 value = sreg_resp.get(attr)
114 if value is not None:
117 ax_resp = ax.FetchResponse.fromSuccessResponse(success_response)
119 for attr in all_attrs:
120 value = ax_resp.getSingle(utils.SREG2AX[attr])
121 if value is not None:
125 def _get_realm(self, req):
126 return req.httprequest.host_url
128 @openerp.addons.web.http.httprequest
129 def verify_direct(self, req, db, url):
130 result = self._verify(req, db, url)
131 if 'error' in result:
132 return werkzeug.exceptions.BadRequest(result['error'])
133 if result['action'] == 'redirect':
134 return werkzeug.utils.redirect(result['value'])
135 return result['value']
137 @openerp.addons.web.http.jsonrequest
138 def verify(self, req, db, url):
139 return self._verify(req, db, url)
141 def _verify(self, req, db, url):
142 redirect_to = werkzeug.urls.Href(req.httprequest.host_url + 'auth_openid/login/process')(session_id=req.session_id)
143 realm = self._get_realm(req)
145 session = dict(dbname=db, openid_url=url) # TODO add origin page ?
146 oidconsumer = consumer.Consumer(session, self._store)
149 request = oidconsumer.begin(url)
150 except consumer.DiscoveryFailure, exc:
151 fetch_error_string = 'Error in discovery: %s' % (str(exc[0]),)
152 return {'error': fetch_error_string, 'title': 'OpenID Error'}
155 return {'error': 'No OpenID services found', 'title': 'OpenID Error'}
157 req.session.openid_session = session
158 self._add_extensions(request)
160 if request.shouldSendRedirect():
161 redirect_url = request.redirectURL(realm, redirect_to)
162 return {'action': 'redirect', 'value': redirect_url, 'session_id': req.session_id}
164 form_html = request.htmlMarkup(realm, redirect_to)
165 return {'action': 'post', 'value': form_html, 'session_id': req.session_id}
167 @openerp.addons.web.http.httprequest
168 def process(self, req, **kw):
169 session = getattr(req.session, 'openid_session', None)
171 return set_cookie_and_redirect(req, '/')
173 oidconsumer = consumer.Consumer(session, self._store, consumer_class=GoogleAppsAwareConsumer)
175 query = req.httprequest.args
176 info = oidconsumer.complete(query, req.httprequest.base_url)
177 display_identifier = info.getDisplayIdentifier()
179 session['status'] = info.status
181 if info.status == consumer.SUCCESS:
182 dbname = session['dbname']
183 registry = RegistryManager.get(dbname)
184 with registry.cursor() as cr:
185 Modules = registry.get('ir.module.module')
187 installed = Modules.search_count(cr, SUPERUSER_ID, ['&', ('name', '=', 'auth_openid'), ('state', '=', 'installed')]) == 1
190 Users = registry.get('res.users')
192 #openid_url = info.endpoint.canonicalID or display_identifier
193 openid_url = session['openid_url']
195 attrs = self._get_attributes_from_success_response(info)
196 attrs['openid_url'] = openid_url
197 session['attributes'] = attrs
198 openid_email = attrs.get('email', False)
202 domain += ['|', ('openid_email', '=', False)]
203 domain += [('openid_email', '=', openid_email)]
205 domain += [('openid_url', '=', openid_url), ('active', '=', True)]
207 ids = Users.search(cr, SUPERUSER_ID, domain)
211 login = Users.browse(cr, SUPERUSER_ID, user_id).login
212 key = randomString(utils.KEY_LENGTH, '0123456789abcdef')
213 Users.write(cr, SUPERUSER_ID, [user_id], {'openid_key': key})
214 # TODO fill empty fields with the ones from sreg/ax
217 return login_and_redirect(req, dbname, login, key)
219 session['message'] = 'This OpenID identifier is not associated to any active users'
221 elif info.status == consumer.SETUP_NEEDED:
222 session['message'] = info.setup_url
223 elif info.status == consumer.FAILURE and display_identifier:
224 fmt = "Verification of %s failed: %s"
225 session['message'] = fmt % (display_identifier, info.message)
227 # Either we don't understand the code or there is no
228 # openid_url included with the error. Give a generic
229 # failure message. The library should supply debug
230 # information in a log.
231 session['message'] = 'Verification failed.'
233 return set_cookie_and_redirect(req, '/#action=login&loginerror=1')
235 @openerp.addons.web.http.jsonrequest
236 def status(self, req):
237 session = getattr(req.session, 'openid_session', {})
238 return {'status': session.get('status'), 'message': session.get('message')}
241 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: