[MERGE] from upstream

bzr revid: fva@openerp.com-20130712121148-0ys14v8jc33t6sf5
This commit is contained in:
Frédéric van der Essen 2013-07-12 14:11:48 +02:00
commit 16d3b79dfd
6 changed files with 241 additions and 286 deletions

View File

@ -362,8 +362,6 @@ def login_and_redirect(db, login, key, redirect_url='/'):
def set_cookie_and_redirect(redirect_url): def set_cookie_and_redirect(redirect_url):
redirect = werkzeug.utils.redirect(redirect_url, 303) redirect = werkzeug.utils.redirect(redirect_url, 303)
redirect.autocorrect_location_header = False redirect.autocorrect_location_header = False
cookie_val = urllib2.quote(simplejson.dumps(request.session_id))
redirect.set_cookie('instance0|session_id', cookie_val)
return redirect return redirect
def load_actions_from_ir_values(key, key2, models, meta): def load_actions_from_ir_values(key, key2, models, meta):
@ -812,16 +810,16 @@ class Session(http.Controller):
request.session.ensure_valid() request.session.ensure_valid()
return { return {
"session_id": request.session_id, "session_id": request.session_id,
"uid": request.session._uid, "uid": request.session.uid,
"user_context": request.session.get_context() if request.session._uid else {}, "user_context": request.session.get_context() if request.session.uid else {},
"db": request.session._db, "db": request.session.db,
"username": request.session._login, "username": request.session.login,
} }
@http.route('/web/session/get_session_info', type='json', auth="none") @http.route('/web/session/get_session_info', type='json', auth="none")
def get_session_info(self): def get_session_info(self):
request.uid = request.session._uid request.uid = request.session.uid
request.db = request.session._db request.db = request.session.db
return self.session_info() return self.session_info()
@http.route('/web/session/authenticate', type='json', auth="none") @http.route('/web/session/authenticate', type='json', auth="none")
@ -855,7 +853,7 @@ class Session(http.Controller):
@http.route('/web/session/sc_list', type='json', auth="user") @http.route('/web/session/sc_list', type='json', auth="user")
def sc_list(self): def sc_list(self):
return request.session.model('ir.ui.view_sc').get_sc( return request.session.model('ir.ui.view_sc').get_sc(
request.session._uid, "ir.ui.menu", request.context) request.session.uid, "ir.ui.menu", request.context)
@http.route('/web/session/get_lang_list', type='json', auth="none") @http.route('/web/session/get_lang_list', type='json', auth="none")
def get_lang_list(self): def get_lang_list(self):
@ -916,7 +914,7 @@ class Session(http.Controller):
@http.route('/web/session/destroy', type='json', auth="user") @http.route('/web/session/destroy', type='json', auth="user")
def destroy(self): def destroy(self):
request.session._suicide = True request.session.logout()
class Menu(http.Controller): class Menu(http.Controller):
@ -930,7 +928,7 @@ class Menu(http.Controller):
s = request.session s = request.session
Menus = s.model('ir.ui.menu') Menus = s.model('ir.ui.menu')
# If a menu action is defined use its domain to get the root menu items # If a menu action is defined use its domain to get the root menu items
user_menu_id = s.model('res.users').read([s._uid], ['menu_id'], user_menu_id = s.model('res.users').read([s.uid], ['menu_id'],
request.context)[0]['menu_id'] request.context)[0]['menu_id']
menu_domain = [('parent_id', '=', False)] menu_domain = [('parent_id', '=', False)]
@ -1127,7 +1125,7 @@ class View(http.Controller):
def add_custom(self, view_id, arch): def add_custom(self, view_id, arch):
CustomView = request.session.model('ir.ui.view.custom') CustomView = request.session.model('ir.ui.view.custom')
CustomView.create({ CustomView.create({
'user_id': request.session._uid, 'user_id': request.session.uid,
'ref_id': view_id, 'ref_id': view_id,
'arch': arch 'arch': arch
}, request.context) }, request.context)
@ -1136,7 +1134,7 @@ class View(http.Controller):
@http.route('/web/view/undo_custom', type='json', auth="user") @http.route('/web/view/undo_custom', type='json', auth="user")
def undo_custom(self, view_id, reset=False): def undo_custom(self, view_id, reset=False):
CustomView = request.session.model('ir.ui.view.custom') CustomView = request.session.model('ir.ui.view.custom')
vcustom = CustomView.search([('user_id', '=', request.session._uid), ('ref_id' ,'=', view_id)], vcustom = CustomView.search([('user_id', '=', request.session.uid), ('ref_id' ,'=', view_id)],
0, False, False, request.context) 0, False, False, request.context)
if vcustom: if vcustom:
if reset: if reset:
@ -1319,9 +1317,9 @@ class Binary(http.Controller):
def company_logo(self, dbname=None): def company_logo(self, dbname=None):
# TODO add etag, refactor to use /image code for etag # TODO add etag, refactor to use /image code for etag
uid = None uid = None
if request.session._db: if request.session.db:
dbname = request.session._db dbname = request.session.db
uid = request.session._uid uid = request.session.uid
elif dbname is None: elif dbname is None:
dbname = db_monodb() dbname = db_monodb()
@ -1687,14 +1685,14 @@ class Reports(http.Controller):
raise ValueError("action['datas']['ids'] and context['active_ids'] are undefined") raise ValueError("action['datas']['ids'] and context['active_ids'] are undefined")
report_id = report_srv.report( report_id = report_srv.report(
request.session._db, request.session._uid, request.session._password, request.session.db, request.session.uid, request.session.password,
action["report_name"], report_ids, action["report_name"], report_ids,
report_data, context) report_data, context)
report_struct = None report_struct = None
while True: while True:
report_struct = report_srv.report_get( report_struct = report_srv.report_get(
request.session._db, request.session._uid, request.session._password, report_id) request.session.db, request.session.uid, request.session.password, report_id)
if report_struct["state"]: if report_struct["state"]:
break break

View File

@ -61,8 +61,9 @@ class WebRequest(object):
.. attribute:: httpsession .. attribute:: httpsession
a :class:`~collections.Mapping` holding the HTTP session data for the .. deprecated:: 8.0
current http session
Use ``self.session`` instead.
.. attribute:: params .. attribute:: params
@ -77,7 +78,8 @@ class WebRequest(object):
.. attribute:: session .. 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 .. attribute:: context
@ -95,12 +97,14 @@ class WebRequest(object):
.. attribute:: uid .. attribute:: uid
``int``, the id of the user related to the current request. Can be ``None`` ``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): def __init__(self, httprequest):
self.httprequest = httprequest self.httprequest = httprequest
self.httpresponse = None self.httpresponse = None
self.httpsession = httprequest.session self.httpsession = httprequest.session
self.session = httprequest.session
self.session_id = httprequest.session.sid
self.db = None self.db = None
self.uid = None self.uid = None
self.func = None self.func = None
@ -108,66 +112,36 @@ class WebRequest(object):
self._cr_cm = None self._cr_cm = None
self._cr = None self._cr = None
self.func_request_type = None self.func_request_type = None
self.debug = self.httprequest.args.get('debug', False) is not False
def init(self, params):
self.params = dict(params)
# OpenERP session setup
self.session_id = self.params.pop("session_id", None)
if not self.session_id:
i0 = self.httprequest.cookies.get("instance0|session_id", None)
if i0:
self.session_id = simplejson.loads(urllib2.unquote(i0))
else:
self.session_id = uuid.uuid4().hex
self.session = self.httpsession.get(self.session_id)
if not self.session:
self.session = OpenERPSession()
self.httpsession[self.session_id] = self.session
with set_request(self): with set_request(self):
self.db = self.session._db or db_monodb() self.db = self.session.db or db_monodb()
# TODO: remove this
# set db/uid trackers - they're cleaned up at the WSGI # set db/uid trackers - they're cleaned up at the WSGI
# dispatching phase in openerp.service.wsgi_server.application # dispatching phase in openerp.service.wsgi_server.application
if self.session._db: if self.db:
threading.current_thread().dbname = self.session._db threading.current_thread().dbname = self.session.db
if self.session._uid: if self.session.uid:
threading.current_thread().uid = self.session._uid threading.current_thread().uid = self.session.uid
self.context = self.session.context
self.context = self.params.pop('context', {}) self.lang = self.context["lang"]
self.debug = self.params.pop('debug', False) is not False
# Determine self.lang
lang = self.params.get('lang', None)
if lang is None:
lang = self.context.get('lang')
if lang is None:
lang = self.httprequest.cookies.get('lang')
if lang is None:
lang = self.httprequest.accept_languages.best
if not lang:
lang = 'en_US'
# tranform 2 letters lang like 'en' into 5 letters like 'en_US'
lang = babel.core.LOCALE_ALIASES.get(lang, lang)
# we use _ as seprator where RFC2616 uses '-'
self.lang = lang.replace('-', '_')
def _authenticate(self): def _authenticate(self):
if self.session.uid:
try:
self.session.check_security()
except SessionExpiredException, e:
self.session.logout()
raise SessionExpiredException("Session expired for request %s" % self.httprequest)
if self.auth_method == "none": if self.auth_method == "none":
self.db = None self.db = None
self.uid = None self.uid = None
elif self.auth_method == "admin": elif self.auth_method == "admin":
self.db = self.session._db or db_monodb() self.db = self.session.db or db_monodb()
if not self.db: if not self.db:
raise SessionExpiredException("No valid database for request %s" % self.httprequest) raise SessionExpiredException("No valid database for request %s" % self.httprequest)
self.uid = openerp.SUPERUSER_ID self.uid = openerp.SUPERUSER_ID
else: # auth else: # auth
try: self.db = self.session.db
self.session.check_security() self.uid = self.session.uid
except SessionExpiredException, e:
raise SessionExpiredException("Session expired for request %s" % self.httprequest)
self.db = self.session._db
self.uid = self.session._uid
@property @property
def registry(self): def registry(self):
@ -218,21 +192,18 @@ 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 Decorator marking the decorated method as being a handler for requests. The method must be part of a subclass
of ``Controller``. 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 :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 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/ ). route expression ( http://werkzeug.pocoo.org/docs/routing/ ).
:param type: The type of request, can be ``'http'`` or ``'json'``. :param type: The type of request, can be ``'http'`` or ``'json'``.
:param auth: The type of authentication method, can on of the following: :param auth: The type of authentication method, can on of the following:
* ``auth``: The user must be authenticated. * ``user``: The user must be authenticated and the current request will perform using the rights of the
* ``db``: There is no need for the user to be authenticated but there must be a way to find the current user.
database. * ``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 * ``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 type in ["http", "json"]
assert auth in ["user", "admin", "none"] assert auth in ["user", "admin", "none"]
@ -260,8 +231,7 @@ class JsonRequest(WebRequest):
--> {"jsonrpc": "2.0", --> {"jsonrpc": "2.0",
"method": "call", "method": "call",
"params": {"session_id": "SID", "params": {"context": {},
"context": {},
"arg1": "val1" }, "arg1": "val1" },
"id": null} "id": null}
@ -273,8 +243,7 @@ class JsonRequest(WebRequest):
--> {"jsonrpc": "2.0", --> {"jsonrpc": "2.0",
"method": "call", "method": "call",
"params": {"session_id": "SID", "params": {"context": {},
"context": {},
"arg1": "val1" }, "arg1": "val1" },
"id": null} "id": null}
@ -323,17 +292,17 @@ class JsonRequest(WebRequest):
# Read POST content or POST Form Data named "request" # Read POST content or POST Form Data named "request"
self.jsonrequest = simplejson.loads(request, object_hook=reject_nonliteral) self.jsonrequest = simplejson.loads(request, object_hook=reject_nonliteral)
self.init(self.jsonrequest.get("params", {})) self.params = dict(self.jsonrequest.get("params", {}))
self.context = self.params.pop('context', self.session.context)
def dispatch(self): def dispatch(self):
""" Calls the method asked for by the JSON-RPC2 or JSONP request """ 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: if self.jsonp_handler:
return self.jsonp_handler() return self.jsonp_handler()
response = {"jsonrpc": "2.0" } response = {"jsonrpc": "2.0" }
error = None error = None
try: try:
#if _logger.isEnabledFor(logging.DEBUG): #if _logger.isEnabledFor(logging.DEBUG):
# _logger.debug("--> %s.%s\n%s", func.im_class.__name__, func.__name__, pprint.pformat(self.jsonrequest)) # _logger.debug("--> %s.%s\n%s", func.im_class.__name__, func.__name__, pprint.pformat(self.jsonrequest))
@ -365,7 +334,7 @@ class JsonRequest(WebRequest):
# If we use jsonp, that's mean we are called from another host # If we use jsonp, that's mean we are called from another host
# Some browser (IE and Safari) do no allow third party cookies # Some browser (IE and Safari) do no allow third party cookies
# We need then to manage http sessions manually. # We need then to manage http sessions manually.
response['httpsessionid'] = self.httpsession.sid response['session_id'] = self.session_id
mime = 'application/javascript' mime = 'application/javascript'
body = "%s(%s);" % (self.jsonp, simplejson.dumps(response),) body = "%s(%s);" % (self.jsonp, simplejson.dumps(response),)
else: else:
@ -406,14 +375,10 @@ def to_jsonable(o):
return u"%s" % o return u"%s" % o
def jsonrequest(f): 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 .. deprecated:: 8.0
``$(Controller._cp_path)/$methodname`` combination.
If the method is called, it will be provided with a :class:`JsonRequest` Use the ``route()`` decorator instead.
instance and all ``params`` sent during the JSON-RPC request, apart from
the ``session_id``, ``context`` and ``debug`` keys (which are stripped out
beforehand)
""" """
f.combine = True f.combine = True
base = f.__name__ base = f.__name__
@ -429,9 +394,13 @@ class HttpRequest(WebRequest):
def __init__(self, *args): def __init__(self, *args):
super(HttpRequest, self).__init__(*args) super(HttpRequest, self).__init__(*args)
params = dict(self.httprequest.args) params = dict(self.httprequest.args)
ex = set(["session_id", "debug"])
for k in params.keys():
if k in ex:
del params[k]
params.update(self.httprequest.form) params.update(self.httprequest.form)
params.update(self.httprequest.files) params.update(self.httprequest.files)
self.init(params) self.params = params
def dispatch(self): def dispatch(self):
akw = {} akw = {}
@ -489,14 +458,10 @@ class HttpRequest(WebRequest):
return werkzeug.exceptions.NotFound(description) return werkzeug.exceptions.NotFound(description)
def httprequest(f): 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 .. deprecated:: 8.0
``$(Controller._cp_path)/$methodname`` combination.
If the method is called, it will be provided with a :class:`HttpRequest` Use the ``route()`` decorator instead.
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)
""" """
f.combine = True f.combine = True
base = f.__name__ base = f.__name__
@ -608,8 +573,8 @@ class Model(object):
def proxy(*args, **kw): def proxy(*args, **kw):
# Can't provide any retro-compatibility for this case, so we check it and raise an Exception # Can't provide any retro-compatibility for this case, so we check it and raise an Exception
# to tell the programmer to adapt his code # to tell the programmer to adapt his code
if not request.db or not request.uid or self.session._db != request.db \ if not request.db or not request.uid or self.session.db != request.db \
or self.session._uid != request.uid: or self.session.uid != request.uid:
raise Exception("Trying to use Model with badly configured database or user.") raise Exception("Trying to use Model with badly configured database or user.")
mod = request.registry.get(self.model) mod = request.registry.get(self.model)
@ -626,32 +591,29 @@ class Model(object):
return result return result
return proxy return proxy
class OpenERPSession(object): class OpenERPSession(werkzeug.contrib.sessions.Session):
""" def __init__(self, *args, **kwargs):
An OpenERP RPC session, a given user can own multiple such sessions self.inited = False
in a web session. self.modified = False
super(OpenERPSession, self).__init__(*args, **kwargs)
self.inited = True
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", {})
self.modified = False
.. attribute:: context def __getattr__(self, attr):
return self.get(attr, None)
The session context, a ``dict``. Can be reloaded by calling def __setattr__(self, k, v):
:meth:`openerpweb.openerpweb.OpenERPSession.get_context` if getattr(self, "inited", False):
try:
.. attribute:: domains_store object.__getattribute__(self, k)
except:
A ``dict`` matching domain keys to evaluable (but non-literal) domains. return self.__setitem__(k, v)
object.__setattr__(self, k, v)
Used to store references to non-literal domains which need to be
round-tripped to the client browser.
"""
def __init__(self):
self._creation_time = time.time()
self._db = False
self._uid = False
self._login = False
self._password = False
self._suicide = False
self.context = {}
self.jsonp_requests = {} # FIXME use a LRU
def authenticate(self, db, login=None, password=None, env=None, uid=None): def authenticate(self, db, login=None, password=None, env=None, uid=None):
""" """
@ -660,14 +622,15 @@ class OpenERPSession(object):
:param uid: If not None, that user id will be used instead the login to authenticate the user. :param uid: If not None, that user id will be used instead the login to authenticate the user.
""" """
if uid is None: if uid is None:
uid = openerp.netsvc.dispatch_rpc('common', 'authenticate', [db, login, password, env]) uid = openerp.netsvc.dispatch_rpc('common', 'authenticate', [db, login, password, env])
else: else:
security.check(db, uid, password) security.check(db, uid, password)
self._db = db self.db = db
self._uid = uid self.uid = uid
self._login = login self.login = login
self._password = password self.password = password
request.db = db request.db = db
request.uid = uid request.uid = uid
@ -680,9 +643,13 @@ class OpenERPSession(object):
should be called at each request. If the authentication fails, a ``SessionExpiredException`` should be called at each request. If the authentication fails, a ``SessionExpiredException``
is raised. is raised.
""" """
if not self._db or not self._uid: if not self.db or not self.uid:
raise SessionExpiredException("Session expired") raise SessionExpiredException("Session expired")
security.check(self._db, self._uid, self._password) security.check(self.db, self.uid, self.password)
def logout(self):
for k in self.keys():
del self[k]
def get_context(self): def get_context(self):
""" """
@ -692,9 +659,9 @@ class OpenERPSession(object):
:returns: the new context :returns: the new context
""" """
assert self._uid, "The user needs to be logged-in to initialize his context" assert self.uid, "The user needs to be logged-in to initialize his context"
self.context = request.registry.get('res.users').context_get(request.cr, request.uid) or {} self.context = request.registry.get('res.users').context_get(request.cr, request.uid) or {}
self.context['uid'] = self._uid self.context['uid'] = self.uid
self._fix_lang(self.context) self._fix_lang(self.context)
return self.context return self.context
@ -718,6 +685,35 @@ class OpenERPSession(object):
context['lang'] = lang or 'en_US' context['lang'] = lang or 'en_US'
"""
Damn properties for retro-compatibility. All of that is deprecated, all
of that.
"""
@property
def _db(self):
return self.db
@_db.setter
def _db(self, value):
self.db = value
@property
def _uid(self):
return self.uid
@_uid.setter
def _uid(self, value):
self.uid = value
@property
def _login(self):
return self.login
@_login.setter
def _login(self, value):
self.login = value
@property
def _password(self):
return self.password
@_password.setter
def _password(self, value):
self.password = value
def send(self, service_name, method, *args): def send(self, service_name, method, *args):
""" """
.. deprecated:: 8.0 .. deprecated:: 8.0
@ -739,11 +735,11 @@ class OpenERPSession(object):
Ensures this session is valid (logged into the openerp server) Ensures this session is valid (logged into the openerp server)
""" """
if self._uid and not force: if self.uid and not force:
return return
# TODO use authenticate instead of login # TODO use authenticate instead of login
self._uid = self.proxy("common").login(self._db, self._login, self._password) self.uid = self.proxy("common").login(self.db, self.login, self.password)
if not self._uid: if not self.uid:
raise AuthenticationError("Authentication failure") raise AuthenticationError("Authentication failure")
def ensure_valid(self): def ensure_valid(self):
@ -751,11 +747,11 @@ class OpenERPSession(object):
.. deprecated:: 8.0 .. deprecated:: 8.0
Use ``check_security()`` instead. Use ``check_security()`` instead.
""" """
if self._uid: if self.uid:
try: try:
self.assert_valid(True) self.assert_valid(True)
except Exception: except Exception:
self._uid = None self.uid = None
def execute(self, model, func, *l, **d): def execute(self, model, func, *l, **d):
""" """
@ -772,7 +768,7 @@ class OpenERPSession(object):
Use the resistry and cursor in ``openerp.addons.web.http.request`` instead. Use the resistry and cursor in ``openerp.addons.web.http.request`` instead.
""" """
self.assert_valid() self.assert_valid()
r = self.proxy('object').exec_workflow(self._db, self._uid, self._password, model, signal, id) r = self.proxy('object').exec_workflow(self.db, self.uid, self.password, model, signal, id)
return r return r
def model(self, model): def model(self, model):
@ -786,74 +782,11 @@ class OpenERPSession(object):
:type model: str :type model: str
:rtype: a model object :rtype: a model object
""" """
if self._db == False: if not self.db:
raise SessionExpiredException("Session expired") raise SessionExpiredException("Session expired")
return Model(self, model) return Model(self, model)
#----------------------------------------------------------
# Session context manager
#----------------------------------------------------------
@contextlib.contextmanager
def session_context(httprequest, session_store, session_lock, sid):
with session_lock:
if sid:
httprequest.session = session_store.get(sid)
else:
httprequest.session = session_store.new()
try:
yield httprequest.session
finally:
# Remove all OpenERPSession instances with no uid, they're generated
# either by login process or by HTTP requests without an OpenERP
# session id, and are generally noise
removed_sessions = set()
for key, value in httprequest.session.items():
if not isinstance(value, OpenERPSession):
continue
if getattr(value, '_suicide', False) or (
not value._uid
and not value.jsonp_requests
# FIXME do not use a fixed value
and value._creation_time + (60*5) < time.time()):
_logger.debug('remove session %s', key)
removed_sessions.add(key)
del httprequest.session[key]
with session_lock:
if sid:
# Re-load sessions from storage and merge non-literal
# contexts and domains (they're indexed by hash of the
# content so conflicts should auto-resolve), otherwise if
# two requests alter those concurrently the last to finish
# will overwrite the previous one, leading to loss of data
# (a non-literal is lost even though it was sent to the
# client and client errors)
#
# note that domains_store and contexts_store are append-only (we
# only ever add items to them), so we can just update one with the
# other to get the right result, if we want to merge the
# ``context`` dict we'll need something smarter
in_store = session_store.get(sid)
for k, v in httprequest.session.iteritems():
stored = in_store.get(k)
if stored and isinstance(v, OpenERPSession):
if hasattr(v, 'contexts_store'):
del v.contexts_store
if hasattr(v, 'domains_store'):
del v.domains_store
if not hasattr(v, 'jsonp_requests'):
v.jsonp_requests = {}
v.jsonp_requests.update(getattr(
stored, 'jsonp_requests', {}))
# add missing keys
for k, v in in_store.iteritems():
if k not in httprequest.session and k not in removed_sessions:
httprequest.session[k] = v
session_store.save(httprequest.session)
def session_gc(session_store): def session_gc(session_store):
if random.random() < 0.001: if random.random() < 0.001:
# we keep session one week # we keep session one week
@ -931,8 +864,7 @@ class Root(object):
# Setup http sessions # Setup http sessions
path = session_path() path = session_path()
self.session_store = werkzeug.contrib.sessions.FilesystemSessionStore(path) self.session_store = werkzeug.contrib.sessions.FilesystemSessionStore(path, session_class=OpenERPSession)
self.session_lock = threading.Lock()
_logger.debug('HTTP sessions stored in: %s', path) _logger.debug('HTTP sessions stored in: %s', path)
@ -943,49 +875,60 @@ class Root(object):
def dispatch(self, environ, start_response): def dispatch(self, environ, start_response):
""" """
Performs the actual WSGI dispatching for the application, may be Performs the actual WSGI dispatching for the application.
wrapped during the initialization of the object.
Call the object directly.
""" """
try: try:
httprequest = werkzeug.wrappers.Request(environ) httprequest = werkzeug.wrappers.Request(environ)
httprequest.parameter_storage_class = werkzeug.datastructures.ImmutableDict httprequest.parameter_storage_class = werkzeug.datastructures.ImmutableDict
httprequest.app = self httprequest.app = self
sid = httprequest.cookies.get('sid')
if not sid:
sid = httprequest.args.get('sid')
session_gc(self.session_store) session_gc(self.session_store)
with session_context(httprequest, self.session_store, self.session_lock, sid) as session: sid = httprequest.args.get('session_id')
request = self._build_request(httprequest) explicit_session = True
db = request.db 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)
if db: if not "lang" in httprequest.session.context:
updated = openerp.modules.registry.RegistryManager.check_registry_signaling(db) lang = httprequest.accept_languages.best or "en_US"
if updated: lang = babel.core.LOCALE_ALIASES.get(lang, lang).replace('-', '_')
with self.db_routers_lock: httprequest.session.context["lang"] = lang
del self.db_routers[db]
with set_request(request): request = self._build_request(httprequest)
self.find_handler() db = request.db
result = request.dispatch()
if db: if db:
openerp.modules.registry.RegistryManager.signal_caches_change(db) updated = openerp.modules.registry.RegistryManager.check_registry_signaling(db)
if updated:
with self.db_routers_lock:
del self.db_routers[db]
if isinstance(result, basestring): with set_request(request):
headers=[('Content-Type', 'text/html; charset=utf-8'), ('Content-Length', len(result))] self.find_handler()
response = werkzeug.wrappers.Response(result, headers=headers) result = request.dispatch()
else:
response = result
if hasattr(response, 'set_cookie'): if db:
response.set_cookie('sid', session.sid) openerp.modules.registry.RegistryManager.signal_caches_change(db)
return response(environ, start_response) if isinstance(result, basestring):
headers=[('Content-Type', 'text/html; charset=utf-8'), ('Content-Length', len(result))]
response = werkzeug.wrappers.Response(result, headers=headers)
else:
response = result
if httprequest.session.should_save:
self.session_store.save(httprequest.session)
if not explicit_session and hasattr(response, 'set_cookie'):
response.set_cookie('session_id', httprequest.session.sid)
return response(environ, start_response)
except werkzeug.exceptions.HTTPException, e: except werkzeug.exceptions.HTTPException, e:
return e(environ, start_response) return e(environ, start_response)
@ -1085,12 +1028,7 @@ class Root(object):
def find_handler(self): def find_handler(self):
""" """
Tries to discover the controller handling the request for the path Tries to discover the controller handling the request for the path specified in the request.
specified by the provided parameters
:param path: path to match
:returns: a callable matching the path sections
:rtype: ``Controller | None``
""" """
path = request.httprequest.path path = request.httprequest.path
urls = self.get_db_router(request.db).bind("") urls = self.get_db_router(request.db).bind("")
@ -1106,6 +1044,8 @@ class Root(object):
request.auth_method = getattr(original, "auth", "user") request.auth_method = getattr(original, "auth", "user")
request.func_request_type = original.exposed request.func_request_type = original.exposed
root = None
def db_list(force=False): def db_list(force=False):
proxy = request.session.proxy("db") proxy = request.session.proxy("db")
dbs = proxy.list(force) dbs = proxy.list(force)
@ -1116,19 +1056,18 @@ def db_list(force=False):
return dbs return dbs
def db_redirect(match_first_only_if_unique): def db_redirect(match_first_only_if_unique):
req = request db = None
db = False redirect = None
redirect = False
# 1 try the db in the url # 1 try the db in the url
db_url = req.params.get('db') db_url = request.httprequest.args.get('db')
if db_url: if db_url:
return (db_url, False) return (db_url, None)
dbs = db_list(True) dbs = db_list(True)
# 2 use the database from the cookie if it's listable and still listed # 2 use the database from the cookie if it's listable and still listed
cookie_db = req.httprequest.cookies.get('last_used_database') cookie_db = request.httprequest.cookies.get('last_used_database')
if cookie_db in dbs: if cookie_db in dbs:
db = cookie_db db = cookie_db
@ -1138,24 +1077,40 @@ def db_redirect(match_first_only_if_unique):
# redirect to the chosen db if multiple are available # redirect to the chosen db if multiple are available
if db and len(dbs) > 1: if db and len(dbs) > 1:
query = dict(urlparse.parse_qsl(req.httprequest.query_string, keep_blank_values=True)) query = dict(urlparse.parse_qsl(request.httprequest.query_string, keep_blank_values=True))
query.update({'db': db}) query.update({'db': db})
redirect = req.httprequest.path + '?' + urllib.urlencode(query) redirect = request.httprequest.path + '?' + urllib.urlencode(query)
return (db, redirect) return (db, redirect)
def db_monodb(): def db_monodb():
# if only one db exists, return it else return False """
Magic function to find the current database.
Implementation details:
* Magic
* More magic
Return ``None`` if the magic is not magic enough.
"""
return db_redirect(True)[0] return db_redirect(True)[0]
class JsonRpcController(Controller): class CommonController(Controller):
@route('/jsonrpc', type='json', auth="none") @route('/jsonrpc', type='json', auth="none")
def jsonrpc(self, service, method, args): def jsonrpc(self, service, method, args):
""" Method used by client APIs to contact OpenERP. """ """ Method used by client APIs to contact OpenERP. """
return openerp.netsvc.dispatch_rpc(service, method, args) 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(): 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: # vim:et:ts=4:sw=4:

View File

@ -1599,7 +1599,7 @@ instance.web.EmbeddedClient = instance.web.Client.extend({
if (s.session_is_valid() && s.db === this.dbname && s.login === this.login) { if (s.session_is_valid() && s.db === this.dbname && s.login === this.login) {
return $.when(); return $.when();
} }
return instance.session.session_authenticate(this.dbname, this.login, this.key, true); return instance.session.session_authenticate(this.dbname, this.login, this.key);
}, },
bind_credentials: function(dbname, login, key) { bind_credentials: function(dbname, login, key) {

View File

@ -967,6 +967,8 @@ instance.web.JsonRPC = instance.web.Class.extend(instance.web.PropertiesMixin, {
instance.web.PropertiesMixin.init.call(this); instance.web.PropertiesMixin.init.call(this);
this.server = null; this.server = null;
this.debug = ($.deparam($.param.querystring()).debug != undefined); this.debug = ($.deparam($.param.querystring()).debug != undefined);
this.override_session = false;
this.session_id = undefined;
}, },
setup: function(origin) { setup: function(origin) {
var window_origin = location.protocol+"//"+location.host, self=this; var window_origin = location.protocol+"//"+location.host, self=this;
@ -1000,8 +1002,6 @@ instance.web.JsonRPC = instance.web.Class.extend(instance.web.PropertiesMixin, {
context: this.user_context || {} context: this.user_context || {}
}); });
// Construct a JSON-RPC2 request, method is currently unused // Construct a JSON-RPC2 request, method is currently unused
if (this.debug)
params.debug = 1;
var payload = { var payload = {
jsonrpc: '2.0', jsonrpc: '2.0',
method: 'call', method: 'call',
@ -1059,7 +1059,10 @@ instance.web.JsonRPC = instance.web.Class.extend(instance.web.PropertiesMixin, {
dataType: 'json', dataType: 'json',
contentType: 'application/json', contentType: 'application/json',
data: JSON.stringify(payload), data: JSON.stringify(payload),
processData: false processData: false,
headers: {
"X-Openerp-Session-Id": this.override_session ? this.session_id : undefined,
},
}, url); }, url);
if (this.synch) if (this.synch)
ajax.async = false; ajax.async = false;
@ -1071,13 +1074,12 @@ instance.web.JsonRPC = instance.web.Class.extend(instance.web.PropertiesMixin, {
var data = { var data = {
session_id: this.session_id, session_id: this.session_id,
id: payload.id, id: payload.id,
sid: this.httpsessionid,
}; };
var set_sid = function (response, textStatus, jqXHR) { var set_sid = function (response, textStatus, jqXHR) {
// If response give us the http session id, we store it for next requests... // If response give us the http session id, we store it for next requests...
if (response.httpsessionid) { if (response.session_id) {
self.httpsessionid = response.httpsessionid; self.session_id = response.session_id;
} }
}; };
@ -1093,7 +1095,7 @@ instance.web.JsonRPC = instance.web.Class.extend(instance.web.PropertiesMixin, {
ajax.async = false; ajax.async = false;
var payload_str = JSON.stringify(payload); var payload_str = JSON.stringify(payload);
var payload_url = $.param({r:payload_str}); var payload_url = $.param({r:payload_str});
if(payload_url.length < 2000) { if (payload_url.length < 2000) {
// Direct jsonp request // Direct jsonp request
ajax.data.r = payload_str; ajax.data.r = payload_str;
return $.ajax(ajax).done(set_sid); return $.ajax(ajax).done(set_sid);
@ -1139,14 +1141,10 @@ instance.web.JsonRPC = instance.web.Class.extend(instance.web.PropertiesMixin, {
}, },
url: function(path, params) { url: function(path, params) {
var qs = ''; params = _.extend(params || {});
if (!_.isNull(params)) { if (this.override_session)
params = _.extend(params || {}, {session_id: this.session_id}); params.session_id = this.session_id;
if (this.httpsessionid) { var qs = '?' + $.param(params);
params.sid = this.httpsessionid;
}
qs = '?' + $.param(params);
}
var prefix = _.any(['http://', 'https://', '//'], _.bind(_.str.startsWith, null, path)) ? '' : this.prefix; var prefix = _.any(['http://', 'https://', '//'], _.bind(_.str.startsWith, null, path)) ? '' : this.prefix;
return prefix + path + qs; return prefix + path + qs;
}, },

View File

@ -23,7 +23,6 @@ instance.web.Session = instance.web.JsonRPC.extend( /** @lends instance.web.Sess
this.qweb_mutex = new $.Mutex(); this.qweb_mutex = new $.Mutex();
}, },
rpc: function(url, params, options) { rpc: function(url, params, options) {
params.session_id = this.session_id;
return this._super(url, params, options); return this._super(url, params, options);
}, },
/** /**
@ -39,11 +38,10 @@ instance.web.Session = instance.web.JsonRPC.extend( /** @lends instance.web.Sess
var self = this; var self = this;
this.setup(origin); this.setup(origin);
instance.web.qweb.default_dict['_s'] = this.origin; instance.web.qweb.default_dict['_s'] = this.origin;
this.session_id = false; this.uid = null;
this.uid = false; this.username = null;
this.username = false;
this.user_context= {}; this.user_context= {};
this.db = false; this.db = null;
this.module_list = instance._modules.slice(); this.module_list = instance._modules.slice();
this.module_loaded = {}; this.module_loaded = {};
_(this.module_list).each(function (mod) { _(this.module_list).each(function (mod) {
@ -57,8 +55,6 @@ instance.web.Session = instance.web.JsonRPC.extend( /** @lends instance.web.Sess
*/ */
session_init: function () { session_init: function () {
var self = this; var self = this;
// TODO: session store in cookie should be optional
this.session_id = this.get_cookie('session_id');
return this.session_reload().then(function(result) { return this.session_reload().then(function(result) {
var modules = instance._modules.join(','); var modules = instance._modules.join(',');
var deferred = self.rpc('/web/webclient/qweblist', {mods: modules}).then(self.load_qweb.bind(self)); var deferred = self.rpc('/web/webclient/qweblist', {mods: modules}).then(self.load_qweb.bind(self));
@ -81,7 +77,19 @@ instance.web.Session = instance.web.JsonRPC.extend( /** @lends instance.web.Sess
*/ */
session_reload: function () { session_reload: function () {
var self = this; var self = this;
return this.rpc("/web/session/get_session_info", {}).done(function(result) { var def = $.when();
if (this.override_session) {
if (! this.session_id) {
def = this.rpc("/gen_session_id", {}).then(function(result) {
self.session_id = result;
});
}
} else {
this.session_id = this.get_cookie('session_id');
}
return def.then(function() {
return self.rpc("/web/session/get_session_info", {})
}).then(function(result) {
// If immediately follows a login (triggered by trying to restore // If immediately follows a login (triggered by trying to restore
// an invalid session or no session at all), refresh session data // an invalid session or no session at all), refresh session data
// (should not change, but just in case...) // (should not change, but just in case...)
@ -107,14 +115,10 @@ instance.web.Session = instance.web.JsonRPC.extend( /** @lends instance.web.Sess
return $.Deferred().reject(); return $.Deferred().reject();
} }
_.extend(self, result); _.extend(self, result);
if (!_volatile) {
self.set_cookie('session_id', self.session_id);
}
return self.load_modules(); return self.load_modules();
}); });
}, },
session_logout: function() { session_logout: function() {
this.set_cookie('session_id', '');
$.bbq.removeState(); $.bbq.removeState();
return this.rpc("/web/session/destroy", {}); return this.rpc("/web/session/destroy", {});
}, },
@ -331,7 +335,7 @@ instance.web.Session = instance.web.JsonRPC.extend( /** @lends instance.web.Sess
} }
_(_.extend({}, options.data || {}, _(_.extend({}, options.data || {},
{session_id: this.session_id, token: token})) {session_id: this.override_session ? this.session_id : undefined, token: token}))
.each(function (value, key) { .each(function (value, key) {
var $input = $form.find('[name=' + key +']'); var $input = $form.find('[name=' + key +']');
if (!$input.length) { if (!$input.length) {

View File

@ -654,7 +654,7 @@
<t t-set="fileupload_action" t-translation="off">/web/binary/upload_attachment</t> <t t-set="fileupload_action" t-translation="off">/web/binary/upload_attachment</t>
<input type="hidden" name="model" t-att-value="widget.dataset and widget.dataset.model"/> <input type="hidden" name="model" t-att-value="widget.dataset and widget.dataset.model"/>
<input type="hidden" name="id" t-att-value="widget.model_id"/> <input type="hidden" name="id" t-att-value="widget.model_id"/>
<input type="hidden" name="session_id" t-att-value="widget.session.session_id"/> <input type="hidden" name="session_id" t-att-value="widget.session.session_id" t-if="widget.session.override_session"/>
<span>Add...</span> <span>Add...</span>
</t> </t>
</li> </li>
@ -1320,7 +1320,7 @@
<div t-attf-class="oe_hidden_input_file #{fileupload_class or ''}" t-att-style="fileupload_style"> <div t-attf-class="oe_hidden_input_file #{fileupload_class or ''}" t-att-style="fileupload_style">
<form class="oe_form_binary_form" t-att-target="fileupload_id" <form class="oe_form_binary_form" t-att-target="fileupload_id"
method="post" enctype="multipart/form-data" t-att-action="fileupload_action || '/web/binary/upload'"> method="post" enctype="multipart/form-data" t-att-action="fileupload_action || '/web/binary/upload'">
<input type="hidden" name="session_id" value=""/> <input type="hidden" name="session_id" value="" t-if="widget.session.override_session"/>
<input type="hidden" name="callback" t-att-value="fileupload_id"/> <input type="hidden" name="callback" t-att-value="fileupload_id"/>
<t t-raw="__content__"/> <t t-raw="__content__"/>
<input type="file" class="oe_form_binary_file" name="ufile" t-if="widget.widget!='image'"/> <input type="file" class="oe_form_binary_file" name="ufile" t-if="widget.widget!='image'"/>
@ -1372,7 +1372,7 @@
<t t-set="fileupload_action" t-translation="off">/web/binary/upload_attachment</t> <t t-set="fileupload_action" t-translation="off">/web/binary/upload_attachment</t>
<input type="hidden" name="model" t-att-value="widget.view.model"/> <input type="hidden" name="model" t-att-value="widget.view.model"/>
<input type="hidden" name="id" value="0"/> <input type="hidden" name="id" value="0"/>
<input type="hidden" name="session_id" t-att-value="widget.session.session_id"/> <input type="hidden" name="session_id" t-att-value="widget.session.session_id" t-if="widget.session.override_session"/>
</t> </t>
</div> </div>
</div> </div>
@ -1835,7 +1835,7 @@
<t t-name="ImportDataView"> <t t-name="ImportDataView">
<form name="import_data" id="import_data" action="" method="post" enctype="multipart/form-data" <form name="import_data" id="import_data" action="" method="post" enctype="multipart/form-data"
class="oe_import oe_import_no_result"> class="oe_import oe_import_no_result">
<input type="hidden" name="session_id" t-att-value="widget.session.session_id"/> <input type="hidden" name="session_id" t-att-value="widget.session.session_id" t-if="widget.session.override_session"/>
<h2 class="separator horizontal">1. Import a .CSV file</h2> <h2 class="separator horizontal">1. Import a .CSV file</h2>
<p>Select a .CSV file to import. If you need a sample of file to import, <p>Select a .CSV file to import. If you need a sample of file to import,
you should use the export tool with the "Import Compatible" option. you should use the export tool with the "Import Compatible" option.