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