Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 23 additions & 1 deletion assets/modules/store/js/store.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,27 @@ function link(){
return mass[0]+'?id='+_GET['id']+'&a='+_GET['a'];
}

// This module renders its own standalone document (see template/main.html), so it never
// gets the CSRF wiring that manager/media/script/main.js sets up for the rest of the
// Manager. Attach the token here instead, or every install/console_catalog call 403s.
function csrfToken(){
var tokenMeta = document.querySelector('meta[name="csrf-token"]');
return tokenMeta ? tokenMeta.getAttribute('content') : '';
}
if (csrfToken()) {
// store.query() posts to the remote extras.evo.im catalog API, not the local Manager -
// only attach the token to same-origin calls, or the header trips a CORS preflight there.
$.ajaxSetup({
beforeSend: function (xhr, settings) {
var a = document.createElement('a');
a.href = settings.url;
if (a.protocol === window.location.protocol && a.host === window.location.host) {
xhr.setRequestHeader('X-CSRF-TOKEN', csrfToken());
}
}
});
}

function store_search(val){
$('.item_list .catalog_item').each(function(){
var search_name = $(this).find('h3').text() || '';
Expand Down Expand Up @@ -291,7 +312,8 @@ store = {
});

if ($(elm).attr('data-method') == "package"){
var install_url = link() + "&action=install&cid="+$(elm).attr('data-id')+"&name="+$(elm).attr('data-name')+"&dependencies="+$(elm).attr('data-dependencies')+"&file="+file;
// A plain iframe navigation can't carry a header, so the token has to ride in the URL here.
var install_url = link() + "&action=install&cid="+$(elm).attr('data-id')+"&name="+$(elm).attr('data-name')+"&dependencies="+$(elm).attr('data-dependencies')+"&file="+file+"&_token="+encodeURIComponent(csrfToken());
$.fancybox.open({
href : install_url,
type: 'iframe',
Expand Down
3 changes: 3 additions & 0 deletions assets/modules/store/template/main.html
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
<html>
<head>
<meta content="text/html; charset=UTF-8" http-equiv="Content-Type">
<!--- Lets store.js attach the token to its AJAX calls; this module renders its own
standalone document, so it doesn't get the meta tag from header.blade.php/main.js. --->
<meta name="csrf-token" content="[+csrf_token+]">
<title>Evolution CMS Store</title>
<link rel="stylesheet" type="text/css" href="media/style/[+manager_theme+]/style.css" />
<link rel="stylesheet" type="text/css" href="[+site_url+]/assets/modules/store/css/style.css?v=[+v+]-legacydelete1-taskui23" />
Expand Down
1 change: 1 addition & 0 deletions core/src/Services/Store/ModuleViewService.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ public function render($store, $version)
? rtrim(EVO_CORE_PATH, '/\\')
: rtrim(EVO_BASE_PATH, '/\\') . '/core';
$store->lang['system_task_ui_flags'] = json_encode($store->getSystemTaskUiFlags(), JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
$store->lang['csrf_token'] = csrf_token();

if ((int) ($modx->config['manager_theme_mode'] ?? 0) === 4) {
$store->lang['body_class_name'] = 'darkness';
Expand Down
42 changes: 42 additions & 0 deletions manager/media/script/main.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,45 @@
// Attaches the CSRF token (see the meta tag in header.blade.php) to every same-origin
// XMLHttpRequest so VerifyCsrfToken doesn't reject Manager actions that never post through
// a <form> - the package Store's install/console_catalog polling, evo.js popups, etc.
// Patching XMLHttpRequest itself (rather than jQuery.ajaxSetup) covers both raw XHR calls
// and jQuery's $.ajax in one place, since jQuery's transport is built on the same native XHR.
(function () {
'use strict';
var tokenMeta = document.querySelector('meta[name="csrf-token"]');
if (!tokenMeta || typeof XMLHttpRequest === 'undefined') {
return;
}

function isSameOrigin(url) {
try {
var a = document.createElement('a');
a.href = url;
return a.protocol === window.location.protocol && a.host === window.location.host;
} catch (error) {
return false;
}
}

var nativeOpen = XMLHttpRequest.prototype.open;
XMLHttpRequest.prototype.open = function (method, url) {
this.__evoCsrfEligible = isSameOrigin(url);
return nativeOpen.apply(this, arguments);
};

var nativeSend = XMLHttpRequest.prototype.send;
XMLHttpRequest.prototype.send = function () {
if (this.__evoCsrfEligible) {
try {
this.setRequestHeader('X-CSRF-TOKEN', tokenMeta.getAttribute('content'));
} catch (error) {
// send() was reached without a matching open() (or in an otherwise invalid
// state) - let the underlying XHR surface that failure itself.
}
}
return nativeSend.apply(this, arguments);
};
})();

// evoTooltips
evo.tooltips = function (a) {
'use strict';
Expand Down