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