[FIX] Restore /login redirection on SessionExpired
[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             raise http.SessionExpiredException("Session expired")
62
63     def _auth_method_none(self):
64         request.uid = None
65
66     def _auth_method_public(self):
67         if not request.session.uid:
68             dummy, request.uid = self.pool['ir.model.data'].get_object_reference(request.cr, openerp.SUPERUSER_ID, 'base', 'public_user')
69         else:
70             request.uid = request.session.uid
71
72     def _authenticate(self, auth_method='user'):
73         try:
74             if request.session.uid:
75                 try:
76                     request.session.check_security()
77                     # what if error in security.check()
78                     #   -> res_users.check()
79                     #   -> res_users.check_credentials()
80                 except (openerp.exceptions.AccessDenied, openerp.http.SessionExpiredException):
81                     # All other exceptions mean undetermined status (e.g. connection pool full),
82                     # let them bubble up
83                     request.session.logout()
84             getattr(self, "_auth_method_%s" % auth_method)()
85         except (openerp.exceptions.AccessDenied, openerp.http.SessionExpiredException):
86             raise
87         except Exception:
88             _logger.exception("Exception during request Authentication.")
89             raise openerp.exceptions.AccessDenied()
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         try:
95             return request._handle_exception(exception)
96         except openerp.exceptions.AccessDenied:
97             return werkzeug.exceptions.Forbidden()
98
99     def _dispatch(self):
100         # locate the controller method
101         try:
102             rule, arguments = self._find_handler(return_rule=True)
103             func = rule.endpoint
104         except werkzeug.exceptions.NotFound, e:
105             return self._handle_exception(e)
106
107         # check authentication level
108         try:
109             auth_method = self._authenticate(func.routing["auth"])
110         except Exception as e:
111             return self._handle_exception(e)
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: