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