[REF] core: HTTPocalypse (12) web ir.http & login
This commit is the 12th commit of a comprehensive refactor of our HTTP framework. See odoo/odoo#78857 for complete historic, discussions and rationnals. The web module is twofold, on one side there are many controllers: /, /web, /web/login, /web/database/selector, /web/dataset/call_kw, etc, on the other side there is `session_info`: the method responsible to create the web client's environ. This module is kinda an exception as it is (with base) a server wide module. In the case of the HTTP framework, it means that the controllers of web are always accessible, i.e. going to / or /web/login will never return a 404 Not Found even if the user is not connected to a database. This is both a blessing and a curse. It is a blessing because the controllers are always accessible it means that a new users can freely access those routes. It is a curse because *any* user can access them, even user who don't have a session yet thus who are not connected to a database yet. From a developer standpoint, we have to put extra care to correct serve users with and without a database. An example is the /web/login route, the login/password pair is stored in a database, without database it is impossible to validate a user login but users can still access this route without db. To solve this problem, there is the `ensure_db` function. This function attempts to find a database using various sources (?db= query-string, session db, mono db) and to save it on the user session. In case no db is found, the user is redirected to the database selector. In a way, this function grants a database to the user in a seamingly experience. In a way, this function brings a welcome differentiation between `auth='none'` with a database and `auth='none'` without a database. Such differentiation only matters for the server wide modules as "regular" module controllers are only accessible via the ir.http routing map, i.e. it is not possible to declare a nodb controller outside of server wide modules. An important changement is the `session.authenticate` method, before it was possible to call the method when the cursor was not yet initialized, authenticate would open a cursor against the given database, setup a registry and an environment and ultimately save everything on the current request. Because the cursor is now greedily created, it is no more possible to update the request environment when authenticating on another database. PR: odoo#78857 Task: 2571224
This commit is contained in:
@@ -16,7 +16,7 @@ from odoo.http import request
|
||||
from odoo import registry as registry_get
|
||||
|
||||
from odoo.addons.auth_signup.controllers.main import AuthSignupHome as Home
|
||||
from odoo.addons.web.controllers.main import db_monodb, ensure_db, set_cookie_and_redirect, login_and_redirect
|
||||
from odoo.addons.web.controllers.main import ensure_db, _get_login_redirect_url
|
||||
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
@@ -130,7 +130,7 @@ class OAuthController(http.Controller):
|
||||
with registry.cursor() as cr:
|
||||
try:
|
||||
env = api.Environment(cr, SUPERUSER_ID, context)
|
||||
credentials = env['res.users'].sudo().auth_oauth(provider, kw)
|
||||
db, login, key = env['res.users'].sudo().auth_oauth(provider, kw)
|
||||
cr.commit()
|
||||
action = state.get('a')
|
||||
menu = state.get('m')
|
||||
@@ -142,7 +142,11 @@ class OAuthController(http.Controller):
|
||||
url = '/web#action=%s' % action
|
||||
elif menu:
|
||||
url = '/web#menu_id=%s' % menu
|
||||
resp = login_and_redirect(*credentials, redirect_url=url)
|
||||
|
||||
pre_uid = request.session.authenticate(db, login, key)
|
||||
resp = request.redirect(_get_login_redirect_url(pre_uid, url), 303)
|
||||
resp.autocorrect_location_header = False
|
||||
|
||||
# Since /web is hardcoded, verify user has right to land on it
|
||||
if werkzeug.urls.url_parse(resp.location).path == '/web' and not request.env.user.has_group('base.group_user'):
|
||||
resp.location = '/'
|
||||
@@ -163,18 +167,20 @@ class OAuthController(http.Controller):
|
||||
_logger.exception("OAuth2: %s" % str(e))
|
||||
url = "/web/login?oauth_error=2"
|
||||
|
||||
return set_cookie_and_redirect(url)
|
||||
redirect = request.redirect(url, 303)
|
||||
redirect.autocorrect_location_header = False
|
||||
return redirect
|
||||
|
||||
@http.route('/auth_oauth/oea', type='http', auth='none')
|
||||
def oea(self, **kw):
|
||||
"""login user via Odoo Account provider"""
|
||||
dbname = kw.pop('db', None)
|
||||
if not dbname:
|
||||
dbname = db_monodb()
|
||||
dbname = request.db
|
||||
if not dbname:
|
||||
return BadRequest()
|
||||
raise BadRequest()
|
||||
if not http.db_filter([dbname]):
|
||||
return BadRequest()
|
||||
raise BadRequest()
|
||||
|
||||
registry = registry_get(dbname)
|
||||
with registry.cursor() as cr:
|
||||
@@ -182,7 +188,9 @@ class OAuthController(http.Controller):
|
||||
env = api.Environment(cr, SUPERUSER_ID, {})
|
||||
provider = env.ref('auth_oauth.provider_openerp')
|
||||
except ValueError:
|
||||
return set_cookie_and_redirect('/web?db=%s' % dbname)
|
||||
redirect = request.redirect(f'/web?db={dbname}', 303)
|
||||
redirect.autocorrect_location_header = False
|
||||
return redirect
|
||||
assert provider._name == 'auth.oauth.provider'
|
||||
|
||||
state = {
|
||||
|
||||
@@ -80,7 +80,7 @@ class ResUsers(models.Model):
|
||||
token = state.get('t')
|
||||
values = self._generate_signup_values(provider, validation, params)
|
||||
try:
|
||||
_, login, _ = self.signup(values, token)
|
||||
login, _ = self.signup(values, token)
|
||||
return login
|
||||
except (SignupError, UserError):
|
||||
raise access_denied_exception
|
||||
|
||||
@@ -18,7 +18,7 @@ class AuthSignupHome(Home):
|
||||
@http.route()
|
||||
def web_login(self, *args, **kw):
|
||||
ensure_db()
|
||||
response = super(AuthSignupHome, self).web_login(*args, **kw)
|
||||
response = super().web_login(*args, **kw)
|
||||
response.qcontext.update(self.get_auth_signup_config())
|
||||
if request.httprequest.method == 'GET' and request.session.uid and request.params.get('redirect'):
|
||||
# Redirect if already logged in and redirect param is present
|
||||
@@ -135,10 +135,10 @@ class AuthSignupHome(Home):
|
||||
request.env.cr.commit()
|
||||
|
||||
def _signup_with_values(self, token, values):
|
||||
db, login, password = request.env['res.users'].sudo().signup(values, token)
|
||||
login, password = request.env['res.users'].sudo().signup(values, token)
|
||||
request.env.cr.commit() # as authenticate will use its own cursor we need to commit the current transaction
|
||||
uid = request.session.authenticate(db, login, password)
|
||||
if not uid:
|
||||
pre_uid = request.session.authenticate(request.db, login, password)
|
||||
if not pre_uid:
|
||||
raise SignupError(_('Authentication Failed.'))
|
||||
|
||||
class AuthBaseSetup(BaseSetup):
|
||||
|
||||
@@ -9,11 +9,11 @@ class Http(models.AbstractModel):
|
||||
_inherit = 'ir.http'
|
||||
|
||||
@classmethod
|
||||
def _dispatch(cls):
|
||||
# add signup token or login to the session if given
|
||||
if 'auth_signup_token' in request.params:
|
||||
request.session['auth_signup_token'] = request.params['auth_signup_token']
|
||||
if 'auth_login' in request.params:
|
||||
request.session['auth_login'] = request.params['auth_login']
|
||||
def _pre_dispatch(cls, rule, args):
|
||||
super()._pre_dispatch(rule, args)
|
||||
|
||||
return super(Http, cls)._dispatch()
|
||||
# add signup token or login to the session if given
|
||||
for key in ('auth_signup_token', 'auth_login'):
|
||||
val = request.httprequest.args.get(key)
|
||||
if val is not None:
|
||||
request.session[key] = val
|
||||
|
||||
@@ -84,7 +84,7 @@ class ResUsers(models.Model):
|
||||
partner_user.write(values)
|
||||
if not partner_user.login_date:
|
||||
partner_user._notify_inviter()
|
||||
return (self.env.cr.dbname, partner_user.login, values.get('password'))
|
||||
return (partner_user.login, values.get('password'))
|
||||
else:
|
||||
# user does not exist: sign up invited user
|
||||
values.update({
|
||||
@@ -102,7 +102,7 @@ class ResUsers(models.Model):
|
||||
values['email'] = values.get('email') or values.get('login')
|
||||
self._signup_create_user(values)
|
||||
|
||||
return (self.env.cr.dbname, values.get('login'), values.get('password'))
|
||||
return (values.get('login'), values.get('password'))
|
||||
|
||||
@api.model
|
||||
def _get_signup_invitation_scope(self):
|
||||
|
||||
@@ -32,7 +32,7 @@ class Home(odoo.addons.web.controllers.main.Home):
|
||||
if key:
|
||||
checked_credentials = request.env['auth_totp.device']._check_credentials(scope="browser", key=key)
|
||||
if checked_credentials == user.id:
|
||||
request.session.finalize()
|
||||
request.session.finalize(request.env)
|
||||
return request.redirect(self._login_redirect(request.session.uid, redirect=redirect))
|
||||
|
||||
elif user and request.httprequest.method == 'POST' and kwargs.get('totp_token'):
|
||||
@@ -44,7 +44,9 @@ class Home(odoo.addons.web.controllers.main.Home):
|
||||
except ValueError:
|
||||
error = _("Invalid authentication code format.")
|
||||
else:
|
||||
request.session.finalize()
|
||||
request.session.finalize(request.env)
|
||||
request.update_env(user=request.session.uid)
|
||||
request.update_context(**request.session.context)
|
||||
response = request.redirect(self._login_redirect(request.session.uid, redirect=redirect))
|
||||
if kwargs.get('remember'):
|
||||
name = _("%(browser)s on %(platform)s",
|
||||
|
||||
@@ -8,6 +8,7 @@ from odoo import http
|
||||
from odoo.exceptions import AccessDenied
|
||||
from odoo.service import common as auth, model
|
||||
from odoo.tests import tagged, HttpCase, get_db_name
|
||||
from odoo.tools import mute_logger
|
||||
|
||||
from ..controllers.home import Home
|
||||
|
||||
@@ -86,10 +87,11 @@ class TestTOTP(HttpCase):
|
||||
self.start_tour('/web', 'totp_admin_disables', login='admin')
|
||||
self.start_tour('/', 'totp_login_disabled', login=None)
|
||||
|
||||
@mute_logger('odoo.http')
|
||||
def test_totp_authenticate(self):
|
||||
"""
|
||||
Ensure that JSON-RPC authentication works and don't return the user id
|
||||
without TOTP check
|
||||
Ensure we don't leak the session info from an half-logged-in
|
||||
user.
|
||||
"""
|
||||
|
||||
self.start_tour('/web', 'totp_tour_setup', login='demo')
|
||||
@@ -112,4 +114,4 @@ class TestTOTP(HttpCase):
|
||||
}
|
||||
response = self.url_open("/web/session/authenticate", data=json.dumps(payload), headers=headers)
|
||||
data = response.json()
|
||||
self.assertEqual(data['result']['uid'], None)
|
||||
self.assertEqual(data['error']['data']['message'], "Reniewing an expired session for user that has multi-factor-authentication is not supported. Please use /web/login instead.")
|
||||
|
||||
@@ -5,33 +5,19 @@ from odoo import _
|
||||
from odoo.exceptions import AccessError
|
||||
from odoo.http import Controller, route, request, Response
|
||||
|
||||
def webservice(f):
|
||||
@functools.wraps(f)
|
||||
def wrap(*args, **kw):
|
||||
try:
|
||||
return f(*args, **kw)
|
||||
except Exception as e:
|
||||
return Response(response=str(e), status=500)
|
||||
return wrap
|
||||
|
||||
|
||||
class ImportModule(Controller):
|
||||
|
||||
def check_user(self, uid=None):
|
||||
if uid is None:
|
||||
uid = request.uid
|
||||
is_admin = request.env['res.users'].browse(uid)._is_admin()
|
||||
if not is_admin:
|
||||
raise AccessError(_("Only administrators can upload a module"))
|
||||
|
||||
@route(
|
||||
'/base_import_module/login_upload',
|
||||
type='http', auth='none', methods=['POST'], csrf=False, save_session=False)
|
||||
@webservice
|
||||
def login_upload(self, login, password, db=None, force='', mod_file=None, **kw):
|
||||
if db and db != request.db:
|
||||
raise Exception(_("Could not select database '%s'", db))
|
||||
uid = request.session.authenticate(request.db, login, password)
|
||||
self.check_user(uid)
|
||||
force = True if force == '1' else False
|
||||
return request.env['ir.module.module'].import_zipfile(mod_file, force=force)[0]
|
||||
def login_upload(self, login, password, force='', mod_file=None, **kw):
|
||||
try:
|
||||
if not request.db:
|
||||
raise Exception(_("Could not select database '%s'", request.db))
|
||||
request.session.authenticate(request.db, login, password)
|
||||
# request.uid is None in case of MFA
|
||||
if request.uid and request.env.user._is_admin():
|
||||
return request.env['ir.module.module'].import_zipfile(mod_file, force=force == '1')[0]
|
||||
raise AccessError(_("Only administrators can upload a module"))
|
||||
except Exception as e:
|
||||
return Response(response=str(e), status=500)
|
||||
|
||||
+68
-132
@@ -1,4 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import base64
|
||||
@@ -30,19 +29,21 @@ from werkzeug.urls import url_encode, url_parse, iri_to_uri
|
||||
|
||||
import odoo
|
||||
import odoo.modules.registry
|
||||
from odoo import http
|
||||
from odoo.api import call_kw
|
||||
from odoo.addons.base.models.ir_qweb import render as qweb_render
|
||||
from odoo.modules import get_resource_path, module, get_manifest
|
||||
from odoo.exceptions import AccessError, UserError, AccessDenied
|
||||
from odoo.http import content_disposition, request
|
||||
from odoo.models import check_method_name
|
||||
from odoo.service import db, dispatch_rpc, security
|
||||
from odoo.tools import html_escape, pycompat, ustr, apply_inheritance_specs, lazy_property, float_repr, osutil
|
||||
from odoo.tools.mimetypes import guess_mimetype
|
||||
from odoo.tools.translate import _
|
||||
from odoo.tools.misc import str2bool, xlsxwriter, file_open, file_path
|
||||
from odoo.tools.safe_eval import safe_eval, time
|
||||
from odoo import http
|
||||
from odoo.http import content_disposition, dispatch_rpc, request, serialize_exception as _serialize_exception
|
||||
from odoo.exceptions import AccessError, UserError, AccessDenied
|
||||
from odoo.models import check_method_name
|
||||
from odoo.service import db, security
|
||||
from odoo.tools.translate import _
|
||||
|
||||
from odoo.addons.base.models.ir_qweb import render as qweb_render
|
||||
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -86,10 +87,6 @@ OPERATOR_MAPPING = {
|
||||
# Odoo Web helpers
|
||||
#----------------------------------------------------------
|
||||
|
||||
db_list = http.db_list
|
||||
|
||||
db_monodb = http.db_monodb
|
||||
|
||||
def clean(name): return name.replace('\x3c', '')
|
||||
def serialize_exception(f):
|
||||
@functools.wraps(f)
|
||||
@@ -98,7 +95,7 @@ def serialize_exception(f):
|
||||
return f(*args, **kwargs)
|
||||
except Exception as e:
|
||||
_logger.exception("An exception occurred during an http request")
|
||||
se = _serialize_exception(e)
|
||||
se = http.serialize_exception(e)
|
||||
error = {
|
||||
'code': 200,
|
||||
'message': "Odoo Server Error",
|
||||
@@ -107,11 +104,6 @@ def serialize_exception(f):
|
||||
return werkzeug.exceptions.InternalServerError(json.dumps(error))
|
||||
return wrap
|
||||
|
||||
def abort_and_redirect(url):
|
||||
response = request.redirect(url, 302)
|
||||
response = http.root.get_response(request.httprequest, response, explicit_session=False)
|
||||
werkzeug.exceptions.abort(response)
|
||||
|
||||
def ensure_db(redirect='/web/database/selector'):
|
||||
# This helper should be used in web client auth="none" routes
|
||||
# if those routes needs a db to work with.
|
||||
@@ -140,7 +132,7 @@ def ensure_db(redirect='/web/database/selector'):
|
||||
query_string = iri_to_uri(r.query_string)
|
||||
url_redirect = url_redirect.replace(query=query_string)
|
||||
request.session.db = db
|
||||
abort_and_redirect(url_redirect.to_url())
|
||||
werkzeug.exceptions.abort(request.redirect(url_redirect.to_url(), 302))
|
||||
|
||||
# if db not provided, use the session one
|
||||
if not db and request.session.db and http.db_filter([request.session.db]):
|
||||
@@ -148,7 +140,9 @@ def ensure_db(redirect='/web/database/selector'):
|
||||
|
||||
# if no database provided and no database in session, use monodb
|
||||
if not db:
|
||||
db = db_monodb(request.httprequest)
|
||||
all_dbs = http.db_list(force=True)
|
||||
if len(all_dbs) == 1:
|
||||
db = all_dbs[0]
|
||||
|
||||
# if no db can be found til here, send to the database selector
|
||||
# the database selector will redirect to database manager if needed
|
||||
@@ -157,10 +151,10 @@ def ensure_db(redirect='/web/database/selector'):
|
||||
|
||||
# always switch the session to the computed db
|
||||
if db != request.session.db:
|
||||
request.session.logout()
|
||||
abort_and_redirect(request.httprequest.url)
|
||||
|
||||
request.session.db = db
|
||||
request.session = http.root.session_store.new()
|
||||
request.session.update(http.DEFAULT_SESSION, db=db)
|
||||
request.session.context['lang'] = request.default_lang()
|
||||
werkzeug.exceptions.abort(request.redirect(request.httprequest.url, 302))
|
||||
|
||||
def fs2web(path):
|
||||
"""convert FS path into web path"""
|
||||
@@ -219,16 +213,6 @@ def _get_login_redirect_url(uid, redirect=None):
|
||||
qs['redirect'] = redirect
|
||||
return parsed.replace(query=werkzeug.urls.url_encode(qs)).to_url()
|
||||
|
||||
def login_and_redirect(db, login, key, redirect_url='/web'):
|
||||
uid = request.session.authenticate(db, login, key)
|
||||
redirect_url = _get_login_redirect_url(uid, redirect_url)
|
||||
return set_cookie_and_redirect(redirect_url)
|
||||
|
||||
def set_cookie_and_redirect(redirect_url):
|
||||
redirect = request.redirect(redirect_url, 303)
|
||||
redirect.autocorrect_location_header = False
|
||||
return redirect
|
||||
|
||||
def clean_action(action, env):
|
||||
action_type = action.setdefault('type', 'ir.actions.act_window_close')
|
||||
if action_type == 'ir.actions.act_window':
|
||||
@@ -820,13 +804,21 @@ class Home(http.Controller):
|
||||
# ideally, this route should be `auth="user"` but that don't work in non-monodb mode.
|
||||
@http.route('/web', type='http', auth="none")
|
||||
def web_client(self, s_action=None, **kw):
|
||||
|
||||
# Ensure we have both a database and a user
|
||||
ensure_db()
|
||||
if not request.session.uid:
|
||||
return request.redirect('/web/login', 303)
|
||||
if kw.get('redirect'):
|
||||
return request.redirect(kw.get('redirect'), 303)
|
||||
if not security.check_session(request.session, request.env):
|
||||
raise http.SessionExpiredException("Session expired")
|
||||
|
||||
request.uid = request.session.uid
|
||||
# Side-effect, refresh the session lifetime
|
||||
request.session.should_touch = True
|
||||
|
||||
# Restore the user on the environment, it was lost due to auth="none"
|
||||
request.update_env(user=request.session.uid)
|
||||
try:
|
||||
context = request.env['ir.http'].webclient_rendering_context()
|
||||
response = request.render('web.webclient_bootstrap', qcontext=context)
|
||||
@@ -862,8 +854,9 @@ class Home(http.Controller):
|
||||
if request.httprequest.method == 'GET' and redirect and request.session.uid:
|
||||
return request.redirect(redirect)
|
||||
|
||||
# so it is correct if overloaded with auth="public"
|
||||
if not request.uid:
|
||||
request.uid = odoo.SUPERUSER_ID
|
||||
request.update_env(user=odoo.SUPERUSER_ID)
|
||||
|
||||
values = {k: v for k, v in request.params.items() if k in SIGN_UP_REQUEST_PARAMS}
|
||||
try:
|
||||
@@ -872,13 +865,11 @@ class Home(http.Controller):
|
||||
values['databases'] = None
|
||||
|
||||
if request.httprequest.method == 'POST':
|
||||
old_uid = request.uid
|
||||
try:
|
||||
uid = request.session.authenticate(request.session.db, request.params['login'], request.params['password'])
|
||||
uid = request.session.authenticate(request.db, request.params['login'], request.params['password'])
|
||||
request.params['login_success'] = True
|
||||
return request.redirect(self._login_redirect(uid, redirect=redirect))
|
||||
except odoo.exceptions.AccessDenied as e:
|
||||
request.uid = old_uid
|
||||
if e.args == odoo.exceptions.AccessDenied().args:
|
||||
values['error'] = _("Wrong login/password")
|
||||
else:
|
||||
@@ -944,10 +935,8 @@ class WebClient(http.Controller):
|
||||
|
||||
@http.route('/web/webclient/qweb/<string:unique>', type='http', auth="none", cors="*")
|
||||
def qweb(self, unique, mods=None, db=None, bundle=None):
|
||||
|
||||
if not request.db and mods is None:
|
||||
mods = odoo.conf.server_wide_modules or []
|
||||
|
||||
content = HomeStaticTemplateHelpers.get_qweb_templates(mods, db, debug=request.session.debug, bundle=bundle)
|
||||
|
||||
return request.make_response(content, [
|
||||
@@ -964,14 +953,12 @@ class WebClient(http.Controller):
|
||||
# For performance reasons we only load a single translation, so for
|
||||
# sub-languages (that should only be partially translated) we load the
|
||||
# main language PO instead - that should be enough for the login screen.
|
||||
context = dict(request.context)
|
||||
request.session._fix_lang(context)
|
||||
lang = context['lang'].split('_')[0]
|
||||
lang = request.env.context['lang'].partition('_')[0]
|
||||
|
||||
if mods is None:
|
||||
mods = odoo.conf.server_wide_modules or []
|
||||
if request.db:
|
||||
mods = request.env.registry._init_modules | set(mods)
|
||||
mods = request.env.registry._init_modules.union(mods)
|
||||
|
||||
translations_per_module = {}
|
||||
for addon_name in mods:
|
||||
@@ -995,8 +982,6 @@ class WebClient(http.Controller):
|
||||
:param lang: the language of the user
|
||||
:return:
|
||||
"""
|
||||
request.disable_db = False
|
||||
|
||||
if mods:
|
||||
mods = mods.split(',')
|
||||
elif mods is None:
|
||||
@@ -1047,23 +1032,6 @@ class WebClient(http.Controller):
|
||||
return request.make_response(data, [('Content-Type', 'application/json')])
|
||||
|
||||
|
||||
class Proxy(http.Controller):
|
||||
|
||||
@http.route('/web/proxy/post/<path:path>', type='http', auth='user', methods=['GET'])
|
||||
def post(self, path):
|
||||
"""Effectively execute a POST request that was hooked through user login"""
|
||||
with request.session.load_request_data() as data:
|
||||
if not data:
|
||||
raise werkzeug.exceptions.BadRequest()
|
||||
from werkzeug.test import Client
|
||||
from werkzeug.wrappers import BaseResponse
|
||||
base_url = request.httprequest.base_url
|
||||
query_string = request.httprequest.query_string
|
||||
client = Client(http.root, BaseResponse)
|
||||
headers = {'X-Openerp-Session-Id': request.session.sid}
|
||||
return client.post('/' + path, base_url=base_url, query_string=query_string,
|
||||
headers=headers, data=data)
|
||||
|
||||
class Database(http.Controller):
|
||||
|
||||
def _render_template(self, **d):
|
||||
@@ -1074,14 +1042,11 @@ class Database(http.Controller):
|
||||
d['countries'] = odoo.service.db.exp_list_countries()
|
||||
d['pattern'] = DBNAME_PATTERN
|
||||
# databases list
|
||||
d['databases'] = []
|
||||
try:
|
||||
d['databases'] = http.db_list()
|
||||
d['incompatible_databases'] = odoo.service.db.list_db_incompatible(d['databases'])
|
||||
except odoo.exceptions.AccessDenied:
|
||||
monodb = db_monodb()
|
||||
if monodb:
|
||||
d['databases'] = [monodb]
|
||||
d['databases'] = [request.db] if request.db else []
|
||||
|
||||
templates = {}
|
||||
|
||||
@@ -1100,12 +1065,14 @@ class Database(http.Controller):
|
||||
|
||||
@http.route('/web/database/selector', type='http', auth="none")
|
||||
def selector(self, **kw):
|
||||
request._cr = None
|
||||
if request.db:
|
||||
request.env.cr.close()
|
||||
return self._render_template(manage=False)
|
||||
|
||||
@http.route('/web/database/manager', type='http', auth="none")
|
||||
def manager(self, **kw):
|
||||
request._cr = None
|
||||
if request.db:
|
||||
request.env.cr.close()
|
||||
return self._render_template()
|
||||
|
||||
@http.route('/web/database/create', type='http', auth="none", methods=['POST'], csrf=False)
|
||||
@@ -1120,8 +1087,10 @@ class Database(http.Controller):
|
||||
country_code = post.get('country_code') or False
|
||||
dispatch_rpc('db', 'create_database', [master_pwd, name, bool(post.get('demo')), lang, password, post['login'], country_code, post['phone']])
|
||||
request.session.authenticate(name, post['login'], password)
|
||||
request.session.db = name
|
||||
return request.redirect('/web')
|
||||
except Exception as e:
|
||||
_logger.exception("Database creation error.")
|
||||
error = "Database creation error: %s" % (str(e) or repr(e))
|
||||
return self._render_template(error=error)
|
||||
|
||||
@@ -1134,9 +1103,11 @@ class Database(http.Controller):
|
||||
if not re.match(DBNAME_PATTERN, new_name):
|
||||
raise Exception(_('Invalid database name. Only alphanumerical characters, underscore, hyphen and dot are allowed.'))
|
||||
dispatch_rpc('db', 'duplicate_database', [master_pwd, name, new_name])
|
||||
request._cr = None # duplicating a database leads to an unusable cursor
|
||||
if request.db == name:
|
||||
request.env.cr.close() # duplicating a database leads to an unusable cursor
|
||||
return request.redirect('/web/database/manager')
|
||||
except Exception as e:
|
||||
_logger.exception("Database duplication error.")
|
||||
error = "Database duplication error: %s" % (str(e) or repr(e))
|
||||
return self._render_template(error=error)
|
||||
|
||||
@@ -1146,16 +1117,14 @@ class Database(http.Controller):
|
||||
if insecure and master_pwd:
|
||||
dispatch_rpc('db', 'change_admin_password', ["admin", master_pwd])
|
||||
try:
|
||||
dispatch_rpc('db','drop', [master_pwd, name])
|
||||
request._cr = None # dropping a database leads to an unusable cursor
|
||||
# if the user is connected to the dropped database, redirect will raise an operational error
|
||||
# trying to access the registry of this database.
|
||||
# Removing db from this request will prevent an invalid error message. (logout looks like a good idea in this case)
|
||||
# https://github.com/odoo/odoo/pull/54102 is proposing a complete fix for this issue.
|
||||
dispatch_rpc('db', 'drop', [master_pwd, name])
|
||||
if request.db == name:
|
||||
request.env.cr._closed = True # the underlying connection was closed
|
||||
if request.session.db == name:
|
||||
request.session.logout()
|
||||
return request.redirect('/web/database/manager')
|
||||
except Exception as e:
|
||||
_logger.exception("Database deletion error.")
|
||||
error = "Database deletion error: %s" % (str(e) or repr(e))
|
||||
return self._render_template(error=error)
|
||||
|
||||
@@ -1219,17 +1188,23 @@ class Database(http.Controller):
|
||||
|
||||
class Session(http.Controller):
|
||||
|
||||
@http.route('/web/session/get_session_info', type='json', auth="none")
|
||||
@http.route('/web/session/get_session_info', type='json', auth="user")
|
||||
def get_session_info(self):
|
||||
request.session.check_security()
|
||||
request.uid = request.session.uid
|
||||
request.disable_db = False
|
||||
return request.env['ir.http'].session_info()
|
||||
|
||||
@http.route('/web/session/authenticate', type='json', auth="none")
|
||||
def authenticate(self, db, login, password, base_location=None):
|
||||
request.session.authenticate(db, login, password)
|
||||
return request.env['ir.http'].session_info()
|
||||
if not http.db_filter([db]):
|
||||
raise AccessError("Database not found.")
|
||||
pre_uid = request.session.authenticate(db, login, password)
|
||||
if pre_uid != request.session.uid:
|
||||
raise AccessError("Reniewing an expired session for user that has multi-factor-authentication is not supported. Please use /web/login instead.")
|
||||
|
||||
request.session.db = db
|
||||
registry = odoo.modules.registry.Registry(db)
|
||||
with registry.cursor() as cr:
|
||||
env = odoo.api.Environment(cr, request.session.uid, request.session.context)
|
||||
return env['ir.http'].session_info()
|
||||
|
||||
@http.route('/web/session/change_password', type='json', auth="user")
|
||||
def change_password(self, fields):
|
||||
@@ -1262,39 +1237,11 @@ class Session(http.Controller):
|
||||
@http.route('/web/session/modules', type='json', auth="user")
|
||||
def modules(self):
|
||||
# return all installed modules. Web client is smart enough to not load a module twice
|
||||
return list(request.env.registry._init_modules | set([module.current_test] if module.current_test else []))
|
||||
|
||||
@http.route('/web/session/save_session_action', type='json', auth="user")
|
||||
def save_session_action(self, the_action):
|
||||
"""
|
||||
This method store an action object in the session object and returns an integer
|
||||
identifying that action. The method get_session_action() can be used to get
|
||||
back the action.
|
||||
|
||||
:param the_action: The action to save in the session.
|
||||
:type the_action: anything
|
||||
:return: A key identifying the saved action.
|
||||
:rtype: integer
|
||||
"""
|
||||
return request.session.save_action(the_action)
|
||||
|
||||
@http.route('/web/session/get_session_action', type='json', auth="user")
|
||||
def get_session_action(self, key):
|
||||
"""
|
||||
Gets back a previously saved action. This method can return None if the action
|
||||
was saved since too much time (this case should be handled in a smart way).
|
||||
|
||||
:param key: The key given by save_session_action()
|
||||
:type key: integer
|
||||
:return: The saved action or None.
|
||||
:rtype: anything
|
||||
"""
|
||||
return request.session.get_action(key)
|
||||
return list(request.env.registry._init_modules.union([module.current_test] if module.current_test else []))
|
||||
|
||||
@http.route('/web/session/check', type='json', auth="user")
|
||||
def check(self):
|
||||
request.session.check_security()
|
||||
return None
|
||||
return # ir.http@_authenticate does the job
|
||||
|
||||
@http.route('/web/session/account', type='json', auth="user")
|
||||
def account(self):
|
||||
@@ -1316,7 +1263,6 @@ class Session(http.Controller):
|
||||
request.session.logout(keep_db=True)
|
||||
return request.redirect(redirect, 303)
|
||||
|
||||
|
||||
class DataSet(http.Controller):
|
||||
|
||||
@http.route('/web/dataset/search_read', type='json', auth="user")
|
||||
@@ -1538,18 +1484,11 @@ class Binary(http.Controller):
|
||||
imgname = 'logo'
|
||||
imgext = '.png'
|
||||
placeholder = functools.partial(get_resource_path, 'web', 'static', 'img')
|
||||
uid = None
|
||||
if request.session.db:
|
||||
dbname = request.session.db
|
||||
uid = request.session.uid
|
||||
elif dbname is None:
|
||||
dbname = db_monodb()
|
||||
|
||||
if not uid:
|
||||
uid = odoo.SUPERUSER_ID
|
||||
dbname = request.db
|
||||
uid = (request.session.uid if dbname else None) or odoo.SUPERUSER_ID
|
||||
|
||||
if not dbname:
|
||||
response = http.send_file(placeholder(imgname + imgext))
|
||||
response = http.send_filepath(placeholder(imgname + imgext))
|
||||
else:
|
||||
try:
|
||||
# create an empty registry
|
||||
@@ -1578,7 +1517,7 @@ class Binary(http.Controller):
|
||||
imgext = '.svg'
|
||||
response = http.send_file(image_data, filename=imgname + imgext, mimetype=mimetype, mtime=row[1])
|
||||
else:
|
||||
response = http.send_file(placeholder('nologo.png'))
|
||||
response = http.send_filepath(placeholder('nologo.png'))
|
||||
except Exception:
|
||||
response = http.send_file(placeholder(imgname + imgext))
|
||||
|
||||
@@ -1628,13 +1567,11 @@ class Action(http.Controller):
|
||||
|
||||
base_action = Actions.browse([action_id]).sudo().read(['type'])
|
||||
if base_action:
|
||||
ctx = dict(request.context)
|
||||
action_type = base_action[0]['type']
|
||||
if action_type == 'ir.actions.report':
|
||||
ctx.update({'bin_size': True})
|
||||
request.update_context(bin_size=True)
|
||||
if additional_context:
|
||||
ctx.update(additional_context)
|
||||
request.context = ctx
|
||||
request.update_context(**additional_context)
|
||||
action = request.env[action_type].sudo().browse([action_id]).read()
|
||||
if action:
|
||||
value = clean_action(action[0], env=request.env)
|
||||
@@ -1937,7 +1874,6 @@ class ExcelExport(ExportFormat, http.Controller):
|
||||
|
||||
return xlsx_writer.value
|
||||
|
||||
|
||||
class ReportController(http.Controller):
|
||||
|
||||
#------------------------------------------------------
|
||||
@@ -2007,7 +1943,7 @@ class ReportController(http.Controller):
|
||||
return request.make_response(barcode, headers=[('Content-Type', 'image/png')])
|
||||
|
||||
@http.route(['/report/download'], type='http', auth="user")
|
||||
def report_download(self, data, context=None):
|
||||
def report_download(self, data, context=None, token=None): # pylint: disable=unused-argument
|
||||
"""This function is used by 'action_manager_report.js' in order to trigger the download of
|
||||
a pdf/controller report.
|
||||
|
||||
@@ -2057,7 +1993,7 @@ class ReportController(http.Controller):
|
||||
return
|
||||
except Exception as e:
|
||||
_logger.exception("Error while generating report %s", reportname)
|
||||
se = _serialize_exception(e)
|
||||
se = http.serialize_exception(e)
|
||||
error = {
|
||||
'code': 200,
|
||||
'message': "Odoo Server Error",
|
||||
|
||||
@@ -1,18 +1,37 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import base64
|
||||
import hashlib
|
||||
import json
|
||||
import logging
|
||||
|
||||
import odoo
|
||||
from odoo import api, http, models
|
||||
from odoo.http import request
|
||||
from odoo.tools import file_open, image_process, ustr
|
||||
|
||||
from odoo.tools.misc import str2bool
|
||||
from odoo.addons.web.controllers.main import HomeStaticTemplateHelpers
|
||||
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
"""
|
||||
Debug mode is stored in session and should always be a string.
|
||||
It can be activated with an URL query string `debug=<mode>` where mode
|
||||
is either:
|
||||
- 'tests' to load tests assets
|
||||
- 'assets' to load assets non minified
|
||||
- any other truthy value to enable simple debug mode (to show some
|
||||
technical feature, to show complete traceback in frontend error..)
|
||||
- any falsy value to disable debug mode
|
||||
|
||||
You can use any truthy/falsy value from `str2bool` (eg: 'on', 'f'..)
|
||||
Multiple debug modes can be activated simultaneously, separated with a
|
||||
comma (eg: 'tests, assets').
|
||||
"""
|
||||
ALLOWED_DEBUG_MODES = ['', '1', 'assets', 'tests']
|
||||
|
||||
|
||||
class Http(models.AbstractModel):
|
||||
_inherit = 'ir.http'
|
||||
|
||||
@@ -25,6 +44,18 @@ class Http(models.AbstractModel):
|
||||
# timeit has been done to check the optimum method
|
||||
return any(bot in user_agent for bot in cls.bots)
|
||||
|
||||
@classmethod
|
||||
def _pre_dispatch(cls, rule, args):
|
||||
super()._pre_dispatch(rule, args)
|
||||
debug = request.httprequest.args.get('debug')
|
||||
if debug is not None:
|
||||
request.session.debug = ','.join(
|
||||
mode if mode in ALLOWED_DEBUG_MODES
|
||||
else '1' if str2bool(mode, mode)
|
||||
else ''
|
||||
for mode in (debug or '1').split(',')
|
||||
)
|
||||
|
||||
def webclient_rendering_context(self):
|
||||
return {
|
||||
'menu_data': request.env['ir.ui.menu'].load_menus(request.session.debug),
|
||||
@@ -33,24 +64,28 @@ class Http(models.AbstractModel):
|
||||
|
||||
def session_info(self):
|
||||
user = request.env.user
|
||||
session_uid = request.session.uid
|
||||
version_info = odoo.service.common.exp_version()
|
||||
|
||||
session_uid = request.session.uid
|
||||
user_context = request.session.get_context() if session_uid else {}
|
||||
if session_uid:
|
||||
user_context = dict(self.env['res.users'].context_get())
|
||||
if user_context != request.session.context:
|
||||
request.session.context = user_context
|
||||
else:
|
||||
user_context = {}
|
||||
|
||||
IrConfigSudo = self.env['ir.config_parameter'].sudo()
|
||||
max_file_upload_size = int(IrConfigSudo.get_param(
|
||||
'web.max_file_upload_size',
|
||||
default=128 * 1024 * 1024, # 128MiB
|
||||
))
|
||||
mods = odoo.conf.server_wide_modules or []
|
||||
lang = user_context.get("lang")
|
||||
translation_hash = request.env['ir.translation'].sudo().get_web_translations_hash(mods, lang)
|
||||
session_info = {
|
||||
"uid": session_uid,
|
||||
"is_system": user._is_system() if session_uid else False,
|
||||
"is_admin": user._is_admin() if session_uid else False,
|
||||
"user_context": user_context,
|
||||
"db": request.session.db,
|
||||
"db": request.db,
|
||||
"server_version": version_info.get('server_version'),
|
||||
"server_version_info": version_info.get('server_version_info'),
|
||||
"support_url": "https://www.odoo.com/buy",
|
||||
@@ -67,7 +102,9 @@ class Http(models.AbstractModel):
|
||||
"max_file_upload_size": max_file_upload_size,
|
||||
"home_action_id": user.action_id.id,
|
||||
"cache_hashes": {
|
||||
"translations": translation_hash,
|
||||
"translations": request.env['ir.translation'].sudo().get_web_translations_hash(
|
||||
mods, request.session.context['lang']
|
||||
) if session_uid else None,
|
||||
},
|
||||
"currencies": self.sudo().get_currencies(),
|
||||
}
|
||||
@@ -105,18 +142,20 @@ class Http(models.AbstractModel):
|
||||
|
||||
@api.model
|
||||
def get_frontend_session_info(self):
|
||||
user = self.env.user
|
||||
session_uid = request.session.uid
|
||||
session_info = {
|
||||
'is_admin': request.session.uid and self.env.user._is_admin() or False,
|
||||
'is_system': request.session.uid and self.env.user._is_system() or False,
|
||||
'is_website_user': request.session.uid and self.env.user._is_public() or False,
|
||||
'user_id': request.session.uid and self.env.user.id or False,
|
||||
'is_admin': user._is_admin() if session_uid else False,
|
||||
'is_system': user._is_system() if session_uid else False,
|
||||
'is_website_user': user._is_public() if session_uid else False,
|
||||
'user_id': user.id if session_uid else False,
|
||||
'is_frontend': True,
|
||||
'profile_session': request.session.profile_session,
|
||||
'profile_collectors': request.session.profile_collectors,
|
||||
'profile_params': request.session.profile_params,
|
||||
'show_effect': bool(request.env['ir.config_parameter'].sudo().get_param('base_setup.show_effect')),
|
||||
}
|
||||
if request.session.uid:
|
||||
if session_uid:
|
||||
version_info = odoo.service.common.exp_version()
|
||||
session_info.update({
|
||||
'server_version': version_info.get('server_version'),
|
||||
|
||||
@@ -69,7 +69,7 @@ var TranslationDataBase = Class.extend(/** @lends instance.TranslationDataBase#
|
||||
url += '/' + (cacheId ? cacheId : Date.now());
|
||||
const paramsGet = {};
|
||||
if (modules) {
|
||||
paramsGet.modules = modules.join(',');
|
||||
paramsGet.mods = modules.join(',');
|
||||
}
|
||||
if (lang) {
|
||||
paramsGet.lang = lang;
|
||||
|
||||
@@ -9,4 +9,4 @@ class TestWebController(HttpCase):
|
||||
self.assertEqual(response.status_code, 200)
|
||||
payload = response.json()
|
||||
self.assertEqual(payload['status'], 'pass')
|
||||
self.assertNotIn('session_id', response.cookies)
|
||||
self.assertFalse(response.cookies.get('session_id'))
|
||||
|
||||
@@ -24,7 +24,7 @@ class ProfilingHttpCase(HttpCase):
|
||||
|
||||
def profile_rpc(self, params=None):
|
||||
params = params or {}
|
||||
return self.url_open(
|
||||
req = self.url_open(
|
||||
'/web/dataset/call_kw/ir.profile/set_profiling', # use model and method in route has web client does
|
||||
headers={'Content-Type': 'application/json'},
|
||||
data=json.dumps({'params':{
|
||||
@@ -33,12 +33,13 @@ class ProfilingHttpCase(HttpCase):
|
||||
'args': [],
|
||||
'kwargs': params,
|
||||
}})
|
||||
).json()
|
||||
)
|
||||
req.raise_for_status()
|
||||
return req.json()
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install', 'profiling')
|
||||
class TestProfilingWeb(ProfilingHttpCase):
|
||||
@mute_logger('odoo.http')
|
||||
def test_profiling_enabled(self):
|
||||
# since profiling will use a direct connection to the database patch 'db_connect' to ensure we are using the test cursor
|
||||
self.authenticate('admin', 'admin')
|
||||
@@ -64,7 +65,6 @@ class TestProfilingWeb(ProfilingHttpCase):
|
||||
|
||||
@tagged('post_install', '-at_install', 'profiling')
|
||||
class TestProfilingModes(ProfilingHttpCase):
|
||||
@mute_logger('odoo.http')
|
||||
def test_profile_collectors(self):
|
||||
expiration = datetime.datetime.now() + datetime.timedelta(seconds=50)
|
||||
self.env['ir.config_parameter'].set_param('base.profiling_enabled_until', expiration)
|
||||
|
||||
@@ -16,6 +16,7 @@ from hashlib import sha256
|
||||
from itertools import chain, repeat
|
||||
from markupsafe import Markup
|
||||
|
||||
import babel.core
|
||||
import decorator
|
||||
import pytz
|
||||
from lxml import etree
|
||||
@@ -26,7 +27,7 @@ from psycopg2 import sql
|
||||
from odoo import api, fields, models, tools, SUPERUSER_ID, _, Command
|
||||
from odoo.addons.base.models.ir_model import MODULE_UNINSTALL_FLAG
|
||||
from odoo.exceptions import AccessDenied, AccessError, UserError, ValidationError
|
||||
from odoo.http import request
|
||||
from odoo.http import request, DEFAULT_LANG
|
||||
from odoo.osv import expression
|
||||
from odoo.service.db import check_super
|
||||
from odoo.tools import is_html_empty, partition, collections, frozendict, lazy_property
|
||||
@@ -646,10 +647,23 @@ class Users(models.Model):
|
||||
# use read() to not read other fields: this must work while modifying
|
||||
# the schema of models res.users or res.partner
|
||||
values = user.read(list(name_to_key), load=False)[0]
|
||||
return frozendict({
|
||||
|
||||
context = {
|
||||
key: values[name]
|
||||
for name, key in name_to_key.items()
|
||||
})
|
||||
}
|
||||
|
||||
# ensure the language is set and is compatible with the web client
|
||||
lang = context.get('lang') or (request and request.default_lang()) or DEFAULT_LANG
|
||||
if lang == 'ar_AR':
|
||||
context['lang'] = 'ar'
|
||||
if lang in babel.core.LOCALE_ALIASES:
|
||||
context['lang'] = babel.core.LOCALE_ALIASES[lang]
|
||||
|
||||
# ensure uid is set
|
||||
context['uid'] = self.env.uid
|
||||
|
||||
return frozendict(context)
|
||||
|
||||
@api.model
|
||||
def action_get(self):
|
||||
|
||||
+72
-9
@@ -684,7 +684,7 @@ class FilesystemSessionStore(sessions.FilesystemSessionStore):
|
||||
|
||||
class Session(dict):
|
||||
""" Structure containing data persisted across requests. """
|
||||
__slots__ = ('can_save', 'is_explicit', 'json_data', 'new', 'should_rotate', 'sid')
|
||||
__slots__ = ('can_save', 'is_explicit', 'json_data', 'new', 'should_rotate', 'should_touch', 'sid')
|
||||
|
||||
def __init__(self, data, sid, new=False):
|
||||
super().__init__(data)
|
||||
@@ -693,6 +693,7 @@ class Session(dict):
|
||||
object.__setattr__(self, 'json_data', json.dumps(data))
|
||||
object.__setattr__(self, 'new', new)
|
||||
object.__setattr__(self, 'should_rotate', False)
|
||||
object.__setattr__(self, 'should_touch', False)
|
||||
object.__setattr__(self, 'sid', sid)
|
||||
|
||||
def __getattr__(self, attr):
|
||||
@@ -704,7 +705,67 @@ class Session(dict):
|
||||
else:
|
||||
self[key] = val
|
||||
|
||||
...
|
||||
def authenticate(self, dbname, login=None, password=None):
|
||||
"""
|
||||
Authenticate the current user with the given db, login and
|
||||
password. If successful, store the authentication parameters in
|
||||
the current session, unless multi-factor-auth (MFA) is
|
||||
activated. In that case, that last part will be done by
|
||||
:ref:`finalize`.
|
||||
|
||||
.. versionchanged:: saas-15.3
|
||||
The current request is no longer updated using the user and
|
||||
context of the session when the authentication is done using
|
||||
a database different than request.db. It is up to the caller
|
||||
to open a new cursor/registry/env on the given database.
|
||||
"""
|
||||
wsgienv = {
|
||||
'interactive': True,
|
||||
'base_location': request.httprequest.url_root.rstrip('/'),
|
||||
'HTTP_HOST': request.httprequest.environ['HTTP_HOST'],
|
||||
'REMOTE_ADDR': request.httprequest.environ['REMOTE_ADDR'],
|
||||
}
|
||||
|
||||
registry = Registry(dbname)
|
||||
pre_uid = registry['res.users'].authenticate(dbname, login, password, wsgienv)
|
||||
|
||||
self.uid = None
|
||||
self.pre_login = login
|
||||
self.pre_uid = pre_uid
|
||||
|
||||
with registry.cursor() as cr:
|
||||
env = odoo.api.Environment(cr, pre_uid, {})
|
||||
|
||||
# if 2FA is disabled we finalize immediately
|
||||
user = env['res.users'].browse(pre_uid)
|
||||
if not user._mfa_url():
|
||||
self.finalize(env)
|
||||
|
||||
if request and request.session is self and request.db == dbname:
|
||||
# Like update_env(user=request.session.uid) but works when uid is None
|
||||
request.env = odoo.api.Environment(request.env.cr, self.uid, self.context)
|
||||
request.update_context(**self.context)
|
||||
|
||||
return pre_uid
|
||||
|
||||
def finalize(self, env):
|
||||
"""
|
||||
Finalizes a partial session, should be called on MFA validation
|
||||
to convert a partial / pre-session into a logged-in one.
|
||||
"""
|
||||
login = self.pop('pre_login')
|
||||
uid = self.pop('pre_uid')
|
||||
|
||||
env = env(user=uid)
|
||||
user_context = dict(env['res.users'].context_get())
|
||||
|
||||
self.should_rotate = True
|
||||
self.update({
|
||||
'login': login,
|
||||
'uid': uid,
|
||||
'context': user_context,
|
||||
'session_token': env.user._compute_session_token(self.sid),
|
||||
})
|
||||
|
||||
def logout(self, keep_db=False):
|
||||
db = self.db if keep_db else DEFAULT_SESSION['db'] # None
|
||||
@@ -1044,13 +1105,15 @@ class Request:
|
||||
|
||||
def _save_session(self):
|
||||
""" Save a modified session on disk. """
|
||||
if not self.session.can_save:
|
||||
sess = self.session
|
||||
|
||||
if not sess.can_save:
|
||||
return
|
||||
|
||||
if self.session.should_rotate:
|
||||
root.session_store.rotate(self.session, self.env) # it saves
|
||||
elif json.dumps(self.session) != self.session.json_data:
|
||||
root.session_store.save(self.session)
|
||||
if sess.should_rotate:
|
||||
root.session_store.rotate(sess, self.env) # it saves
|
||||
elif sess.should_touch or json.dumps(sess) != sess.json_data:
|
||||
root.session_store.save(sess)
|
||||
|
||||
# We must not set the cookie if the session id was specified
|
||||
# using a http header or a GET parameter.
|
||||
@@ -1062,8 +1125,8 @@ class Request:
|
||||
# cookie). That is a special feature of the Javascript Session.
|
||||
# - It could allow session fixation attacks.
|
||||
cookie_sid = self.httprequest.cookies.get('session_id')
|
||||
if (cookie_sid != self.session.sid and not self.session.is_explicit):
|
||||
self.future_response.set_cookie('session_id', self.session.sid, max_age=SESSION_LIFETIME, httponly=True)
|
||||
if (sess.should_touch or cookie_sid != sess.sid and not sess.is_explicit):
|
||||
self.future_response.set_cookie('session_id', sess.sid, max_age=SESSION_LIFETIME, httponly=True)
|
||||
|
||||
def _set_request_dispatcher(self, rule):
|
||||
routing = rule.endpoint.routing
|
||||
|
||||
Reference in New Issue
Block a user