diff --git a/addons/auth_signup/controllers/main.py b/addons/auth_signup/controllers/main.py
index e16e65db888..a7069fbabe0 100644
--- a/addons/auth_signup/controllers/main.py
+++ b/addons/auth_signup/controllers/main.py
@@ -61,7 +61,8 @@ class AuthSignupHome(Home):
qcontext['error'] = _("Could not create a new account.")
response = request.render('auth_signup.signup', qcontext)
- response.headers['X-Frame-Options'] = 'DENY'
+ response.headers['X-Frame-Options'] = 'SAMEORIGIN'
+ response.headers['Content-Security-Policy'] = "frame-ancestors 'self'"
return response
@http.route('/web/reset_password', type='http', auth='public', website=True, sitemap=False)
@@ -93,7 +94,8 @@ class AuthSignupHome(Home):
qcontext['error'] = str(e)
response = request.render('auth_signup.reset_password', qcontext)
- response.headers['X-Frame-Options'] = 'DENY'
+ response.headers['X-Frame-Options'] = 'SAMEORIGIN'
+ response.headers['Content-Security-Policy'] = "frame-ancestors 'self'"
return response
def get_auth_signup_config(self):
diff --git a/addons/portal/controllers/portal.py b/addons/portal/controllers/portal.py
index 880afa26318..07505196952 100644
--- a/addons/portal/controllers/portal.py
+++ b/addons/portal/controllers/portal.py
@@ -213,7 +213,8 @@ class CustomerPortal(Controller):
})
response = request.render("portal.portal_my_details", values)
- response.headers['X-Frame-Options'] = 'DENY'
+ response.headers['X-Frame-Options'] = 'SAMEORIGIN'
+ response.headers['Content-Security-Policy'] = "frame-ancestors 'self'"
return response
def on_account_update(self, values, partner):
@@ -233,7 +234,8 @@ class CustomerPortal(Controller):
))
return request.render('portal.portal_my_security', values, headers={
- 'X-Frame-Options': 'DENY'
+ 'X-Frame-Options': 'SAMEORIGIN',
+ 'Content-Security-Policy': "frame-ancestors 'self'"
})
def _update_password(self, old, new1, new2):
diff --git a/addons/web/controllers/home.py b/addons/web/controllers/home.py
index 7c22ac3c3b2..f0f08d30f91 100644
--- a/addons/web/controllers/home.py
+++ b/addons/web/controllers/home.py
@@ -119,7 +119,8 @@ class Home(http.Controller):
values['disable_database_manager'] = True
response = request.render('web.login', values)
- response.headers['X-Frame-Options'] = 'DENY'
+ response.headers['X-Frame-Options'] = 'SAMEORIGIN'
+ response.headers['Content-Security-Policy'] = "frame-ancestors 'self'"
return response
@http.route('/web/login_successful', type='http', auth='user', website=True, sitemap=False)
diff --git a/addons/website/static/src/client_actions/website_preview/website_preview.js b/addons/website/static/src/client_actions/website_preview/website_preview.js
index 270b92c929a..ff54e9123b8 100644
--- a/addons/website/static/src/client_actions/website_preview/website_preview.js
+++ b/addons/website/static/src/client_actions/website_preview/website_preview.js
@@ -3,7 +3,7 @@
import { registry } from '@web/core/registry';
import { useService } from '@web/core/utils/hooks';
-const { Component, onWillStart, useEffect, useRef } = owl;
+const { Component, onWillStart, useRef } = owl;
export class WebsitePreview extends Component {
setup() {
@@ -16,15 +16,6 @@ export class WebsitePreview extends Component {
const encodedPath = encodeURIComponent(this.path);
this.initialUrl = `/website/force/${this.websiteId}?path=${encodedPath}`;
});
-
- useEffect(() => {
- this.iframe.el.addEventListener('load', () => {
- // This replaces the browser url (/web#action=website...) with
- // the iframe's url (it is clearer for the user).
- this.currentUrl = this.iframe.el.contentDocument.location.href;
- history.replaceState({}, this.props.action.display_name, this.currentUrl);
- });
- }, () => []);
}
get websiteId() {
@@ -37,11 +28,63 @@ export class WebsitePreview extends Component {
get path() {
let path = this.props.action.context.params && this.props.action.context.params.path;
- if (!path) {
+ if (path) {
+ const url = new URL(path, window.location.origin);
+ if (this._isTopWindowURL(url)) {
+ // If the client action is initialized with a path that
+ // should not be opened inside the iframe (= something we
+ // would want to open on the top window), we consider that
+ // this is not a valid flow. Instead of trying to open it on
+ // the top window, we initialize the iframe with the
+ // website homepage...
+ path = '/';
+ } else {
+ // ... otherwise, the path still needs to be normalized (as
+ // it would be if the given path was used as an href of a
+ // element).
+ path = url.pathname + url.search + url.hash;
+ }
+ } else {
path = '/';
}
return path;
}
+
+ /**
+ * Returns true if the url should be opened in the top
+ * window.
+ *
+ * @param host {string} host of the route.
+ * @param pathname {string} path of the route.
+ * @private
+ */
+ _isTopWindowURL({ host, pathname }) {
+ const backendRoutes = ['/web', '/web/session/logout'];
+ return host !== window.location.host || (pathname && backendRoutes.includes(pathname));
+ }
+
+ _onPageLoaded() {
+ // This replaces the browser url (/web#action=website...) with
+ // the iframe's url (it is clearer for the user).
+ this.currentUrl = this.iframe.el.contentDocument.location.href;
+ history.replaceState({}, this.props.action.display_name, this.currentUrl);
+
+ // The clicks on the iframe are listened, so that links with external
+ // redirections can be opened in the top window.
+ this.iframe.el.contentDocument.addEventListener('click', (ev) => {
+ const linkEl = ev.target.closest('[href]');
+ if (!linkEl) {
+ return;
+ }
+
+ const { href, target } = linkEl;
+ if (href && target !== '_blank' && !this.websiteContext.edition && this._isTopWindowURL(linkEl)) {
+ ev.preventDefault();
+ ev.stopPropagation();
+ window.location.replace(href);
+ }
+ });
+ }
}
WebsitePreview.template = 'website.WebsitePreview';
diff --git a/addons/website/static/src/client_actions/website_preview/website_preview.xml b/addons/website/static/src/client_actions/website_preview/website_preview.xml
index 7840168b132..db9b295f392 100644
--- a/addons/website/static/src/client_actions/website_preview/website_preview.xml
+++ b/addons/website/static/src/client_actions/website_preview/website_preview.xml
@@ -2,7 +2,10 @@
-
+
diff --git a/addons/website/static/src/js/utils.js b/addons/website/static/src/js/utils.js
index 0de8729c62d..3fc007b7498 100644
--- a/addons/website/static/src/js/utils.js
+++ b/addons/website/static/src/js/utils.js
@@ -255,6 +255,10 @@ function sendRequest(route, params) {
let form = document.createElement('form');
form.setAttribute('action', route);
form.setAttribute('method', params.method || 'POST');
+ const isInIframe = window.frameElement && window.frameElement.classList.contains('o_iframe');
+ if (isInIframe) {
+ form.setAttribute('target', '_top');
+ }
if (core.csrf_token) {
_addInput(form, 'csrf_token', core.csrf_token);
diff --git a/addons/website_knowledge/__manifest__.py b/addons/website_knowledge/__manifest__.py
index f4af43241a9..6d20178b359 100644
--- a/addons/website_knowledge/__manifest__.py
+++ b/addons/website_knowledge/__manifest__.py
@@ -19,6 +19,7 @@
'assets': {
'web.assets_backend': [
'website_knowledge/static/src/js/knowledge_controller.js',
+ 'website_knowledge/static/src/client_actions/website_preview/website_preview.js',
]
},
}
diff --git a/addons/website_knowledge/static/src/client_actions/website_preview/website_preview.js b/addons/website_knowledge/static/src/client_actions/website_preview/website_preview.js
new file mode 100644
index 00000000000..44d74139894
--- /dev/null
+++ b/addons/website_knowledge/static/src/client_actions/website_preview/website_preview.js
@@ -0,0 +1,12 @@
+/** @odoo-module **/
+import { patch } from 'web.utils';
+import { WebsitePreview } from '@website/client_actions/website_preview/website_preview';
+
+patch(WebsitePreview.prototype, 'website_knowledge_website_preview', {
+ /**
+ * @override
+ */
+ _isTopWindowURL({ pathname }) {
+ return (pathname && pathname.startsWith('/knowledge/article/')) || this._super(...arguments);
+ }
+});