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 ##############################################################################
26 from openerp import SUPERUSER_ID
29 import werkzeug.exceptions
31 from openerp.modules.registry import RegistryManager
32 from openerp.addons.web.controllers.main import login_and_redirect, set_cookie_and_redirect
34 import openerp.addons.web.common.http as openerpweb
36 import web.common.http as openerpweb # noqa
38 from openid import oidutil
39 from openid.store import filestore
40 from openid.consumer import consumer
41 from openid.cryptutil import randomString
42 from openid.extensions import ax, sreg
46 _logger = logging.getLogger(__name__)
47 oidutil.log = _logger.debug
49 _storedir = os.path.join(tempfile.gettempdir(), 'openerp-auth_openid-store')
51 class GoogleAppsAwareConsumer(consumer.GenericConsumer):
52 def complete(self, message, endpoint, return_to):
53 if message.getOpenIDNamespace() == consumer.OPENID2_NS:
54 server_url = message.getArg(consumer.OPENID2_NS, 'op_endpoint', '')
55 if server_url.startswith('https://www.google.com/a/'):
56 assoc_handle = message.getArg(consumer.OPENID_NS, 'assoc_handle')
57 assoc = self.store.getAssociation(server_url, assoc_handle)
60 for attr in ['claimed_id', 'identity']:
61 value = message.getArg(consumer.OPENID2_NS, attr, '')
62 value = 'https://www.google.com/accounts/o8/user-xrds?uri=%s' % urllib.quote_plus(value)
63 message.setArg(consumer.OPENID2_NS, attr, value)
65 # now, resign the message
66 message.delArg(consumer.OPENID2_NS, 'sig')
67 message.delArg(consumer.OPENID2_NS, 'signed')
68 message = assoc.signMessage(message)
70 return super(GoogleAppsAwareConsumer, self).complete(message, endpoint, return_to)
73 class OpenIDController(openerpweb.Controller):
74 _cp_path = '/auth_openid/login'
76 _store = filestore.FileOpenIDStore(_storedir)
78 _REQUIRED_ATTRIBUTES = ['email']
79 _OPTIONAL_ATTRIBUTES = 'nickname fullname postcode country language timezone'.split()
81 def _add_extensions(self, request):
82 """Add extensions to the request"""
84 sreg_request = sreg.SRegRequest(required=self._REQUIRED_ATTRIBUTES,
85 optional=self._OPTIONAL_ATTRIBUTES)
86 request.addExtension(sreg_request)
88 ax_request = ax.FetchRequest()
89 for alias in self._REQUIRED_ATTRIBUTES:
90 uri = utils.SREG2AX[alias]
91 ax_request.add(ax.AttrInfo(uri, required=True, alias=alias))
92 for alias in self._OPTIONAL_ATTRIBUTES:
93 uri = utils.SREG2AX[alias]
94 ax_request.add(ax.AttrInfo(uri, required=False, alias=alias))
96 request.addExtension(ax_request)
98 def _get_attributes_from_success_response(self, success_response):
101 all_attrs = self._REQUIRED_ATTRIBUTES + self._OPTIONAL_ATTRIBUTES
103 sreg_resp = sreg.SRegResponse.fromSuccessResponse(success_response)
105 for attr in all_attrs:
106 value = sreg_resp.get(attr)
107 if value is not None:
110 ax_resp = ax.FetchResponse.fromSuccessResponse(success_response)
112 for attr in all_attrs:
113 value = ax_resp.getSingle(utils.SREG2AX[attr])
114 if value is not None:
118 def _get_realm(self, req):
119 return req.httprequest.host_url
121 @openerpweb.httprequest
122 def verify_direct(self, req, db, url):
123 result = self._verify(req, db, url)
124 if 'error' in result:
125 return werkzeug.exceptions.BadRequest(result['error'])
126 if result['action'] == 'redirect':
127 return werkzeug.utils.redirect(result['value'])
128 return result['value']
130 @openerpweb.jsonrequest
131 def verify(self, req, db, url):
132 return self._verify(req, db, url)
134 def _verify(self, req, db, url):
135 redirect_to = werkzeug.urls.Href(req.httprequest.host_url + 'auth_openid/login/process')(session_id=req.session_id)
136 realm = self._get_realm(req)
138 session = dict(dbname=db, openid_url=url) # TODO add origin page ?
139 oidconsumer = consumer.Consumer(session, self._store)
142 request = oidconsumer.begin(url)
143 except consumer.DiscoveryFailure, exc:
144 fetch_error_string = 'Error in discovery: %s' % (str(exc[0]),)
145 return {'error': fetch_error_string, 'title': 'OpenID Error'}
148 return {'error': 'No OpenID services found', 'title': 'OpenID Error'}
150 req.session.openid_session = session
151 self._add_extensions(request)
153 if request.shouldSendRedirect():
154 redirect_url = request.redirectURL(realm, redirect_to)
155 return {'action': 'redirect', 'value': redirect_url, 'session_id': req.session_id}
157 form_html = request.htmlMarkup(realm, redirect_to)
158 return {'action': 'post', 'value': form_html, 'session_id': req.session_id}
160 @openerpweb.httprequest
161 def process(self, req, **kw):
162 session = getattr(req.session, 'openid_session', None)
164 return set_cookie_and_redirect(req, '/')
166 oidconsumer = consumer.Consumer(session, self._store, consumer_class=GoogleAppsAwareConsumer)
168 query = req.httprequest.args
169 info = oidconsumer.complete(query, req.httprequest.base_url)
170 display_identifier = info.getDisplayIdentifier()
172 session['status'] = info.status
174 if info.status == consumer.SUCCESS:
175 dbname = session['dbname']
176 registry = RegistryManager.get(dbname)
177 with registry.cursor() as cr:
178 Modules = registry.get('ir.module.module')
180 installed = Modules.search_count(cr, SUPERUSER_ID, ['&', ('name', '=', 'auth_openid'), ('state', '=', 'installed')]) == 1
183 Users = registry.get('res.users')
185 #openid_url = info.endpoint.canonicalID or display_identifier
186 openid_url = session['openid_url']
188 attrs = self._get_attributes_from_success_response(info)
189 attrs['openid_url'] = openid_url
190 session['attributes'] = attrs
191 openid_email = attrs.get('email', False)
195 domain += ['|', ('openid_email', '=', False)]
196 domain += [('openid_email', '=', openid_email)]
198 domain += [('openid_url', '=', openid_url), ('active', '=', True)]
200 ids = Users.search(cr, SUPERUSER_ID, domain)
204 login = Users.browse(cr, SUPERUSER_ID, user_id).login
205 key = randomString(utils.KEY_LENGTH, '0123456789abcdef')
206 Users.write(cr, SUPERUSER_ID, [user_id], {'openid_key': key})
207 # TODO fill empty fields with the ones from sreg/ax
210 return login_and_redirect(req, dbname, login, key)
212 session['message'] = 'This OpenID identifier is not associated to any active users'
214 elif info.status == consumer.SETUP_NEEDED:
215 session['message'] = info.setup_url
216 elif info.status == consumer.FAILURE and display_identifier:
217 fmt = "Verification of %s failed: %s"
218 session['message'] = fmt % (display_identifier, info.message)
220 # Either we don't understand the code or there is no
221 # openid_url included with the error. Give a generic
222 # failure message. The library should supply debug
223 # information in a log.
224 session['message'] = 'Verification failed.'
226 return set_cookie_and_redirect(req, '/#action=login&loginerror=1')
228 @openerpweb.jsonrequest
229 def status(self, req):
230 session = getattr(req.session, 'openid_session', {})
231 return {'status': session.get('status'), 'message': session.get('message')}
234 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: