Automatic @auth='user' redirection for unlogged users
[odoo/odoo.git] / openerp / addons / base / ir / ir_http.py
1 #----------------------------------------------------------
2 # ir_http modular http routing
3 #----------------------------------------------------------
4 import logging
5 import re
6 import sys
7
8 import werkzeug
9
10 import openerp
11 from openerp import http
12 from openerp.http import request
13 from openerp.osv import osv, orm
14
15 _logger = logging.getLogger(__name__)
16
17 UID_PLACEHOLDER = object()
18
19 class ModelConverter(werkzeug.routing.BaseConverter):
20
21     def __init__(self, url_map, model=False):
22         super(ModelConverter, self).__init__(url_map)
23         self.model = model
24         self.regex = '([0-9]+)'
25
26     def to_python(self, value):
27         m = re.match(self.regex, value)
28         return request.registry[self.model].browse(
29             request.cr, UID_PLACEHOLDER, int(m.group(1)), context=request.context)
30
31     def to_url(self, value):
32         return value.id
33
34 class ModelsConverter(werkzeug.routing.BaseConverter):
35
36     def __init__(self, url_map, model=False):
37         super(ModelsConverter, self).__init__(url_map)
38         self.model = model
39         # TODO add support for slug in the form [A-Za-z0-9-] bla-bla-89 -> id 89
40         self.regex = '([0-9,]+)'
41
42     def to_python(self, value):
43         return request.registry[self.model].browse(request.cr, UID_PLACEHOLDER, [int(i) for i in value.split(',')], context=request.context)
44
45     def to_url(self, value):
46         return ",".join(i.id for i in value)
47
48 class ir_http(osv.AbstractModel):
49     _name = 'ir.http'
50     _description = "HTTP routing"
51
52     def _get_converters(self):
53         return {'model': ModelConverter, 'models': ModelsConverter}
54
55     def _find_handler(self, return_rule=False):
56         return self.routing_map().bind_to_environ(request.httprequest.environ).match(return_rule=return_rule)
57
58     def _auth_method_user(self):
59         request.uid = request.session.uid
60         if not request.uid:
61             if not request.params.get('noredirect'):
62                 query = werkzeug.url_encode({
63                     'redirect': request.httprequest.url,
64                 })
65                 response = werkzeug.utils.redirect('/web/login?%s' % query)
66                 werkzeug.exceptions.abort(response)
67             raise http.SessionExpiredException("Session expired")
68
69     def _auth_method_none(self):
70         request.uid = None
71
72     def _auth_method_public(self):
73         if not request.session.uid:
74             dummy, request.uid = self.pool['ir.model.data'].get_object_reference(request.cr, openerp.SUPERUSER_ID, 'base', 'public_user')
75         else:
76             request.uid = request.session.uid
77
78     def _authenticate(self, auth_method='user'):
79         if request.session.uid:
80             try:
81                 request.session.check_security()
82                 # what if error in security.check()
83                 #   -> res_users.check()
84                 #   -> res_users.check_credentials()
85             except (openerp.exceptions.AccessDenied, openerp.http.SessionExpiredException):
86                 # All other exceptions mean undetermined status (e.g. connection pool full),
87                 # let them bubble up
88                 request.session.logout()
89         getattr(self, "_auth_method_%s" % auth_method)()
90         return auth_method
91
92     def _handle_exception(self, exception):
93         # If handle_exception returns something different than None, it will be used as a response
94         return request._handle_exception(exception)
95
96     def _dispatch(self):
97         # locate the controller method
98         try:
99             rule, arguments = self._find_handler(return_rule=True)
100             func = rule.endpoint
101         except werkzeug.exceptions.NotFound, e:
102             return self._handle_exception(e)
103
104         # check authentication level
105         try:
106             auth_method = self._authenticate(func.routing["auth"])
107         except Exception:
108             # force a Forbidden exception with the original traceback
109             return self._handle_exception(
110                 convert_exception_to(
111                     werkzeug.exceptions.Forbidden))
112
113         processing = self._postprocess_args(arguments, rule)
114         if processing:
115             return processing
116
117
118         # set and execute handler
119         try:
120             request.set_handler(func, arguments, auth_method)
121             result = request.dispatch()
122             if isinstance(result, Exception):
123                 raise result
124         except Exception, e:
125             return self._handle_exception(e)
126
127         return result
128
129     def _postprocess_args(self, arguments, rule):
130         """ post process arg to set uid on browse records """
131         for arg in arguments.itervalues():
132             if isinstance(arg, orm.browse_record) and arg._uid is UID_PLACEHOLDER:
133                 arg._uid = request.uid
134                 try:
135                     arg[arg._rec_name]
136                 except KeyError:
137                     return self._handle_exception(werkzeug.exceptions.NotFound())
138
139     def routing_map(self):
140         if not hasattr(self, '_routing_map'):
141             _logger.info("Generating routing map")
142             cr = request.cr
143             m = request.registry.get('ir.module.module')
144             ids = m.search(cr, openerp.SUPERUSER_ID, [('state', '=', 'installed'), ('name', '!=', 'web')], context=request.context)
145             installed = set(x['name'] for x in m.read(cr, 1, ids, ['name'], context=request.context))
146             if openerp.tools.config['test_enable']:
147                 installed.add(openerp.modules.module.current_test)
148             mods = [''] + openerp.conf.server_wide_modules + sorted(installed)
149             self._routing_map = http.routing_map(mods, False, converters=self._get_converters())
150
151         return self._routing_map
152
153 def convert_exception_to(to_type, with_message=False):
154     """ Should only be called from an exception handler. Fetches the current
155     exception data from sys.exc_info() and creates a new exception of type
156     ``to_type`` with the original traceback.
157
158     If ``with_message`` is ``True``, sets the new exception's message to be
159     the stringification of the original exception. If ``False``, does not
160     set the new exception's message. Otherwise, uses ``with_message`` as the
161     new exception's message.
162
163     :type with_message: str|bool
164     """
165     etype, original, tb = sys.exc_info()
166     try:
167         if with_message is False:
168             message = None
169         elif with_message is True:
170             message = str(original)
171         else:
172             message = str(with_message)
173
174         raise to_type, message, tb
175     except to_type, e:
176         return e
177
178 # vim:et: