Launchpad automatic translations update.
[odoo/odoo.git] / addons / auth_openid / controllers / main.py
1 # -*- coding: utf-8 -*-
2 ##############################################################################
3 #
4 #    OpenERP, Open Source Management Solution
5 #    Copyright (C) 2010-2012 OpenERP s.a. (<http://openerp.com>).
6 #
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.
11 #
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.
16 #
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/>.
19 #
20 ##############################################################################
21
22 import logging
23 import os
24 import tempfile
25 import getpass
26 import urllib
27
28 import werkzeug.urls
29 import werkzeug.exceptions
30
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
36
37 import openerp
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
41
42 from .. import utils
43
44 _logger = logging.getLogger(__name__)
45 oidutil.log = _logger.debug
46
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
51     if euid is not None:
52         info = '%s-%d' % (info, euid())
53     return info
54
55 _storedir = os.path.join(tempfile.gettempdir(), 
56                          'openerp-auth_openid-%s-store' % get_system_user())
57
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)
65                 if assoc:
66                     # update fields
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)
71
72                     # now, resign the message
73                     message.delArg(consumer.OPENID2_NS, 'sig')
74                     message.delArg(consumer.OPENID2_NS, 'signed')
75                     message = assoc.signMessage(message)
76
77         return super(GoogleAppsAwareConsumer, self).complete(message, endpoint, return_to)
78
79
80 class OpenIDController(openerp.addons.web.http.Controller):
81     _cp_path = '/auth_openid/login'
82
83     _store = filestore.FileOpenIDStore(_storedir)
84
85     _REQUIRED_ATTRIBUTES = ['email']
86     _OPTIONAL_ATTRIBUTES = 'nickname fullname postcode country language timezone'.split()
87
88     def _add_extensions(self, request):
89         """Add extensions to the request"""
90
91         sreg_request = sreg.SRegRequest(required=self._REQUIRED_ATTRIBUTES,
92                                         optional=self._OPTIONAL_ATTRIBUTES)
93         request.addExtension(sreg_request)
94
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))
102
103         request.addExtension(ax_request)
104
105     def _get_attributes_from_success_response(self, success_response):
106         attrs = {}
107
108         all_attrs = self._REQUIRED_ATTRIBUTES + self._OPTIONAL_ATTRIBUTES
109
110         sreg_resp = sreg.SRegResponse.fromSuccessResponse(success_response)
111         if sreg_resp:
112             for attr in all_attrs:
113                 value = sreg_resp.get(attr)
114                 if value is not None:
115                     attrs[attr] = value
116
117         ax_resp = ax.FetchResponse.fromSuccessResponse(success_response)
118         if ax_resp:
119             for attr in all_attrs:
120                 value = ax_resp.getSingle(utils.SREG2AX[attr])
121                 if value is not None:
122                     attrs[attr] = value
123         return attrs
124
125     def _get_realm(self, req):
126         return req.httprequest.host_url
127
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']
136
137     @openerp.addons.web.http.jsonrequest
138     def verify(self, req, db, url):
139         return self._verify(req, db, url)
140
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)
144
145         session = dict(dbname=db, openid_url=url)       # TODO add origin page ?
146         oidconsumer = consumer.Consumer(session, self._store)
147
148         try:
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'}
153
154         if request is None:
155             return {'error': 'No OpenID services found', 'title': 'OpenID Error'}
156
157         req.session.openid_session = session
158         self._add_extensions(request)
159
160         if request.shouldSendRedirect():
161             redirect_url = request.redirectURL(realm, redirect_to)
162             return {'action': 'redirect', 'value': redirect_url, 'session_id': req.session_id}
163         else:
164             form_html = request.htmlMarkup(realm, redirect_to)
165             return {'action': 'post', 'value': form_html, 'session_id': req.session_id}
166
167     @openerp.addons.web.http.httprequest
168     def process(self, req, **kw):
169         session = getattr(req.session, 'openid_session', None)
170         if not session:
171             return set_cookie_and_redirect(req, '/')
172
173         oidconsumer = consumer.Consumer(session, self._store, consumer_class=GoogleAppsAwareConsumer)
174
175         query = req.httprequest.args
176         info = oidconsumer.complete(query, req.httprequest.base_url)
177         display_identifier = info.getDisplayIdentifier()
178
179         session['status'] = info.status
180
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')
186
187                 installed = Modules.search_count(cr, SUPERUSER_ID, ['&', ('name', '=', 'auth_openid'), ('state', '=', 'installed')]) == 1
188                 if installed:
189
190                     Users = registry.get('res.users')
191
192                     #openid_url = info.endpoint.canonicalID or display_identifier
193                     openid_url = session['openid_url']
194
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)
199
200                     domain = []
201                     if openid_email:
202                         domain += ['|', ('openid_email', '=', False)]
203                     domain += [('openid_email', '=', openid_email)]
204
205                     domain += [('openid_url', '=', openid_url), ('active', '=', True)]
206
207                     ids = Users.search(cr, SUPERUSER_ID, domain)
208                     assert len(ids) < 2
209                     if ids:
210                         user_id = ids[0]
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
215                         cr.commit()
216
217                         return login_and_redirect(req, dbname, login, key)
218
219             session['message'] = 'This OpenID identifier is not associated to any active users'
220
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)
226         else:   # FAILURE
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.'
232
233         return set_cookie_and_redirect(req, '/#action=login&loginerror=1')
234
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')}
239
240
241 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: