X-Git-Url: http://git.inspyration.org/?a=blobdiff_plain;f=addons%2Fweb%2Fhttp.py;h=3699b993521f6fa090abcd9a9ebb8733852d1dd4;hb=af68d40f6a52dea93e7b817a6db04ba669e15581;hp=aed64cc9183428fecb9795db730f79818c7e06ba;hpb=0d7ec71bc9d2c0fed4999d7a796166b05293ea6f;p=odoo%2Fodoo.git diff --git a/addons/web/http.py b/addons/web/http.py index aed64cc..3699b99 100644 --- a/addons/web/http.py +++ b/addons/web/http.py @@ -61,8 +61,9 @@ class WebRequest(object): .. attribute:: httpsession - a :class:`~collections.Mapping` holding the HTTP session data for the - current http session + .. deprecated:: 8.0 + + Use ``self.session`` instead. .. attribute:: params @@ -77,16 +78,13 @@ class WebRequest(object): .. attribute:: session - :class:`~session.OpenERPSession` instance for the current request + a :class:`OpenERPSession` holding the HTTP session data for the + current http session .. attribute:: context :class:`~collections.Mapping` of context values for the current request - .. attribute:: debug - - ``bool``, indicates whether the debug mode is active on the client - .. attribute:: db ``str``, the name of the database linked to the current request. Can be ``None`` @@ -95,7 +93,7 @@ class WebRequest(object): .. attribute:: uid ``int``, the id of the user related to the current request. Can be ``None`` - if the current request uses the ``none`` or the ``db`` authenticatoin. + if the current request uses the ``none`` authenticatoin. """ def __init__(self, httprequest): self.httprequest = httprequest @@ -103,42 +101,30 @@ class WebRequest(object): self.httpsession = httprequest.session self.session = httprequest.session self.session_id = httprequest.session.sid - self.db = None + self.disable_db = False self.uid = None self.func = None self.auth_method = None self._cr_cm = None self._cr = None self.func_request_type = None - self.debug = self.httprequest.args.get('debug', False) is not False - with set_request(self): - self.db = self.session.db or db_monodb() # set db/uid trackers - they're cleaned up at the WSGI # dispatching phase in openerp.service.wsgi_server.application if self.db: - threading.current_thread().dbname = self.session.db + threading.current_thread().dbname = self.db if self.session.uid: threading.current_thread().uid = self.session.uid - self.context = self.session.context + self.context = dict(self.session.context) self.lang = self.context["lang"] def _authenticate(self): - if self.auth_method == "none": - self.db = None - self.uid = None - elif self.auth_method == "admin": - self.db = self.session.db or db_monodb() - if not self.db: - raise SessionExpiredException("No valid database for request %s" % self.httprequest) - self.uid = openerp.SUPERUSER_ID - else: # auth + if self.session.uid: try: self.session.check_security() except SessionExpiredException, e: + self.session.logout() raise SessionExpiredException("Session expired for request %s" % self.httprequest) - self.db = self.session.db - self.uid = self.session.uid - + auth_methods[self.auth_method]() @property def registry(self): """ @@ -148,6 +134,14 @@ class WebRequest(object): return openerp.modules.registry.RegistryManager.get(self.db) if self.db else None @property + def db(self): + """ + The registry to the database linked to this request. Can be ``None`` if the current request uses the + ``none'' authentication. + """ + return self.session.db if not self.disable_db else None + + @property def cr(self): """ The cursor initialized for the current method call. If the current request uses the ``none`` authentication @@ -180,32 +174,52 @@ class WebRequest(object): return self.func(*args, **kwargs) finally: # just to be sure no one tries to re-use the request - self.db = None + self.disable_db = True self.uid = None + @property + def debug(self): + return 'debug' in self.httprequest.args + + +def auth_method_user(): + request.uid = request.session.uid + +def auth_method_admin(): + if not request.db: + raise SessionExpiredException("No valid database for request %s" % request.httprequest) + request.uid = openerp.SUPERUSER_ID + +def auth_method_none(): + request.disable_db = True + request.uid = None + +auth_methods = { + "user": auth_method_user, + "admin": auth_method_admin, + "none": auth_method_none, +} + def route(route, type="http", auth="user"): """ Decorator marking the decorated method as being a handler for requests. The method must be part of a subclass of ``Controller``. - Decorator to put on a controller method to inform it does not require a user to be logged. When this decorator - is used, ``request.uid`` will be ``None``. The request will still try to detect the database and an exception - will be launched if there is no way to guess it. - :param route: string or array. The route part that will determine which http requests will match the decorated method. Can be a single string or an array of strings. See werkzeug's routing documentation for the format of route expression ( http://werkzeug.pocoo.org/docs/routing/ ). :param type: The type of request, can be ``'http'`` or ``'json'``. :param auth: The type of authentication method, can on of the following: - * ``auth``: The user must be authenticated. - * ``db``: There is no need for the user to be authenticated but there must be a way to find the current - database. + * ``user``: The user must be authenticated and the current request will perform using the rights of the + user. + * ``admin``: The user may not be authenticated and the current request will perform using the admin user. * ``none``: The method is always active, even if there is no database. Mainly used by the framework and - authentication modules. + authentication modules. There request code will not have any facilities to access the database nor have any + configuration indicating the current database nor the current user. """ assert type in ["http", "json"] - assert auth in ["user", "admin", "none"] + assert auth in auth_methods.keys() def decorator(f): if isinstance(route, list): f.routes = route @@ -230,8 +244,7 @@ class JsonRequest(WebRequest): --> {"jsonrpc": "2.0", "method": "call", - "params": {"session_id": "SID", - "context": {}, + "params": {"context": {}, "arg1": "val1" }, "id": null} @@ -243,8 +256,7 @@ class JsonRequest(WebRequest): --> {"jsonrpc": "2.0", "method": "call", - "params": {"session_id": "SID", - "context": {}, + "params": {"context": {}, "arg1": "val1" }, "id": null} @@ -268,13 +280,12 @@ class JsonRequest(WebRequest): self.jsonp = jsonp request = None request_id = args.get('id') - + if jsonp and self.httprequest.method == 'POST': # jsonp 2 steps step1 POST: save call - self.init(args) - def handler(): self.session.jsonp_requests[request_id] = self.httprequest.form['r'] + self.session.modified = True headers=[('Content-Type', 'text/plain; charset=utf-8')] r = werkzeug.wrappers.Response(request_id, headers=headers) return r @@ -285,7 +296,6 @@ class JsonRequest(WebRequest): request = args.get('r') elif jsonp and request_id: # jsonp 2 steps step2 GET: run and return result - self.init(args) request = self.session.jsonp_requests.pop(request_id, "") else: # regular jsonrpc2 @@ -298,8 +308,6 @@ class JsonRequest(WebRequest): def dispatch(self): """ Calls the method asked for by the JSON-RPC2 or JSONP request - - :returns: an utf8 encoded JSON-RPC2 or JSONP reply """ if self.jsonp_handler: return self.jsonp_handler() @@ -307,8 +315,6 @@ class JsonRequest(WebRequest): error = None try: - #if _logger.isEnabledFor(logging.DEBUG): - # _logger.debug("--> %s.%s\n%s", func.im_class.__name__, func.__name__, pprint.pformat(self.jsonrequest)) response['id'] = self.jsonrequest.get('id') response["result"] = self._call_function(**self.params) except AuthenticationError, e: @@ -330,9 +336,6 @@ class JsonRequest(WebRequest): if error: response["error"] = error - if _logger.isEnabledFor(logging.DEBUG): - _logger.debug("<--\n%s", pprint.pformat(response)) - if self.jsonp: # If we use jsonp, that's mean we are called from another host # Some browser (IE and Safari) do no allow third party cookies @@ -378,20 +381,16 @@ def to_jsonable(o): return u"%s" % o def jsonrequest(f): - """ Decorator marking the decorated method as being a handler for a - JSON-RPC request (the exact request path is specified via the - ``$(Controller._cp_path)/$methodname`` combination. - - If the method is called, it will be provided with a :class:`JsonRequest` - instance and all ``params`` sent during the JSON-RPC request, apart from - the ``session_id``, ``context`` and ``debug`` keys (which are stripped out - beforehand) + """ + .. deprecated:: 8.0 + + Use the ``route()`` decorator instead. """ f.combine = True - base = f.__name__ + base = f.__name__.lstrip('/') if f.__name__ == "index": base = "" - return route([base, os.path.join(base, "")], type="json", auth="user")(f) + return route([base, base + "/"], type="json", auth="none")(f) class HttpRequest(WebRequest): """ Regular GET/POST request @@ -401,12 +400,12 @@ class HttpRequest(WebRequest): def __init__(self, *args): super(HttpRequest, self).__init__(*args) params = dict(self.httprequest.args) - ex = set(["session_id", "debug", "db"]) + params.update(self.httprequest.form) + params.update(self.httprequest.files) + ex = set(["session_id"]) for k in params.keys(): if k in ex: del params[k] - params.update(self.httprequest.form) - params.update(self.httprequest.files) self.params = params def dispatch(self): @@ -416,7 +415,6 @@ class HttpRequest(WebRequest): akw[key] = value else: akw[key] = type(value) - #_logger.debug("%s --> %s.%s %r", self.httprequest.func, func.im_class.__name__, func.__name__, akw) try: r = self._call_function(**self.params) except werkzeug.exceptions.HTTPException, e: @@ -433,10 +431,6 @@ class HttpRequest(WebRequest): else: if not r: r = werkzeug.wrappers.Response(status=204) # no content - if isinstance(r, (werkzeug.wrappers.BaseResponse, werkzeug.exceptions.HTTPException)): - _logger.debug('<-- %s', r) - else: - _logger.debug("<-- size: %s", len(r)) return r def make_response(self, data, headers=None, cookies=None): @@ -465,20 +459,16 @@ class HttpRequest(WebRequest): return werkzeug.exceptions.NotFound(description) def httprequest(f): - """ Decorator marking the decorated method as being a handler for a - normal HTTP request (the exact request path is specified via the - ``$(Controller._cp_path)/$methodname`` combination. - - If the method is called, it will be provided with a :class:`HttpRequest` - instance and all ``params`` sent during the request (``GET`` and ``POST`` - merged in the same dictionary), apart from the ``session_id``, ``context`` - and ``debug`` keys (which are stripped out beforehand) + """ + .. deprecated:: 8.0 + + Use the ``route()`` decorator instead. """ f.combine = True - base = f.__name__ + base = f.__name__.lstrip('/') if f.__name__ == "index": base = "" - return route([base, os.path.join(base, "")], type="http", auth="user")(f) + return route([base, base + "/"], type="http", auth="none")(f) #---------------------------------------------------------- # Local storage of requests @@ -511,16 +501,13 @@ class ControllerType(type): def __init__(cls, name, bases, attrs): super(ControllerType, cls).__init__(name, bases, attrs) - # create wrappers for old-style methods with req as first argument - cls._methods_wrapper = {} + # flag old-style methods with req as first argument for k, v in attrs.items(): if inspect.isfunction(v): spec = inspect.getargspec(v) first_arg = spec.args[1] if len(spec.args) >= 2 else None if first_arg in ["req", "request"]: - def build_new(nv): - return lambda self, *args, **kwargs: nv(self, request, *args, **kwargs) - cls._methods_wrapper[k] = build_new(v) + v._first_arg_is_req = True # store the controller in the controllers list name_class = ("%s.%s" % (cls.__module__, cls.__name__), cls) @@ -529,7 +516,6 @@ class ControllerType(type): return # we want to know all modules that have controllers module = class_path[2] - controllers_per_module.setdefault(module, []) # but we only store controllers directly inheriting from Controller if not "Controller" in globals() or not Controller in bases: return @@ -538,12 +524,6 @@ class ControllerType(type): class Controller(object): __metaclass__ = ControllerType - def get_wrapped_method(self, name): - if name in self.__class__._methods_wrapper: - return functools.partial(self.__class__._methods_wrapper[name], self) - else: - return getattr(self, name) - ############################# # OpenERP Sessions # ############################# @@ -589,6 +569,8 @@ class Model(object): raise Exception("Trying to use Model with badly configured database or user.") mod = request.registry.get(self.model) + if method.startswith('_'): + raise Exception("Access denied") meth = getattr(mod, method) cr = request.cr result = meth(cr, request.uid, *args, **kw) @@ -608,12 +590,7 @@ class OpenERPSession(werkzeug.contrib.sessions.Session): self.modified = False super(OpenERPSession, self).__init__(*args, **kwargs) self.inited = True - self.setdefault("db", False) - self.setdefault("uid", False) - self.setdefault("login", False) - self.setdefault("password", False) - self.setdefault("context", {'tz': "UTC", "uid": None}) - self.setdefault("jsonp_requests", {}) + self._default_values() self.modified = False def __getattr__(self, attr): @@ -626,7 +603,7 @@ class OpenERPSession(werkzeug.contrib.sessions.Session): return self.__setitem__(k, v) object.__setattr__(self, k, v) - def authenticate(self, db, login=None, password=None, env=None, uid=None): + def authenticate(self, db, login=None, password=None, uid=None): """ Authenticate the current user with the given db, login and password. If successful, store the authentication parameters in the current session and request. @@ -635,6 +612,12 @@ class OpenERPSession(werkzeug.contrib.sessions.Session): """ if uid is None: + wsgienv = request.httprequest.environ + env = dict( + base_location=request.httprequest.url_root.rstrip('/'), + HTTP_HOST=wsgienv['HTTP_HOST'], + REMOTE_ADDR=wsgienv['REMOTE_ADDR'], + ) uid = openerp.netsvc.dispatch_rpc('common', 'authenticate', [db, login, password, env]) else: security.check(db, uid, password) @@ -642,8 +625,8 @@ class OpenERPSession(werkzeug.contrib.sessions.Session): self.uid = uid self.login = login self.password = password - request.db = db request.uid = uid + request.disable_db = False if uid: self.get_context() return uid @@ -661,6 +644,15 @@ class OpenERPSession(werkzeug.contrib.sessions.Session): def logout(self): for k in self.keys(): del self[k] + self._default_values() + + def _default_values(self): + self.setdefault("db", None) + self.setdefault("uid", None) + self.setdefault("login", None) + self.setdefault("password", None) + self.setdefault("context", {'tz': "UTC", "uid": None}) + self.setdefault("jsonp_requests", {}) def get_context(self): """ @@ -793,7 +785,7 @@ class OpenERPSession(werkzeug.contrib.sessions.Session): :type model: str :rtype: a model object """ - if self.db == False: + if not self.db: raise SessionExpiredException("Session expired") return Model(self, model) @@ -886,10 +878,7 @@ class Root(object): def dispatch(self, environ, start_response): """ - Performs the actual WSGI dispatching for the application, may be - wrapped during the initialization of the object. - - Call the object directly. + Performs the actual WSGI dispatching for the application. """ try: httprequest = werkzeug.wrappers.Request(environ) @@ -899,13 +888,19 @@ class Root(object): session_gc(self.session_store) sid = httprequest.args.get('session_id') + explicit_session = True + if not sid: + sid = httprequest.headers.get("X-Openerp-Session-Id") if not sid: sid = httprequest.cookies.get('session_id') + explicit_session = False if sid is None: httprequest.session = self.session_store.new() else: httprequest.session = self.session_store.get(sid) + self._find_db(httprequest) + if not "lang" in httprequest.session.context: lang = httprequest.accept_languages.best or "en_US" lang = babel.core.LOCALE_ALIASES.get(lang, lang).replace('-', '_') @@ -935,24 +930,26 @@ class Root(object): if httprequest.session.should_save: self.session_store.save(httprequest.session) - if hasattr(response, 'set_cookie'): - response.set_cookie('session_id', httprequest.session.sid) + if not explicit_session and hasattr(response, 'set_cookie'): + response.set_cookie('session_id', httprequest.session.sid, max_age=90 * 24 * 60 * 60) return response(environ, start_response) except werkzeug.exceptions.HTTPException, e: return e(environ, start_response) + def _find_db(self, httprequest): + db = db_monodb(httprequest) + if db != httprequest.session.db: + httprequest.session.logout() + httprequest.session.db = db + def _build_request(self, httprequest): if httprequest.args.get('jsonp'): return JsonRequest(httprequest) - content = httprequest.stream.read() - import cStringIO - httprequest.stream = cStringIO.StringIO(content) - try: - simplejson.loads(content) + if httprequest.mimetype == "application/json": return JsonRequest(httprequest) - except: + else: return HttpRequest(httprequest) def load_addons(self): @@ -989,8 +986,8 @@ class Root(object): cls = v[1] subclasses = cls.__subclasses__() - subclasses = [c for c in subclasses if c.__module__.split(".")[:2] == ["openerp", "addons"] and \ - cls.__module__.split(".")[2] in modules] + subclasses = [c for c in subclasses if c.__module__.startswith('openerp.addons.') and + c.__module__.split(".")[2] in modules] if subclasses: name = "%s (extended by %s)" % (cls.__name__, ', '.join(sub.__name__ for sub in subclasses)) cls = type(name, tuple(reversed(subclasses)), {}) @@ -999,17 +996,15 @@ class Root(object): members = inspect.getmembers(o) for mk, mv in members: if inspect.ismethod(mv) and getattr(mv, 'exposed', False) and \ - nodb_only == (getattr(mv, "auth", None) == "none"): - function = (o.get_wrapped_method(mk), mv) + nodb_only == (getattr(mv, "auth", "none") == "none"): for url in mv.routes: if getattr(mv, "combine", False): - url = os.path.join(o._cp_path, url) + url = o._cp_path.rstrip('/') + '/' + url.lstrip('/') if url.endswith("/") and len(url) > 1: url = url[: -1] - routing_map.add(routing.Rule(url, endpoint=function)) + routing_map.add(routing.Rule(url, endpoint=mv)) - modules_set = set(controllers_per_module.keys()) - modules_set -= set("web") + modules_set = set(controllers_per_module.keys()) - set(['web']) # building all none methods gen(["web"] + sorted(modules_set), True) if not db: @@ -1018,12 +1013,12 @@ class Root(object): registry = openerp.modules.registry.RegistryManager.get(db) with registry.cursor() as cr: m = registry.get('ir.module.module') - ids = m.search(cr, openerp.SUPERUSER_ID, [('state','=','installed')]) + ids = m.search(cr, openerp.SUPERUSER_ID, [('state', '=', 'installed'), ('name', '!=', 'web')]) installed = set([x['name'] for x in m.read(cr, 1, ids, ['name'])]) modules_set = modules_set.intersection(set(installed)) modules = ["web"] + sorted(modules_set) # building all other methods - gen(["web"] + sorted(modules_set), False) + gen(modules, False) return routing_map @@ -1038,76 +1033,77 @@ class Root(object): def find_handler(self): """ - Tries to discover the controller handling the request for the path - specified by the provided parameters - - :param path: path to match - :returns: a callable matching the path sections - :rtype: ``Controller | None`` + Tries to discover the controller handling the request for the path specified in the request. """ path = request.httprequest.path urls = self.get_db_router(request.db).bind("") - matched, arguments = urls.match(path) + func, arguments = urls.match(path) arguments = dict([(k, v) for k, v in arguments.items() if not k.startswith("_ignored_")]) - func, original = matched def nfunc(*args, **kwargs): kwargs.update(arguments) + if getattr(func, '_first_arg_is_req', False): + args = (request,) + args return func(*args, **kwargs) request.func = nfunc - request.auth_method = getattr(original, "auth", "user") - request.func_request_type = original.exposed + request.auth_method = getattr(func, "auth", "user") + request.func_request_type = func.exposed + +root = None -def db_list(force=False): - proxy = request.session.proxy("db") - dbs = proxy.list(force) - h = request.httprequest.environ['HTTP_HOST'].split(':')[0] +def db_list(force=False, httprequest=None): + httprequest = httprequest or request.httprequest + dbs = openerp.netsvc.dispatch_rpc("db", "list", [force]) + h = httprequest.environ['HTTP_HOST'].split(':')[0] d = h.split('.')[0] r = openerp.tools.config['dbfilter'].replace('%h', h).replace('%d', d) dbs = [i for i in dbs if re.match(r, i)] return dbs -def db_redirect(match_first_only_if_unique): - db = False - redirect = False - - # 1 try the db in the url - db_url = request.httprequest.args.get('db') - if db_url: - return (db_url, False) +def db_monodb(httprequest=None): + """ + Magic function to find the current database. - dbs = db_list(True) + Implementation details: - # 2 use the database from the cookie if it's listable and still listed - cookie_db = request.httprequest.cookies.get('last_used_database') - if cookie_db in dbs: - db = cookie_db + * Magic + * More magic - # 3 use the first db if user can list databases - if dbs and not db and (not match_first_only_if_unique or len(dbs) == 1): - db = dbs[0] + Returns ``None`` if the magic is not magic enough. + """ + httprequest = httprequest or request.httprequest + db = None + redirect = None - # redirect to the chosen db if multiple are available - if db and len(dbs) > 1: - query = dict(urlparse.parse_qsl(request.httprequest.query_string, keep_blank_values=True)) - query.update({'db': db}) - redirect = request.httprequest.path + '?' + urllib.urlencode(query) - return (db, redirect) + dbs = db_list(True, httprequest) -def db_monodb(): - # if only one db exists, return it else return False - return db_redirect(True)[0] + # try the db already in the session + db_session = httprequest.session.db + if db_session in dbs: + return db_session + # if dbfilters was specified when launching the server and there is + # only one possible db, we take that one + if openerp.tools.config['dbfilter'] != ".*" and len(dbs) == 1: + return dbs[0] + return None -class JsonRpcController(Controller): +class CommonController(Controller): @route('/jsonrpc', type='json', auth="none") def jsonrpc(self, service, method, args): """ Method used by client APIs to contact OpenERP. """ return openerp.netsvc.dispatch_rpc(service, method, args) + @route('/gen_session_id', type='json', auth="none") + def gen_session_id(self): + nsession = root.session_store.new() + return nsession.sid + def wsgi_postload(): - openerp.wsgi.register_wsgi_handler(Root()) + global root + root = Root() + openerp.wsgi.register_wsgi_handler(root) # vim:et:ts=4:sw=4: