[FIX] auth_openid, edi: fix web imports to correctly handle conflicts with e.g. web.py
[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 sys
25 import urllib
26
27 import werkzeug.urls
28 import werkzeug.exceptions
29
30 from openerp.modules.registry import RegistryManager
31 try:
32     import openerp.addons.web.common.http as openerpweb
33 except ImportError:
34     import web.common.http as openerpweb
35
36 from openid import oidutil
37 from openid.store import memstore
38 #from openid.store import filestore
39 from openid.consumer import consumer
40 from openid.cryptutil import randomString
41 from openid.extensions import ax, sreg
42
43 from .. import utils
44
45
46
47 _logger = logging.getLogger('web.auth_openid')
48 oidutil.log = logging.getLogger('openid').debug
49
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', consumer.no_default)
55             if server_url.startswith('https://www.google.com/a/'):
56                 # update fields
57                 for attr in ['claimed_id', 'identity']:
58                     value = message.getArg(consumer.OPENID2_NS, attr)
59                     value = 'https://www.google.com/accounts/o8/user-xrds?uri=%s' % urllib.quote_plus(value)
60                     message.setArg(consumer.OPENID2_NS, attr, value)
61
62                 # now, resign the message
63                 assoc_handle = message.getArg(consumer.OPENID_NS, 'assoc_handle')
64                 assoc = self.store.getAssociation(server_url, assoc_handle)
65                 message.delArg(consumer.OPENID2_NS, 'sig')
66                 message.delArg(consumer.OPENID2_NS, 'signed')
67                 message = assoc.signMessage(message)
68
69         return super(GoogleAppsAwareConsumer, self).complete(message, endpoint, return_to) 
70
71
72 class OpenIDController(openerpweb.Controller):
73     _cp_path = '/auth_openid/login'
74
75     _store = memstore.MemoryStore()  # TODO use a filestore
76
77     _REQUIRED_ATTRIBUTES = ['email']
78     _OPTIONAL_ATTRIBUTES = 'nickname fullname postcode country language timezone'.split()
79
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.jsonrequest
122     def verify(self, req, db, url):
123         redirect_to = werkzeug.urls.Href(req.httprequest.host_url + 'auth_openid/login/process')(session_id=req.session_id)
124         realm = self._get_realm(req)
125
126         session = dict(dbname=db, openid_url=url)       # TODO add origin page ?
127         oidconsumer = consumer.Consumer(session, self._store)
128
129         try:
130             request = oidconsumer.begin(url)
131         except consumer.DiscoveryFailure, exc:
132             fetch_error_string = 'Error in discovery: %s' % (str(exc[0]),)
133             return {'error': fetch_error_string, 'title': 'OpenID Error'}
134
135         if request is None:
136             return {'error': 'No OpenID services found', 'title': 'OpenID Error'}
137
138         req.session.openid_session = session
139         self._add_extensions(request)
140
141         if request.shouldSendRedirect():
142             redirect_url = request.redirectURL(realm, redirect_to)
143             return {'action': 'redirect', 'value': redirect_url, 'session_id': req.session_id}
144         else:
145             form_html = request.htmlMarkup(realm, redirect_to)
146             return {'action': 'post', 'value': form_html, 'session_id': req.session_id}
147
148     
149     @openerpweb.httprequest
150     def process(self, req, **kw):
151         session = getattr(req.session, 'openid_session', None)
152         if not session:
153             return werkzeug.utils.redirect('/')
154
155         oidconsumer = consumer.Consumer(session, self._store, consumer_class=GoogleAppsAwareConsumer)
156
157         query = req.httprequest.args
158         info = oidconsumer.complete(query, req.httprequest.base_url)
159         display_identifier = info.getDisplayIdentifier()
160
161         session['status'] = info.status
162         user_id = None
163
164         if info.status == consumer.SUCCESS:
165             dbname = session['dbname']
166             with utils.cursor(dbname) as cr:
167                 registry = RegistryManager.get(dbname)
168                 Modules = registry.get('ir.module.module')
169
170                 installed = Modules.search_count(cr, 1, ['&', ('name', '=', 'auth_openid'), ('state', '=', 'installed')]) == 1
171                 if installed:
172
173                     Users = registry.get('res.users')
174
175                     #openid_url = info.endpoint.canonicalID or display_identifier
176                     openid_url = session['openid_url']
177
178                     attrs = self._get_attributes_from_success_response(info)
179                     attrs['openid_url'] = openid_url
180                     session['attributes'] = attrs
181                     openid_email = attrs.get('email', False)
182
183                     domain = []
184                     if openid_email:
185                         domain += ['|', ('openid_email', '=', False)]
186                     domain += [('openid_email', '=', openid_email)]
187
188                     domain += [
189                                ('openid_url', '=', openid_url),
190                                ('active', '=', True),
191                               ]
192                     ids = Users.search(cr, 1, domain)
193                     assert len(ids) < 2
194                     if ids:
195                         user_id = ids[0]
196                         login = Users.browse(cr, 1, user_id).login
197                         key = randomString(utils.KEY_LENGTH, '0123456789abcdef')
198                         Users.write(cr, 1, [user_id], {'openid_key': key})
199                         # TODO fill empty fields with the ones from sreg/ax
200                         cr.commit()
201
202                         u = req.session.login(dbname, login, key)
203
204             if not user_id:
205                 session['message'] = 'This OpenID identifier is not associated to any active users'
206
207                 
208         elif info.status == consumer.SETUP_NEEDED:
209             session['message'] = info.setup_url
210         elif info.status == consumer.FAILURE and display_identifier:
211             fmt = "Verification of %s failed: %s"
212             session['message'] = fmt % (display_identifier, info.message)
213         else:   # FAILURE
214             # Either we don't understand the code or there is no
215             # openid_url included with the error. Give a generic
216             # failure message. The library should supply debug
217             # information in a log.
218             session['message'] = 'Verification failed.'
219
220
221         fragment = '#loginerror' if not user_id else ''
222         return werkzeug.utils.redirect('/'+fragment)
223
224     @openerpweb.jsonrequest
225     def status(self, req):
226         session = getattr(req.session, 'openid_session', {})
227         return {'status': session.get('status'), 'message': session.get('message')}
228
229
230 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: