[REF] Replace the user id 1 by openerp.SUPERUSER_ID
[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 urllib
26 from openerp import SUPERUSER_ID
27
28 import werkzeug.urls
29 import werkzeug.exceptions
30
31 from openerp.modules.registry import RegistryManager
32 from openerp.addons.web.controllers.main import login_and_redirect, set_cookie_and_redirect
33 try:
34     import openerp.addons.web.common.http as openerpweb
35 except ImportError:
36     import web.common.http as openerpweb    # noqa
37
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
43
44 from .. import utils
45
46 _logger = logging.getLogger(__name__)
47 oidutil.log = _logger.debug
48
49 _storedir = os.path.join(tempfile.gettempdir(), 'openerp-auth_openid-store')
50
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)
58                 if assoc:
59                     # update fields
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)
64
65                     # now, resign the message
66                     message.delArg(consumer.OPENID2_NS, 'sig')
67                     message.delArg(consumer.OPENID2_NS, 'signed')
68                     message = assoc.signMessage(message)
69
70         return super(GoogleAppsAwareConsumer, self).complete(message, endpoint, return_to)
71
72
73 class OpenIDController(openerpweb.Controller):
74     _cp_path = '/auth_openid/login'
75
76     _store = filestore.FileOpenIDStore(_storedir)
77
78     _REQUIRED_ATTRIBUTES = ['email']
79     _OPTIONAL_ATTRIBUTES = 'nickname fullname postcode country language timezone'.split()
80
81     def _add_extensions(self, request):
82         """Add extensions to the request"""
83
84         sreg_request = sreg.SRegRequest(required=self._REQUIRED_ATTRIBUTES,
85                                         optional=self._OPTIONAL_ATTRIBUTES)
86         request.addExtension(sreg_request)
87
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))
95
96         request.addExtension(ax_request)
97
98     def _get_attributes_from_success_response(self, success_response):
99         attrs = {}
100
101         all_attrs = self._REQUIRED_ATTRIBUTES + self._OPTIONAL_ATTRIBUTES
102
103         sreg_resp = sreg.SRegResponse.fromSuccessResponse(success_response)
104         if sreg_resp:
105             for attr in all_attrs:
106                 value = sreg_resp.get(attr)
107                 if value is not None:
108                     attrs[attr] = value
109
110         ax_resp = ax.FetchResponse.fromSuccessResponse(success_response)
111         if ax_resp:
112             for attr in all_attrs:
113                 value = ax_resp.getSingle(utils.SREG2AX[attr])
114                 if value is not None:
115                     attrs[attr] = value
116         return attrs
117
118     def _get_realm(self, req):
119         return req.httprequest.host_url
120
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']
129
130     @openerpweb.jsonrequest
131     def verify(self, req, db, url):
132         return self._verify(req, db, url)
133
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)
137
138         session = dict(dbname=db, openid_url=url)       # TODO add origin page ?
139         oidconsumer = consumer.Consumer(session, self._store)
140
141         try:
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'}
146
147         if request is None:
148             return {'error': 'No OpenID services found', 'title': 'OpenID Error'}
149
150         req.session.openid_session = session
151         self._add_extensions(request)
152
153         if request.shouldSendRedirect():
154             redirect_url = request.redirectURL(realm, redirect_to)
155             return {'action': 'redirect', 'value': redirect_url, 'session_id': req.session_id}
156         else:
157             form_html = request.htmlMarkup(realm, redirect_to)
158             return {'action': 'post', 'value': form_html, 'session_id': req.session_id}
159
160     @openerpweb.httprequest
161     def process(self, req, **kw):
162         session = getattr(req.session, 'openid_session', None)
163         if not session:
164             return set_cookie_and_redirect(req, '/')
165
166         oidconsumer = consumer.Consumer(session, self._store, consumer_class=GoogleAppsAwareConsumer)
167
168         query = req.httprequest.args
169         info = oidconsumer.complete(query, req.httprequest.base_url)
170         display_identifier = info.getDisplayIdentifier()
171
172         session['status'] = info.status
173
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')
179
180                 installed = Modules.search_count(cr, SUPERUSER_ID, ['&', ('name', '=', 'auth_openid'), ('state', '=', 'installed')]) == 1
181                 if installed:
182
183                     Users = registry.get('res.users')
184
185                     #openid_url = info.endpoint.canonicalID or display_identifier
186                     openid_url = session['openid_url']
187
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)
192
193                     domain = []
194                     if openid_email:
195                         domain += ['|', ('openid_email', '=', False)]
196                     domain += [('openid_email', '=', openid_email)]
197
198                     domain += [('openid_url', '=', openid_url), ('active', '=', True)]
199
200                     ids = Users.search(cr, SUPERUSER_ID, domain)
201                     assert len(ids) < 2
202                     if ids:
203                         user_id = ids[0]
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
208                         cr.commit()
209
210                         return login_and_redirect(req, dbname, login, key)
211
212             session['message'] = 'This OpenID identifier is not associated to any active users'
213
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)
219         else:   # FAILURE
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.'
225
226         return set_cookie_and_redirect(req, '/#action=login&loginerror=1')
227
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')}
232
233
234 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: