vanilla 19.0

This commit is contained in:
Ernad Husremovic 2025-10-08 10:49:46 +02:00
parent 991d2234ca
commit d1963a3c3a
3066 changed files with 1651266 additions and 922560 deletions

View file

@ -4,18 +4,22 @@ import json
import logging
import psycopg2
import odoo
import odoo.api
import odoo.exceptions
import odoo.modules.registry
from odoo import http
from odoo.exceptions import AccessError
from odoo.http import request
from odoo.service import security
from odoo.tools import ustr
from odoo.tools.translate import _
from .utils import ensure_db, _get_login_redirect_url, is_user_internal
from odoo.tools.misc import hmac
from odoo.tools.translate import _, LazyTranslate
from .utils import (
ensure_db,
_get_login_redirect_url,
is_user_internal,
)
_lt = LazyTranslate(__name__)
_logger = logging.getLogger(__name__)
@ -24,6 +28,7 @@ SIGN_UP_REQUEST_PARAMS = {'db', 'login', 'debug', 'token', 'message', 'error', '
'redirect', 'redirect_hostname', 'email', 'name', 'partner_id',
'password', 'confirm_password', 'city', 'country_id', 'lang', 'signup_email'}
LOGIN_SUCCESSFUL_PARAMS = set()
CREDENTIAL_PARAMS = ['login', 'password', 'type']
class Home(http.Controller):
@ -32,19 +37,22 @@ class Home(http.Controller):
def index(self, s_action=None, db=None, **kw):
if request.db and request.session.uid and not is_user_internal(request.session.uid):
return request.redirect_query('/web/login_successful', query=request.params)
return request.redirect_query('/web', query=request.params)
return request.redirect_query('/odoo', query=request.params)
def _web_client_readonly(self, rule, args):
return False
# ideally, this route should be `auth="user"` but that don't work in non-monodb mode.
@http.route('/web', type='http', auth="none")
@http.route(['/web', '/odoo', '/odoo/<path:subpath>', '/scoped_app/<path:subpath>'], type='http', auth="none", readonly=_web_client_readonly)
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)
return request.redirect_query('/web/login', query={'redirect': request.httprequest.full_path}, code=303)
if kw.get('redirect'):
return request.redirect(kw.get('redirect'), 303)
if not security.check_session(request.session, request.env):
if not security.check_session(request.session, request.env, request):
raise http.SessionExpiredException("Session expired")
if not is_user_internal(request.session.uid):
return request.redirect('/web/login_successful', 303)
@ -55,22 +63,37 @@ class Home(http.Controller):
# Restore the user on the environment, it was lost due to auth="none"
request.update_env(user=request.session.uid)
try:
if request.env.user:
request.env.user._on_webclient_bootstrap()
context = request.env['ir.http'].webclient_rendering_context()
# Add the browser_cache_secret here and not in session_info() to ensure that it is only in
# the webclient page, which is cache-control: "no-store" (see below)
# Reuse session security related fields, to change the key when a security event
# occurs for the user, like a password or 2FA change.
hmac_payload = request.env.user._session_token_get_values() # already ordered
session_info = context.get("session_info")
session_info['browser_cache_secret'] = hmac(request.env(su=True), "browser_cache_key", hmac_payload)
response = request.render('web.webclient_bootstrap', qcontext=context)
response.headers['X-Frame-Options'] = 'DENY'
response.headers['Cache-Control'] = 'no-store'
return response
except AccessError:
return request.redirect('/web/login?error=access')
@http.route('/web/webclient/load_menus/<string:unique>', type='http', auth='user', methods=['GET'])
def web_load_menus(self, unique):
@http.route('/web/webclient/load_menus', type='http', auth='user', methods=['GET'], readonly=True)
def web_load_menus(self, lang=None):
"""
Loads the menus for the webclient
:param unique: this parameters is not used, but mandatory: it is used by the HTTP stack to make a unique request
:param lang: language in which the menus should be loaded (only works if language is installed)
:return: the menus (including the images in Base64)
"""
if lang:
request.update_context(lang=lang)
menus = request.env["ir.ui.menu"].load_web_menus(request.session.debug)
body = json.dumps(menus, default=ustr)
body = json.dumps(menus)
response = request.make_response(body, [
# this method must specify a content-type application/json instead of using the default text/html set because
# the type of the route is set to HTTP, but the rpc is made with a get and expects JSON
@ -82,7 +105,7 @@ class Home(http.Controller):
def _login_redirect(self, uid, redirect=None):
return _get_login_redirect_url(uid, redirect)
@http.route('/web/login', type='http', auth="none")
@http.route('/web/login', type='http', auth='none', readonly=False, list_as_website_content=_lt("Login"))
def web_login(self, redirect=None, **kw):
ensure_db()
request.params['login_success'] = False
@ -107,9 +130,13 @@ class Home(http.Controller):
if request.httprequest.method == 'POST':
try:
uid = request.session.authenticate(request.db, request.params['login'], request.params['password'])
credential = {key: value for key, value in request.params.items() if key in CREDENTIAL_PARAMS and value}
credential.setdefault('type', 'password')
if request.env['res.users']._should_captcha_login(credential):
request.env['ir.http']._verify_request_recaptcha_token('login')
auth_info = request.session.authenticate(request.env, credential)
request.params['login_success'] = True
return request.redirect(self._login_redirect(uid, redirect=redirect))
return request.redirect(self._login_redirect(auth_info['uid'], redirect=redirect))
except odoo.exceptions.AccessDenied as e:
if e.args == odoo.exceptions.AccessDenied().args:
values['error'] = _("Wrong login/password")
@ -137,13 +164,13 @@ class Home(http.Controller):
valid_values = {k: v for k, v in kwargs.items() if k in LOGIN_SUCCESSFUL_PARAMS}
return request.render('web.login_successful', valid_values)
@http.route('/web/become', type='http', auth='user', sitemap=False)
@http.route('/web/become', type='http', auth='user', sitemap=False, readonly=True)
def switch_to_admin(self):
uid = request.env.user.id
if request.env.user._is_system():
uid = request.session.uid = odoo.SUPERUSER_ID
# invalidate session token cache as we've changed the uid
request.env['res.users'].clear_caches()
request.env.registry.clear_cache()
request.session.session_token = security.compute_session_token(request.session, request.env)
return request.redirect(self._login_redirect(uid))
@ -165,11 +192,16 @@ class Home(http.Controller):
('Cache-Control', 'no-store')]
return request.make_response(data, headers, status=status)
@http.route(['/robots.txt'], type='http', auth="none")
def robots(self, **kwargs):
allowed_routes = self._get_allowed_robots_routes()
robots_content = ["User-agent: *", "Disallow: /"]
robots_content.extend(f"Allow: {route}" for route in allowed_routes)
return request.make_response("\n".join(robots_content), [('Content-Type', 'text/plain')])
def _get_allowed_robots_routes(self):
"""Override this method to return a list of allowed routes.
By default this controller does not serve robots.txt so all routes
are implicitly open but we want any module to be able to append
to this list, in case the website module is installed.
:return: A list of URL paths that should be allowed by robots.txt
Examples: ['/social_instagram/', '/sitemap.xml', '/web/']