Skip to content
Open
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
4 changes: 2 additions & 2 deletions _build/data/transport.core.accesspolicies.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,15 @@ function jsonifyPermissions(array $permissions = []) {

$corePermissions = [
modAccessPolicy::POLICY_RESOURCE => ['add_children', 'create', 'copy', 'delete', 'list', 'load', 'move', 'publish', 'remove', 'save', 'steal_lock', 'undelete', 'unpublish', 'view'],
modAccessPolicy::POLICY_ADMINISTRATOR => ['about', 'access_permissions', 'actions', 'change_password', 'change_profile', 'charsets', 'class_map', 'components', 'content_types', 'countries', 'create', 'credits', 'customize_forms', 'dashboards', 'database', 'database_truncate', 'delete_category', 'delete_chunk', 'delete_context', 'delete_document', 'delete_eventlog', 'delete_plugin', 'delete_propertyset', 'delete_role', 'delete_snippet', 'delete_static_resource', 'delete_symlink', 'delete_template', 'delete_tv', 'delete_user', 'delete_weblink', 'directory_chmod', 'directory_create', 'directory_list', 'directory_remove', 'directory_update', 'edit_category', 'edit_chunk', 'edit_context', 'edit_document', 'edit_locked', 'edit_plugin', 'edit_propertyset', 'edit_role', 'edit_snippet', 'edit_static_resource', 'edit_symlink', 'edit_template', 'edit_tv', 'edit_user', 'edit_weblink', 'element_tree', 'empty_cache', 'error_log_erase', 'error_log_view', 'events', 'export_static', 'file_create', 'file_list', 'file_manager', 'file_remove', 'file_tree', 'file_unpack', 'file_update', 'file_upload', 'file_view', 'flush_sessions', 'frames', 'help', 'home', 'language', 'languages', 'lexicons', 'list', 'load', 'logout', 'mgr_log_view', 'mgr_log_erase', 'menu_reports', 'menu_security', 'menu_site', 'menu_support', 'menu_system', 'menu_tools', 'menu_trash', 'menu_user', 'menus', 'messages', 'namespaces', 'new_category', 'new_chunk', 'new_context', 'new_document', 'new_document_in_root', 'new_plugin', 'new_propertyset', 'new_role', 'new_snippet', 'new_static_resource', 'new_symlink', 'new_template', 'new_tv', 'new_user', 'new_weblink', 'packages', 'policy_delete', 'policy_edit', 'policy_new', 'policy_save', 'policy_template_delete', 'policy_template_edit', 'policy_template_new', 'policy_template_save', 'policy_template_view', 'policy_view', 'property_sets', 'providers', 'publish_document', 'purge_deleted', 'remove', 'remove_locks', 'resource_duplicate', 'resource_quick_create', 'resource_quick_update', 'resource_tree', 'resourcegroup_delete', 'resourcegroup_edit', 'resourcegroup_new', 'resourcegroup_resource_edit', 'resourcegroup_resource_list', 'resourcegroup_save', 'resourcegroup_view', 'save', 'save_category', 'save_chunk', 'save_context', 'save_document', 'save_plugin', 'save_propertyset', 'save_role', 'save_snippet', 'save_template', 'save_tv', 'save_user', 'search', 'set_sudo', 'settings', 'source_delete', 'source_edit', 'source_save', 'source_view', 'sources', 'steal_locks', 'tree_show_element_ids', 'tree_show_resource_ids', 'undelete_document', 'unlock_element_properties', 'unpublish_document', 'usergroup_delete', 'usergroup_edit', 'usergroup_new', 'usergroup_save', 'usergroup_user_edit', 'usergroup_user_list', 'usergroup_view', 'view', 'view_category', 'view_chunk', 'view_context', 'view_document', 'view_element', 'view_eventlog', 'view_offline', 'view_plugin', 'view_propertyset', 'view_role', 'view_snippet', 'view_sysinfo', 'view_template', 'view_tv', 'view_unpublished', 'view_user', 'workspaces'],
modAccessPolicy::POLICY_ADMINISTRATOR => ['about', 'access_permissions', 'actions', 'change_password', 'change_profile', 'charsets', 'class_map', 'components', 'content_types', 'countries', 'create', 'credits', 'customize_forms', 'dashboards', 'database', 'database_truncate', 'delete_category', 'delete_chunk', 'delete_context', 'delete_document', 'delete_eventlog', 'delete_plugin', 'delete_propertyset', 'delete_role', 'delete_snippet', 'delete_static_resource', 'delete_symlink', 'delete_template', 'delete_tv', 'delete_user', 'delete_weblink', 'directory_chmod', 'directory_create', 'directory_list', 'directory_remove', 'directory_update', 'edit_category', 'edit_chunk', 'edit_context', 'edit_document', 'edit_locked', 'edit_plugin', 'edit_propertyset', 'edit_role', 'edit_snippet', 'edit_static_resource', 'edit_symlink', 'edit_template', 'edit_tv', 'edit_user', 'edit_weblink', 'element_tree', 'empty_cache', 'error_log_erase', 'error_log_view', 'events', 'export_static', 'file_create', 'file_list', 'file_manager', 'file_remove', 'file_tree', 'file_unpack', 'file_update', 'file_upload', 'file_view', 'flush_sessions', 'frames', 'help', 'home', 'language', 'languages', 'lexicons', 'list', 'load', 'logout', 'mgr_log_view', 'mgr_log_erase', 'menu_reports', 'menu_security', 'menu_site', 'menu_support', 'menu_system', 'menu_tools', 'menu_trash', 'menu_user', 'menus', 'messages', 'namespaces', 'new_category', 'new_chunk', 'new_context', 'new_document', 'new_document_in_root', 'new_plugin', 'new_propertyset', 'new_role', 'new_snippet', 'new_static_resource', 'new_symlink', 'new_template', 'new_tv', 'new_user', 'new_weblink', 'packages', 'policy_delete', 'policy_edit', 'policy_new', 'policy_save', 'policy_template_delete', 'policy_template_edit', 'policy_template_new', 'policy_template_save', 'policy_template_view', 'policy_view', 'property_sets', 'providers', 'publish_document', 'purge_deleted', 'remove', 'remove_locks', 'resource_duplicate', 'resource_quick_create', 'resource_quick_update', 'resource_tree', 'resourcegroup_delete', 'resourcegroup_edit', 'resourcegroup_new', 'resourcegroup_resource_edit', 'resourcegroup_resource_list', 'resourcegroup_save', 'resourcegroup_view', 'save', 'save_category', 'save_chunk', 'save_context', 'save_document', 'save_plugin', 'save_propertyset', 'save_role', 'save_snippet', 'save_template', 'save_tv', 'save_user', 'search', 'set_sudo', 'settings', 'upgrade_core', 'source_delete', 'source_edit', 'source_save', 'source_view', 'sources', 'steal_locks', 'tree_show_element_ids', 'tree_show_resource_ids', 'undelete_document', 'unlock_element_properties', 'unpublish_document', 'usergroup_delete', 'usergroup_edit', 'usergroup_new', 'usergroup_save', 'usergroup_user_edit', 'usergroup_user_list', 'usergroup_view', 'view', 'view_category', 'view_chunk', 'view_context', 'view_document', 'view_element', 'view_eventlog', 'view_offline', 'view_plugin', 'view_propertyset', 'view_role', 'view_snippet', 'view_sysinfo', 'view_template', 'view_tv', 'view_unpublished', 'view_user', 'workspaces'],
modAccessPolicy::POLICY_LOAD_ONLY => ['load'],
modAccessPolicy::POLICY_LOAD_LIST_VIEW => ['load', 'list', 'view'],
modAccessPolicy::POLICY_OBJECT => ['load', 'list', 'view', 'save', 'remove'],
modAccessPolicy::POLICY_ELEMENT => ['add_children', 'create', 'delete', 'list', 'load', 'remove', 'save', 'view', 'copy'],
modAccessPolicy::POLICY_CONTENT_EDITOR => ['change_profile', 'class_map', 'countries', 'delete_document', 'delete_static_resource', 'delete_symlink', 'delete_weblink', 'edit_document', 'edit_static_resource', 'edit_symlink', 'edit_weblink', 'frames', 'help', 'home', 'language', 'list', 'load', 'logout', 'menu_reports', 'menu_site', 'menu_support', 'menu_tools', 'menu_user', 'new_document', 'new_static_resource', 'new_symlink', 'new_weblink', 'resource_duplicate', 'resource_tree', 'save_document', 'source_view', 'tree_show_resource_ids', 'view', 'view_document', 'view_template'],
modAccessPolicy::POLICY_MEDIA_SOURCE_ADMIN => ['create', 'copy', 'load', 'list', 'save', 'remove', 'view'],
modAccessPolicy::POLICY_MEDIA_SOURCE_USER => ['load', 'list', 'view'],
modAccessPolicy::POLICY_DEVELOPER => ['about', 'change_password', 'change_profile', 'charsets', 'class_map', 'components', 'content_types', 'countries', 'create', 'credits', 'customize_forms', 'dashboards', 'database', 'delete_category', 'delete_chunk', 'delete_context', 'delete_document', 'delete_eventlog', 'delete_plugin', 'delete_propertyset', 'delete_role', 'delete_snippet', 'delete_template', 'delete_tv', 'delete_user', 'directory_chmod', 'directory_create', 'directory_list', 'directory_remove', 'directory_update', 'edit_category', 'edit_chunk', 'edit_context', 'edit_document', 'edit_locked', 'edit_plugin', 'edit_propertyset', 'edit_role', 'edit_snippet', 'edit_static_resource', 'edit_symlink', 'edit_template', 'edit_tv', 'edit_user', 'edit_weblink', 'element_tree', 'empty_cache', 'error_log_erase', 'error_log_view', 'export_static', 'file_create', 'file_list', 'file_manager', 'file_remove', 'file_tree', 'file_unpack', 'file_update', 'file_upload', 'file_view', 'frames', 'help', 'home', 'language', 'languages', 'lexicons', 'list', 'load', 'logout', 'mgr_log_view', 'mgr_log_erase', 'menu_reports', 'menu_site', 'menu_support', 'menu_system', 'menu_tools', 'menu_user', 'menus', 'messages', 'namespaces', 'new_category', 'new_chunk', 'new_context', 'new_document', 'new_document_in_root', 'new_plugin', 'new_propertyset', 'new_role', 'new_snippet', 'new_static_resource', 'new_symlink', 'new_template', 'new_tv', 'new_user', 'new_weblink', 'packages', 'property_sets', 'providers', 'publish_document', 'purge_deleted', 'remove', 'resource_duplicate', 'resource_quick_create', 'resource_quick_update', 'resource_tree', 'save', 'save_category', 'save_chunk', 'save_context', 'save_document', 'save_plugin', 'save_propertyset', 'save_snippet', 'save_template', 'save_tv', 'save_user', 'search', 'settings', 'source_delete', 'source_edit', 'source_save', 'source_view', 'sources', 'tree_show_element_ids', 'tree_show_resource_ids', 'undelete_document', 'unlock_element_properties', 'unpublish_document', 'view', 'view_category', 'view_chunk', 'view_context', 'view_document', 'view_element', 'view_eventlog', 'view_offline', 'view_plugin', 'view_propertyset', 'view_role', 'view_snippet', 'view_sysinfo', 'view_template', 'view_tv', 'view_unpublished', 'view_user', 'workspaces'],
modAccessPolicy::POLICY_DEVELOPER => ['about', 'change_password', 'change_profile', 'charsets', 'class_map', 'components', 'content_types', 'countries', 'create', 'credits', 'customize_forms', 'dashboards', 'database', 'delete_category', 'delete_chunk', 'delete_context', 'delete_document', 'delete_eventlog', 'delete_plugin', 'delete_propertyset', 'delete_role', 'delete_snippet', 'delete_template', 'delete_tv', 'delete_user', 'directory_chmod', 'directory_create', 'directory_list', 'directory_remove', 'directory_update', 'edit_category', 'edit_chunk', 'edit_context', 'edit_document', 'edit_locked', 'edit_plugin', 'edit_propertyset', 'edit_role', 'edit_snippet', 'edit_static_resource', 'edit_symlink', 'edit_template', 'edit_tv', 'edit_user', 'edit_weblink', 'element_tree', 'empty_cache', 'error_log_erase', 'error_log_view', 'export_static', 'file_create', 'file_list', 'file_manager', 'file_remove', 'file_tree', 'file_unpack', 'file_update', 'file_upload', 'file_view', 'frames', 'help', 'home', 'language', 'languages', 'lexicons', 'list', 'load', 'logout', 'mgr_log_view', 'mgr_log_erase', 'menu_reports', 'menu_site', 'menu_support', 'menu_system', 'menu_tools', 'menu_user', 'menus', 'messages', 'namespaces', 'new_category', 'new_chunk', 'new_context', 'new_document', 'new_document_in_root', 'new_plugin', 'new_propertyset', 'new_role', 'new_snippet', 'new_static_resource', 'new_symlink', 'new_template', 'new_tv', 'new_user', 'new_weblink', 'packages', 'property_sets', 'providers', 'publish_document', 'purge_deleted', 'remove', 'resource_duplicate', 'resource_quick_create', 'resource_quick_update', 'resource_tree', 'save', 'save_category', 'save_chunk', 'save_context', 'save_document', 'save_plugin', 'save_propertyset', 'save_snippet', 'save_template', 'save_tv', 'save_user', 'search', 'settings', 'upgrade_core', 'source_delete', 'source_edit', 'source_save', 'source_view', 'sources', 'tree_show_element_ids', 'tree_show_resource_ids', 'undelete_document', 'unlock_element_properties', 'unpublish_document', 'view', 'view_category', 'view_chunk', 'view_context', 'view_document', 'view_element', 'view_eventlog', 'view_offline', 'view_plugin', 'view_propertyset', 'view_role', 'view_snippet', 'view_sysinfo', 'view_template', 'view_tv', 'view_unpublished', 'view_user', 'workspaces'],
modAccessPolicy::POLICY_CONTEXT => ['load', 'list', 'view', 'save', 'remove', 'copy', 'view_unpublished'],
modAccessPolicy::POLICY_HIDDEN_NAMESPACE => ['load' => false, 'list' => false, 'view' => true],
];
Expand Down
18 changes: 18 additions & 0 deletions _build/data/transport.core.system_settings.php
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,24 @@
'area' => 'system',
'editedon' => null,
], '', true, true);
$settings['core_upgrade_temp_dir'] = $xpdo->newObject(modSystemSetting::class);
$settings['core_upgrade_temp_dir']->fromArray([
'key' => 'core_upgrade_temp_dir',
'value' => '',
'xtype' => 'textfield',
'namespace' => 'core',
'area' => 'system',
'editedon' => null,
], '', true, true);
$settings['core_upgrade_allowed_groups'] = $xpdo->newObject(modSystemSetting::class);
$settings['core_upgrade_allowed_groups']->fromArray([
'key' => 'core_upgrade_allowed_groups',
'value' => 'Administrator',
'xtype' => 'textfield',
'namespace' => 'core',
'area' => 'system',
'editedon' => null,
], '', true, true);
$settings['auto_menuindex'] = $xpdo->newObject(modSystemSetting::class);
$settings['auto_menuindex']->fromArray([
'key' => 'auto_menuindex',
Expand Down
103 changes: 103 additions & 0 deletions _build/test/Tests/Processors/SoftwareUpdate/UpgradeCoreTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
<?php

/*
* This file is part of the MODX Revolution package.
*
* Copyright (c) MODX, LLC. All Rights Reserved.
*
* For complete copyright and license information, see the COPYRIGHT and LICENSE
* files found in the top-level directory of this distribution.
*
* @package modx-test
*/
namespace MODX\Revolution\Tests\Processors\SoftwareUpdate;

use MODX\Revolution\Processors\ProcessorResponse;
use MODX\Revolution\Processors\SoftwareUpdate\UpgradeCore;
use MODX\Revolution\MODxTestCase;

/**
* Tests for SoftwareUpdate/UpgradeCore processor (validation and permission).
*
* @package modx-test
* @subpackage Processors
* @group Processors
* @group SoftwareUpdate
* @group SoftwareUpdateProcessors
*/
class UpgradeCoreTest extends MODxTestCase
{
/**
* Test that UpgradeCore fails with empty downloadId.
*/
public function testUpgradeCoreFailsWithEmptyDownloadId()
{
$result = $this->modx->runProcessor(UpgradeCore::class, [
'downloadId' => '',
]);
$this->assertInstanceOf(ProcessorResponse::class, $result);
$this->assertTrue($result->isError());
}

/**
* Test that UpgradeCore fails with invalid downloadId format (not a UUID).
*/
public function testUpgradeCoreFailsWithInvalidDownloadId()
{
$result = $this->modx->runProcessor(UpgradeCore::class, [
'downloadId' => 'not-a-valid-uuid',
]);
$this->assertInstanceOf(ProcessorResponse::class, $result);
$this->assertTrue($result->isError());
}

/**
* Test that UpgradeCore fails with malformed downloadId.
*/
public function testUpgradeCoreFailsWithMalformedDownloadId()
{
$result = $this->modx->runProcessor(UpgradeCore::class, [
'downloadId' => 'abc',
]);
$this->assertInstanceOf(ProcessorResponse::class, $result);
$this->assertTrue($result->isError());
}

/**
* Test that UpgradeCore fails when user lacks upgrade_core permission.
*/
public function testUpgradeCoreFailsWhenUserLacksPermission()
{
$originalUser = $this->modx->user;
$originalAllowedGroups = $this->modx->getOption('core_upgrade_allowed_groups', null, 'Administrator');

$restrictedUser = $this->modx->newObject(\MODX\Revolution\modUser::class);
$restrictedUser->set('id', 999999);
$restrictedUser->set('username', 'restricted_test_user');
$this->modx->user = $restrictedUser;

$this->modx->setOption('core_upgrade_allowed_groups', '');

$result = $this->modx->runProcessor(UpgradeCore::class, [
'downloadId' => '11111111-1111-1111-1111-111111111111',
]);

$this->modx->user = $originalUser;
$this->modx->setOption('core_upgrade_allowed_groups', $originalAllowedGroups);

$this->assertInstanceOf(ProcessorResponse::class, $result);
$this->assertTrue($result->isError());
}

/**
* Test that UpgradeCore fails when GetFile returns no zip URL (retrieve error path).
*/
public function testUpgradeCoreFailsOnRetrieveError()
{
$result = $this->modx->runProcessor(UpgradeCore::class, [
'downloadId' => '00000000-0000-0000-0000-000000000000',
]);
$this->assertInstanceOf(ProcessorResponse::class, $result);
$this->assertTrue($result->isError());
}
}
3 changes: 3 additions & 0 deletions core/lexicon/en/dashboard.inc.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@
$_lang['updates_update'] = 'Update';
$_lang['updates_ok'] = 'Up to date';
$_lang['updates_extras'] = 'Extras';
$_lang['updates_upgrade_modx'] = 'Upgrade MODX';
$_lang['updates_upgrading'] = 'Upgrading MODX...';
$_lang['updates_badge'] = 'updates';

$_lang['quicklinks'] = 'Quicklinks';
$_lang['security_notices'] = 'Security Notices';
Expand Down
1 change: 1 addition & 0 deletions core/lexicon/en/permissions.inc.php
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@
$_lang['perm.search_desc'] = 'To use the Search page.';
$_lang['perm.set_sudo_desc'] = 'To make any User sudo.';
$_lang['perm.settings_desc'] = 'To view and edit any System Settings. <strong>WARNING: secrets such as API keys are commonly stored in System Settings. User may change uploadable file types, allowing execution of arbitrary code.</strong>';
$_lang['perm.upgrade_core_desc'] = 'To upgrade MODX core from the Manager dashboard. Allows downloading and installing core updates.';
$_lang['perm.events_desc'] = 'To view any System Events.';
$_lang['perm.source_delete_desc'] = 'To delete a Media Source.';
$_lang['perm.source_edit_desc'] = 'To edit a Media Source.';
Expand Down
16 changes: 16 additions & 0 deletions core/lexicon/en/setting.inc.php
Original file line number Diff line number Diff line change
Expand Up @@ -849,3 +849,19 @@

$_lang['setting_static_elements_html_extension'] = 'Static elements html extension';
$_lang['setting_static_elements_html_extension_desc'] = 'The extension for files used by static elements with HTML content.';

$_lang['invalid_download_id'] = 'Invalid download ID.';
$_lang['software_update_err_retrieve'] = 'Could not retrieve upgrade package URL.';
$_lang['software_update_err_temp_dir'] = 'Could not create or write to temporary directory.';
$_lang['software_update_err_download'] = 'Could not download upgrade package.';
$_lang['software_update_err_checksum'] = 'Downloaded package checksum verification failed.';
$_lang['software_update_err_extract'] = 'Could not extract upgrade package.';
$_lang['software_update_err_archive_structure'] = 'Upgrade archive has unexpected structure.';
$_lang['software_update_err_copy'] = 'Could not copy upgrade files.';
$_lang['software_update_err_prepare_setup'] = 'Could not prepare setup (unlock or flush sessions).';
$_lang['software_update_err_busy'] = 'Another core upgrade is already running. Try again in a moment.';

$_lang['setting_core_upgrade_temp_dir'] = 'Core upgrade temp directory';
$_lang['setting_core_upgrade_temp_dir_desc'] = 'Base directory for per-run upgrade work folders. Leave empty for default {core_path}cache/upgrade/. Only the unique run folder is deleted after an upgrade.';
$_lang['setting_core_upgrade_allowed_groups'] = 'Core upgrade allowed groups';
$_lang['setting_core_upgrade_allowed_groups_desc'] = 'Optional comma-separated user groups. Users still need the upgrade_core permission; if this list is set they must also belong to one of these groups. Default: Administrator.';
5 changes: 4 additions & 1 deletion core/src/Revolution/Processors/SoftwareUpdate/GetFile.php
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,14 @@ public function process()

if ($fileData) {
$fileData = json_decode($fileData, true);
if (!empty($fileData['zip_url']) && strpos($fileData['zip_url'], 'http') === 0) {
if (!empty($fileData['zip_url']) && strpos($fileData['zip_url'], 'https://') === 0) {
$name = basename($fileData['zip_url']);
$responseData['filename'] = $name;
$responseData['zip'] = $fileData['zip_url'];
$responseData['status'] = $response->getStatusCode();
if (!empty($fileData['sha256']) && is_string($fileData['sha256'])) {
$responseData['sha256'] = $fileData['sha256'];
}
}
}
return $this->success('', $responseData);
Expand Down
Loading
Loading