Refactors AccessControl and passes it as dependency to JSON API and Menu

This commit is contained in:
Vlad
2017-08-14 11:28:31 -04:00
parent 51fbf29031
commit 2e5554a3af
12 changed files with 286 additions and 241 deletions

View File

@ -2,7 +2,6 @@
namespace MailPoet\API\JSON;
use MailPoet\Config\AccessControl;
use MailPoet\Config\Env;
use MailPoet\Util\Helpers;
use MailPoet\Util\Security;
use MailPoet\WP\Hooks;
@ -20,9 +19,11 @@ class API {
private $_available_api_versions = array(
'v1'
);
private $access_control;
const CURRENT_VERSION = 'v1';
function __construct() {
$this->access_control = new AccessControl();
foreach($this->_available_api_versions as $available_api_version) {
$this->addEndpointNamespace(
sprintf('%s\%s', __NAMESPACE__, $available_api_version),
@ -127,7 +128,7 @@ class API {
throw new \Exception(__('Invalid API endpoint.', 'mailpoet'));
}
$endpoint = new $this->_request_endpoint_class();
$endpoint = new $this->_request_endpoint_class($this->access_control);
// check the accessibility of the requested endpoint's action
// by default, an endpoint's action is considered "private"
@ -148,12 +149,12 @@ class API {
function validatePermissions($request_method, $permissions) {
// if method permission is defined, validate it
if (!empty($permissions['methods'][$request_method])) {
return ($permissions['methods'][$request_method] === Access::ALL) ?
return ($permissions['methods'][$request_method] === AccessControl::ACCESS_ALL) ?
true :
AccessControl::validatePermission($permissions['methods'][$request_method]);
$this->access_control->validatePermission($permissions['methods'][$request_method]);
}
// use global permission
return AccessControl::validatePermission($permissions['global']);
return $this->access_control->validatePermission($permissions['global']);
}
function checkToken() {

View File

@ -1,12 +0,0 @@
<?php
namespace MailPoet\API\JSON;
if(!defined('ABSPATH')) exit;
final class Access {
const ALL = 'all';
private function __construct() {
}
}

View File

@ -12,8 +12,9 @@ class MP2Migrator extends APIEndpoint {
'global' => AccessControl::PERMISSION_MANAGE_SETTINGS
);
public function __construct() {
$this->MP2Migrator = new \MailPoet\Config\MP2Migrator();
public function __construct(AccessControl $access_control) {
$this->access_control = $access_control;
$this->MP2Migrator = new \MailPoet\Config\MP2Migrator($this->access_control);
}
/**

View File

@ -13,10 +13,15 @@ class Setup extends APIEndpoint {
public $permissions = array(
'global' => AccessControl::PERMISSION_MANAGE_SETTINGS
);
private $access_control;
function __construct(AccessControl $access_control) {
$this->access_control = $access_control;
}
function reset() {
try {
$activator = new Activator();
$activator = new Activator($this->access_control);
$activator->deactivate();
$activator->activate();
Hooks::doAction('mailpoet_setup_reset');

View File

@ -2,7 +2,6 @@
namespace MailPoet\API\JSON\v1;
use MailPoet\API\JSON\Access as APIAccess;
use MailPoet\API\JSON\Endpoint as APIEndpoint;
use MailPoet\API\JSON\Error as APIError;
use MailPoet\Config\AccessControl;
@ -17,7 +16,7 @@ if(!defined('ABSPATH')) exit;
class Subscribers extends APIEndpoint {
public $permissions = array(
'global' => AccessControl::PERMISSION_MANAGE_SUBSCRIBERS,
'methods' => array('subscribe' => APIAccess::ALL)
'methods' => array('subscribe' => AccessControl::ACCESS_ALL)
);
function get($data = array()) {

View File

@ -8,23 +8,26 @@ if(!defined('ABSPATH')) exit;
require_once(ABSPATH . 'wp-includes/pluggable.php');
class AccessControl {
static $permissions;
const PERMISSION_ACCESS_PLUGIN = 'access_plugin';
const PERMISSION_MANAGE_SETTINGS = 'manage_settings';
const PERMISSION_MANAGE_EMAILS = 'manage_emails';
const PERMISSION_MANAGE_SUBSCRIBERS = 'manage_subscribers';
const PERMISSION_MANAGE_FORMS = 'manage_forms';
const PERMISSION_MANAGE_SEGMENTS = 'manage_segments';
const PERMISSION_UPDATE_PLUGIN = 'update_plugin';
const ACCESS_ALL = 'All';
static function init($permissions = array()) {
self::setPermissions($permissions);
public $permissions;
public $current_user_roles;
public $user_capabilities;
function __construct() {
$this->permissions = $this->getDefaultPermissions();
$this->user_roles = $this->getUserRoles();
$this->user_capabilities = $this->getUserCapabilities();
}
static function setPermissions($permissions = array()) {
self::$permissions = ($permissions) ? $permissions : self::getPermissions();
}
static function getPermissions() {
private function getDefaultPermissions() {
return array(
self::PERMISSION_ACCESS_PLUGIN => WPHooks::applyFilters(
'mailpoet_permission_access_plugin',
@ -63,18 +66,31 @@ class AccessControl {
array(
'administrator'
)
),
self::PERMISSION_UPDATE_PLUGIN => WPHooks::applyFilters(
'mailpoet_permission_update_plugin',
array(
'administrator'
)
),
);
}
static function validatePermission($permission) {
if(empty(self::$permissions)) self::init();
if(empty(self::$permissions[$permission])) return false;
$current_user = wp_get_current_user();
$current_user_roles = $current_user->roles;
function getUserRoles() {
$user = wp_get_current_user();
return $user->roles;
}
function getUserCapabilities() {
$user = wp_get_current_user();
return array_keys($user->allcaps);
}
function validatePermission($permission) {
if(empty($this->permissions[$permission])) return false;
$permitted_roles = array_intersect(
$current_user_roles,
self::$permissions[$permission]
$this->user_roles,
$this->permissions[$permission]
);
return (!empty($permitted_roles));
}

View File

@ -5,13 +5,16 @@ namespace MailPoet\Config;
if(!defined('ABSPATH')) exit;
class Activator {
const REQUIRED_PERMISSION = AccessControl::PERMISSION_MANAGE_SETTINGS;
private $access_control;
static function activate() {
self::validatePermission();
if(!current_user_can(self::PERMISSION_ACTIVATE)) {
throw new \Exception('MaiLpoet ID must be greater than zero');
function __construct(AccessControl $access_control) {
$this->access_control = $access_control;
if(!$this->access_control->validatePermission(AccessControl::PERMISSION_UPDATE_PLUGIN)) {
throw new \Exception(__('You do not have permission to activate/deactivate MailPoet plugin.', 'mailpoet'));
}
}
function activate() {
$migrator = new Migrator();
$migrator->up();
@ -21,19 +24,8 @@ class Activator {
update_option('mailpoet_db_version', Env::$version);
}
static function deactivate() {
self::validatePermission();
function deactivate() {
$migrator = new Migrator();
$migrator->down();
}
static function validatePermission() {
if(AccessControl::validatePermission(self::REQUIRED_PERMISSION)) return;
throw new \Exception(
sprintf(
__('MailPoet can only be activated/deactivated by a user with <strong>%s</strong> capability.', 'mailpoet'),
self::REQUIRED_PERMISSION
)
);
}
}

View File

@ -4,7 +4,10 @@ use MailPoet\Models\Setting;
use MailPoet\Util\Url;
class Changelog {
function __construct() {
private $access_control;
function __construct(AccessControl $access_control) {
$this->access_control = $access_control;
}
function init() {
@ -34,7 +37,7 @@ class Changelog {
$version = Setting::getValue('version', null);
$redirect_url = null;
$mp2_migrator = new MP2Migrator();
$mp2_migrator = new MP2Migrator($this->access_control);
if(!in_array($_GET['page'], array('mailpoet-migration', 'mailpoet-settings')) && $mp2_migrator->isMigrationStartedAndNotCompleted()) {
// Force the redirection if the migration has started but is not completed
$redirect_url = admin_url('admin.php?page=mailpoet-migration');

View File

@ -13,18 +13,18 @@ if(!defined('ABSPATH')) exit;
require_once(ABSPATH . 'wp-admin/includes/plugin.php');
class Initializer {
const UNABLE_TO_CONNECT = 'Unable to connect to the database (the database is unable to open a file or folder), the connection is likely not configured correctly. Please read our [link] Knowledge Base article [/link] for steps how to resolve it.';
const SOLVE_DB_ISSUE_URL = 'http://beta.docs.mailpoet.com/article/200-solving-database-connection-issues';
protected $plugin_initialized = false;
private $access_control;
function __construct($params = array(
'file' => '',
'version' => '1.0.0'
)) {
Env::init($params['file'], $params['version']);
AccessControl::init();
$this->access_control = new AccessControl();
}
function init() {
@ -136,7 +136,8 @@ class Initializer {
// if current db version and plugin version differ
if(version_compare($current_db_version, Env::$version) !== 0) {
Activator::activate();
$activator = new Activator($this->access_control);
$activator->activate();
}
}
@ -186,12 +187,12 @@ class Initializer {
}
function setupMenu() {
$menu = new Menu($this->renderer, Env::$assets_url);
$menu = new Menu($this->renderer, Env::$assets_url, $this->access_control);
$menu->init();
}
function setupChangelog() {
$changelog = new Changelog();
$changelog = new Changelog($this->access_control);
$changelog->init();
}
@ -247,7 +248,7 @@ class Initializer {
function handleFailedInitialization($exception) {
// Check if we are able to add pages at this point
if(function_exists('wp_get_current_user')) {
Menu::addErrorPage();
Menu::addErrorPage($this->access_control);
}
return WPNotice::displayError($exception);
}

View File

@ -19,17 +19,19 @@ class MP2Migrator {
const CHUNK_SIZE = 10; // To import the data by batch
private $log_file;
private $access_control;
public $log_file_url;
public $progressbar;
private $segments_mapping = array(); // Mapping between old and new segment IDs
private $wp_users_segment;
public function __construct() {
public function __construct(AccessControl $access_control) {
$this->defineMP2Tables();
$log_filename = 'mp2migration.log';
$this->log_file = Env::$temp_path . '/' . $log_filename;
$this->log_file_url = Env::$temp_url . '/' . $log_filename;
$this->progressbar = new ProgressBar('mp2migration');
$this->access_control = $access_control;
}
private function defineMP2Tables() {
@ -187,8 +189,9 @@ class MP2Migrator {
*
*/
private function eraseMP3Data() {
Activator::deactivate();
Activator::activate();
$activator = new Activator($this->access_control);
$activator->deactivate();
$activator->activate();
$this->deleteSegments();
$this->resetMigrationCounters();

View File

@ -26,9 +26,18 @@ use MailPoet\WP\Readme;
if(!defined('ABSPATH')) exit;
class Menu {
function __construct($renderer, $assets_url) {
const MAIN_PAGE_SLUG = 'mailpoet-newsletters';
public $renderer;
public $assets_url;
private $access_control;
private $subscribers_over_limit;
function __construct($renderer, $assets_url, AccessControl $access_control) {
$this->renderer = $renderer;
$this->assets_url = $assets_url;
$this->access_control = $access_control;
$this->user_capability = $this->access_control->user_capabilities[0];
$subscribers_feature = new SubscribersFeature();
$this->subscribers_over_limit = $subscribers_feature->check();
$this->checkMailPoetAPIKey();
@ -46,30 +55,31 @@ class Menu {
}
function setup() {
if(!AccessControl::validatePermission('access_plugin')) return;
if(!$this->access_control->validatePermission(AccessControl::PERMISSION_ACCESS_PLUGIN)) return;
if(self::isOnMailPoetAdminPage()) {
do_action('mailpoet_conflict_resolver_styles');
do_action('mailpoet_conflict_resolver_scripts');
}
$main_page_slug = 'mailpoet-newsletters';
// Main page
add_menu_page(
'MailPoet',
'MailPoet',
AccessControl::validatePermission(AccessControl::PERMISSION_ACCESS_PLUGIN),
$main_page_slug,
$this->user_capability,
self::MAIN_PAGE_SLUG,
null,
$this->assets_url . '/img/menu_icon.png',
30
);
// Emails page
if ($this->access_control->validatePermission(AccessControl::PERMISSION_MANAGE_EMAILS)) {
$newsletters_page = add_submenu_page(
$main_page_slug,
self::MAIN_PAGE_SLUG,
$this->setPageTitle(__('Emails', 'mailpoet')),
__('Emails', 'mailpoet'),
AccessControl::validatePermission(AccessControl::PERMISSION_MANAGE_EMAILS),
$main_page_slug,
$this->user_capability,
self::MAIN_PAGE_SLUG,
array(
$this,
'newsletters'
@ -88,17 +98,34 @@ class Menu {
));
});
// newsletter editor
add_submenu_page(
true,
$this->setPageTitle(__('Newsletter', 'mailpoet')),
__('Newsletter Editor', 'mailpoet'),
$this->user_capability,
'mailpoet-newsletter-editor',
array(
$this,
'newletterEditor'
)
);
}
// Forms page
if($this->access_control->validatePermission(AccessControl::PERMISSION_MANAGE_FORMS)) {
$forms_page = add_submenu_page(
$main_page_slug,
self::MAIN_PAGE_SLUG,
$this->setPageTitle(__('Forms', 'mailpoet')),
__('Forms', 'mailpoet'),
AccessControl::validatePermission(AccessControl::PERMISSION_MANAGE_FORMS),
$this->user_capability,
'mailpoet-forms',
array(
$this,
'forms'
)
);
// add limit per page to screen options
add_action('load-' . $forms_page, function() {
add_screen_option('per_page', array(
@ -111,17 +138,34 @@ class Menu {
));
});
// form editor
add_submenu_page(
true,
$this->setPageTitle(__('Form Editor', 'mailpoet')),
__('Form Editor', 'mailpoet'),
$this->user_capability,
'mailpoet-form-editor',
array(
$this,
'formEditor'
)
);
}
// Subscribers page
if($this->access_control->validatePermission(AccessControl::PERMISSION_MANAGE_SUBSCRIBERS)) {
$subscribers_page = add_submenu_page(
$main_page_slug,
self::MAIN_PAGE_SLUG,
$this->setPageTitle(__('Subscribers', 'mailpoet')),
__('Subscribers', 'mailpoet'),
AccessControl::validatePermission(AccessControl::PERMISSION_MANAGE_SUBSCRIBERS),
$this->user_capability,
'mailpoet-subscribers',
array(
$this,
'subscribers'
)
);
// add limit per page to screen options
add_action('load-' . $subscribers_page, function() {
add_screen_option('per_page', array(
@ -134,11 +178,40 @@ class Menu {
));
});
// import
add_submenu_page(
'admin.php?page=mailpoet-subscribers',
$this->setPageTitle(__('Import', 'mailpoet')),
__('Import', 'mailpoet'),
$this->user_capability,
'mailpoet-import',
array(
$this,
'import'
)
);
// export
add_submenu_page(
true,
$this->setPageTitle(__('Export', 'mailpoet')),
__('Export', 'mailpoet'),
$this->user_capability,
'mailpoet-export',
array(
$this,
'export'
)
);
}
// Segments page
if($this->access_control->validatePermission(AccessControl::PERMISSION_MANAGE_SEGMENTS)) {
$segments_page = add_submenu_page(
$main_page_slug,
self::MAIN_PAGE_SLUG,
$this->setPageTitle(__('Lists', 'mailpoet')),
__('Lists', 'mailpoet'),
AccessControl::validatePermission(AccessControl::PERMISSION_MANAGE_SEGMENTS),
$this->user_capability,
'mailpoet-segments',
array(
$this,
@ -157,24 +230,29 @@ class Menu {
'option' => 'mailpoet_segments_per_page'
));
});
}
// Settings page
if($this->access_control->validatePermission(AccessControl::PERMISSION_MANAGE_SETTINGS)) {
add_submenu_page(
$main_page_slug,
self::MAIN_PAGE_SLUG,
$this->setPageTitle(__('Settings', 'mailpoet')),
__('Settings', 'mailpoet'),
AccessControl::validatePermission(AccessControl::PERMISSION_MANAGE_SETTINGS),
$this->user_capability,
'mailpoet-settings',
array(
$this,
'settings'
)
);
}
// Help page
add_submenu_page(
$main_page_slug,
self::MAIN_PAGE_SLUG,
$this->setPageTitle(__('Help', 'mailpoet')),
__('Help', 'mailpoet'),
AccessControl::validatePermission(AccessControl::PERMISSION_ACCESS_PLUGIN),
$this->user_capability,
'mailpoet-help',
array(
$this,
@ -182,12 +260,13 @@ class Menu {
)
);
// Premium page
// Only show this page in menu if the Premium plugin is not activated
add_submenu_page(
License::getLicense() ? true : $main_page_slug,
License::getLicense() ? true : self::MAIN_PAGE_SLUG,
$this->setPageTitle(__('Premium', 'mailpoet')),
__('Premium', 'mailpoet'),
AccessControl::validatePermission(AccessControl::PERMISSION_ACCESS_PLUGIN),
$this->user_capability,
'mailpoet-premium',
array(
$this,
@ -195,35 +274,12 @@ class Menu {
)
);
add_submenu_page(
'admin.php?page=mailpoet-subscribers',
$this->setPageTitle(__('Import', 'mailpoet')),
__('Import', 'mailpoet'),
AccessControl::validatePermission(AccessControl::PERMISSION_MANAGE_SUBSCRIBERS),
'mailpoet-import',
array(
$this,
'import'
)
);
add_submenu_page(
true,
$this->setPageTitle(__('Export', 'mailpoet')),
__('Export', 'mailpoet'),
AccessControl::validatePermission(AccessControl::PERMISSION_MANAGE_SUBSCRIBERS),
'mailpoet-export',
array(
$this,
'export'
)
);
// Welcome page
add_submenu_page(
true,
$this->setPageTitle(__('Welcome', 'mailpoet')),
__('Welcome', 'mailpoet'),
AccessControl::validatePermission(AccessControl::PERMISSION_ACCESS_PLUGIN),
$this->user_capability,
'mailpoet-welcome',
array(
$this,
@ -231,11 +287,12 @@ class Menu {
)
);
// Update page
add_submenu_page(
true,
$this->setPageTitle(__('Update', 'mailpoet')),
__('Update', 'mailpoet'),
AccessControl::validatePermission(AccessControl::PERMISSION_ACCESS_PLUGIN),
$this->user_capability,
'mailpoet-update',
array(
$this,
@ -243,41 +300,18 @@ class Menu {
)
);
// Migration page
add_submenu_page(
true,
$this->setPageTitle(__('Migration', 'mailpoet')),
'',
AccessControl::validatePermission(AccessControl::PERMISSION_MANAGE_SETTINGS),
$this->user_capability,
'mailpoet-migration',
array(
$this,
'migration'
)
);
add_submenu_page(
true,
$this->setPageTitle(__('Form Editor', 'mailpoet')),
__('Form Editor', 'mailpoet'),
AccessControl::validatePermission(AccessControl::PERMISSION_MANAGE_FORMS),
'mailpoet-form-editor',
array(
$this,
'formEditor'
)
);
add_submenu_page(
true,
$this->setPageTitle(__('Newsletter', 'mailpoet')),
__('Newsletter Editor', 'mailpoet'),
AccessControl::validatePermission(AccessControl::PERMISSION_MANAGE_EMAILS),
'mailpoet-newsletter-editor',
array(
$this,
'newletterEditor'
)
);
}
function welcome() {
@ -295,20 +329,20 @@ class Menu {
or
strpos($redirect_url, 'mailpoet') === false
) {
$redirect_url = admin_url('admin.php?page=mailpoet-newsletters');
$redirect_url = admin_url('admin.php?page=' . self::MAIN_PAGE_SLUG);
}
$data = array(
'settings' => Setting::getAll(),
'current_user' => wp_get_current_user(),
'redirect_url' => $redirect_url,
'sub_menu' => 'mailpoet-newsletters'
'sub_menu' => self::MAIN_PAGE_SLUG
);
$this->displayPage('welcome.html', $data);
}
function migration() {
$mp2_migrator = new MP2Migrator();
$mp2_migrator = new MP2Migrator($this->access_control);
$mp2_migrator->init();
$data = array(
'log_file_url' => $mp2_migrator->log_file_url,
@ -330,14 +364,14 @@ class Menu {
or
strpos($redirect_url, 'mailpoet') === false
) {
$redirect_url = admin_url('admin.php?page=mailpoet-newsletters');
$redirect_url = admin_url('admin.php?page=' . self::MAIN_PAGE_SLUG);
}
$data = array(
'settings' => Setting::getAll(),
'current_user' => wp_get_current_user(),
'redirect_url' => $redirect_url,
'sub_menu' => 'mailpoet-newsletters'
'sub_menu' => self::MAIN_PAGE_SLUG
);
$readme_file = Env::$path . '/readme.txt';
@ -354,7 +388,7 @@ class Menu {
function premium() {
$data = array(
'subscriber_count' => Subscriber::getTotalSubscribers(),
'sub_menu' => 'mailpoet-newsletters'
'sub_menu' => self::MAIN_PAGE_SLUG
);
$this->displayPage('premium.html', $data);
@ -507,7 +541,7 @@ class Menu {
'shortcodes' => ShortcodesHelper::getShortcodes(),
'settings' => Setting::getAll(),
'current_wp_user' => Subscriber::getCurrentWPUser(),
'sub_menu' => 'mailpoet-newsletters'
'sub_menu' => self::MAIN_PAGE_SLUG
);
wp_enqueue_media();
wp_enqueue_script('tinymce-wplink', includes_url('js/tinymce/plugins/wplink/plugin.js'));
@ -599,13 +633,13 @@ class Menu {
* This error page is used when the initialization is failed
* to display admin notices only
*/
static function addErrorPage() {
static function addErrorPage(AccessControl $access_control) {
if(!self::isOnMailPoetAdminPage()) {
return false;
}
// Check if page already exists
if(get_plugin_page_hook($_REQUEST['page'], '')
|| get_plugin_page_hook($_REQUEST['page'], 'mailpoet-newsletters')
|| get_plugin_page_hook($_REQUEST['page'], self::MAIN_PAGE_SLUG)
) {
return false;
}
@ -613,7 +647,7 @@ class Menu {
true,
'MailPoet',
'MailPoet',
Env::$use_plugin_permission,
$access_control->user_capabilities[0],
$_REQUEST['page'],
array(
__CLASS__,
@ -629,7 +663,7 @@ class Menu {
function checkMailPoetAPIKey(ServicesChecker $checker = null) {
if(self::isOnMailPoetAdminPage()) {
$show_notices = isset($_REQUEST['page'])
&& stripos($_REQUEST['page'], 'mailpoet-newsletters') === false;
&& stripos($_REQUEST['page'], self::MAIN_PAGE_SLUG) === false;
$checker = $checker ?: new ServicesChecker();
$this->mp_api_key_valid = $checker->isMailPoetAPIKeyValid($show_notices);
}
@ -638,7 +672,7 @@ class Menu {
function checkPremiumKey(ServicesChecker $checker = null) {
if(self::isOnMailPoetAdminPage()) {
$show_notices = isset($_REQUEST['page'])
&& stripos($_REQUEST['page'], 'mailpoet-newsletters') === false;
&& stripos($_REQUEST['page'], self::MAIN_PAGE_SLUG) === false;
$checker = $checker ?: new ServicesChecker();
$this->premium_key_valid = $checker->isPremiumKeyValid($show_notices);
}

View File

@ -1,6 +1,7 @@
<?php
namespace MailPoet\Router\Endpoints;
use MailPoet\Config\AccessControl;
use MailPoet\Config\Env;
use MailPoet\Models\Newsletter;
use MailPoet\Models\SendingQueue;
@ -20,6 +21,7 @@ class ViewInBrowser {
function __construct($data) {
$this->data = $this->_processBrowserPreviewData($data);
$this->access_control = new AccessControl();
}
function view() {
@ -69,8 +71,8 @@ class ViewInBrowser {
$data->queue = false;
}
// allow users with 'manage_options' permission to preview any newsletter
if(!empty($data->preview) && current_user_can(Env::$required_permission)
// allow users with permission to manage emails to preview any newsletter
if(!empty($data->preview) && $this->access_control->validatePermission(AccessControl::PERMISSION_MANAGE_EMAILS)
) return $data;
// allow others to preview newsletters only when newsletter hash is defined