Merge pull request #1041 from mailpoet/access_control

Allows granular control of access to various parts of the plugin [MAILPOET-1057] [MAILPOET-1048]
This commit is contained in:
Tautvidas Sipavičius
2017-08-28 16:48:47 +03:00
committed by GitHub
41 changed files with 890 additions and 323 deletions

View File

@ -1,11 +1,14 @@
<?php
namespace MailPoet\API;
use MailPoet\Config\AccessControl;
if(!defined('ABSPATH')) exit;
class API {
static function JSON() {
return new \MailPoet\API\JSON\API();
static function JSON(AccessControl $access_control) {
return new \MailPoet\API\JSON\API($access_control);
}
static function MP($version) {

View File

@ -1,7 +1,7 @@
<?php
namespace MailPoet\API\JSON;
use MailPoet\Config\Env;
use MailPoet\Config\AccessControl;
use MailPoet\Util\Helpers;
use MailPoet\Util\Security;
use MailPoet\WP\Hooks;
@ -19,9 +19,11 @@ class API {
private $_available_api_versions = array(
'v1'
);
private $access_control;
const CURRENT_VERSION = 'v1';
function __construct() {
function __construct(AccessControl $access_control) {
$this->access_control = $access_control;
foreach($this->_available_api_versions as $available_api_version) {
$this->addEndpointNamespace(
sprintf('%s\%s', __NAMESPACE__, $available_api_version),
@ -130,17 +132,11 @@ class API {
// check the accessibility of the requested endpoint's action
// by default, an endpoint's action is considered "private"
$permissions = $endpoint->permissions;
if(array_key_exists($this->_request_method, $permissions) === false ||
$permissions[$this->_request_method] !== Access::ALL
) {
if($this->checkPermissions() === false) {
$error_message = __('You do not have the required permissions.', 'mailpoet');
$error_response = $this->createErrorResponse(Error::FORBIDDEN, $error_message, Response::STATUS_FORBIDDEN);
return $error_response;
}
if(!$this->validatePermissions($this->_request_method, $endpoint->permissions)) {
$error_message = __('You do not have the required permissions.', 'mailpoet');
$error_response = $this->createErrorResponse(Error::FORBIDDEN, $error_message, Response::STATUS_FORBIDDEN);
return $error_response;
}
$response = $endpoint->{$this->_request_method}($this->_request_data);
return $response;
} catch(\Exception $e) {
@ -150,8 +146,11 @@ class API {
}
}
function checkPermissions() {
return current_user_can(Env::$required_permission);
function validatePermissions($request_method, $permissions) {
// validate method permission if defined, otherwise validate global permission
return(!empty($permissions['methods'][$request_method])) ?
$this->access_control->validatePermission($permissions['methods'][$request_method]) :
$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

@ -1,11 +1,16 @@
<?php
namespace MailPoet\API\JSON;
use MailPoet\Config\AccessControl;
if(!defined('ABSPATH')) exit;
abstract class Endpoint {
public $permissions = array();
public $permissions = array(
'global' => array(AccessControl::PERMISSION_MANAGE_SETTINGS),
'methods' => array()
);
function successResponse(
$data = array(), $meta = array(), $status = Response::STATUS_OK
@ -18,7 +23,7 @@ abstract class Endpoint {
) {
if(empty($errors)) {
$errors = array(
Error::UNKNOWN => __('An unknown error occurred.', 'mailpoet')
Error::UNKNOWN => __('An unknown error occurred.', 'mailpoet')
);
}
return new ErrorResponse($errors, $meta, $status);

View File

@ -1,12 +1,18 @@
<?php
namespace MailPoet\API\JSON\v1;
use MailPoet\API\JSON\Endpoint as APIEndpoint;
use MailPoet\Config\AccessControl;
use MailPoet\WP\Posts as WPPosts;
if(!defined('ABSPATH')) exit;
class AutomatedLatestContent extends APIEndpoint {
public $ALC;
public $permissions = array(
'global' => AccessControl::PERMISSION_MANAGE_EMAILS
);
function __construct() {
$this->ALC = new \MailPoet\Newsletter\AutomatedLatestContent();

View File

@ -1,12 +1,19 @@
<?php
namespace MailPoet\API\JSON\v1;
use MailPoet\API\JSON\Endpoint as APIEndpoint;
use MailPoet\API\JSON\Error as APIError;
use MailPoet\Config\AccessControl;
use MailPoet\Models\CustomField;
if(!defined('ABSPATH')) exit;
class CustomFields extends APIEndpoint {
public $permissions = array(
'global' => AccessControl::PERMISSION_MANAGE_FORMS
);
function getAll() {
$collection = CustomField::orderByAsc('created_at')->findMany();
$custom_fields = array_map(function($custom_field) {

View File

@ -1,17 +1,23 @@
<?php
namespace MailPoet\API\JSON\v1;
use MailPoet\API\JSON\Endpoint as APIEndpoint;
use MailPoet\API\JSON\Error as APIError;
use MailPoet\Config\AccessControl;
use MailPoet\Form\Renderer as FormRenderer;
use MailPoet\Form\Util;
use MailPoet\Listing;
use MailPoet\Models\Form;
use MailPoet\Models\StatisticsForms;
use MailPoet\Form\Renderer as FormRenderer;
use MailPoet\Listing;
use MailPoet\Form\Util;
if(!defined('ABSPATH')) exit;
class Forms extends APIEndpoint {
public $permissions = array(
'global' => AccessControl::PERMISSION_MANAGE_FORMS
);
function get($data = array()) {
$id = (isset($data['id']) ? (int)$data['id'] : false);
$form = Form::findOne($id);

View File

@ -1,13 +1,19 @@
<?php
namespace MailPoet\API\JSON\v1;
use MailPoet\API\JSON\Endpoint as APIEndpoint;
use MailPoet\Subscribers\ImportExport\Import\MailChimp;
namespace MailPoet\API\JSON\v1;
use MailPoet\API\JSON\Endpoint as APIEndpoint;
use MailPoet\Config\AccessControl;
use MailPoet\Models\Segment;
use MailPoet\Subscribers\ImportExport\Import\MailChimp;
if(!defined('ABSPATH')) exit;
class ImportExport extends APIEndpoint {
public $permissions = array(
'global' => AccessControl::PERMISSION_MANAGE_SUBSCRIBERS
);
function getMailChimpLists($data) {
try {
$mailChimp = new MailChimp($data['api_key']);

View File

@ -1,10 +1,16 @@
<?php
namespace MailPoet\API\JSON\v1;
use MailPoet\API\JSON\Endpoint as APIEndpoint;
use MailPoet\Config\AccessControl;
if(!defined('ABSPATH')) exit;
class MP2Migrator extends APIEndpoint {
public $permissions = array(
'global' => AccessControl::PERMISSION_MANAGE_SETTINGS
);
public function __construct() {
$this->MP2Migrator = new \MailPoet\Config\MP2Migrator();

View File

@ -1,12 +1,19 @@
<?php
namespace MailPoet\API\JSON\v1;
use MailPoet\API\JSON\Endpoint as APIEndpoint;
use MailPoet\API\JSON\Error as APIError;
use MailPoet\Config\AccessControl;
use MailPoet\Mailer\MailerLog;
if(!defined('ABSPATH')) exit;
class Mailer extends APIEndpoint {
public $permissions = array(
'global' => AccessControl::PERMISSION_MANAGE_EMAILS
);
function send($data = array()) {
try {
$mailer = new \MailPoet\Mailer\Mailer(

View File

@ -1,13 +1,19 @@
<?php
namespace MailPoet\API\JSON\v1;
use MailPoet\API\JSON\Endpoint as APIEndpoint;
use MailPoet\API\JSON\Error as APIError;
use MailPoet\Config\AccessControl;
use MailPoet\Models\NewsletterTemplate;
if(!defined('ABSPATH')) exit;
class NewsletterTemplates extends APIEndpoint {
public $permissions = array(
'global' => AccessControl::PERMISSION_MANAGE_EMAILS
);
function get($data = array()) {
$id = (isset($data['id']) ? (int)$data['id'] : false);
$template = NewsletterTemplate::findOne($id);

View File

@ -1,17 +1,19 @@
<?php
namespace MailPoet\API\JSON\v1;
use Carbon\Carbon;
use MailPoet\API\JSON\Endpoint as APIEndpoint;
use MailPoet\API\JSON\Error as APIError;
use MailPoet\Config\AccessControl;
use MailPoet\Listing;
use MailPoet\Models\Newsletter;
use MailPoet\Models\NewsletterOption;
use MailPoet\Models\NewsletterOptionField;
use MailPoet\Models\NewsletterSegment;
use MailPoet\Models\NewsletterTemplate;
use MailPoet\Models\SendingQueue;
use MailPoet\Models\Setting;
use MailPoet\Models\Newsletter;
use MailPoet\Models\NewsletterTemplate;
use MailPoet\Models\NewsletterSegment;
use MailPoet\Models\NewsletterOptionField;
use MailPoet\Models\NewsletterOption;
use MailPoet\Models\Subscriber;
use MailPoet\Newsletter\Renderer\Renderer;
use MailPoet\Newsletter\Scheduler\Scheduler;
@ -23,6 +25,10 @@ if(!defined('ABSPATH')) exit;
require_once(ABSPATH . 'wp-includes/pluggable.php');
class Newsletters extends APIEndpoint {
public $permissions = array(
'global' => AccessControl::PERMISSION_MANAGE_EMAILS
);
function get($data = array()) {
$id = (isset($data['id']) ? (int)$data['id'] : false);
$newsletter = Newsletter::findOne($id);

View File

@ -1,15 +1,21 @@
<?php
namespace MailPoet\API\JSON\v1;
use MailPoet\API\JSON\Endpoint as APIEndpoint;
use MailPoet\API\JSON\Error as APIError;
use MailPoet\Models\Segment;
use MailPoet\Config\AccessControl;
use MailPoet\Listing;
use MailPoet\Models\Segment;
use MailPoet\Segments\WP;
if(!defined('ABSPATH')) exit;
class Segments extends APIEndpoint {
public $permissions = array(
'global' => AccessControl::PERMISSION_MANAGE_SEGMENTS
);
function get($data = array()) {
$id = (isset($data['id']) ? (int)$data['id'] : false);
$segment = Segment::findOne($id);

View File

@ -1,18 +1,24 @@
<?php
namespace MailPoet\API\JSON\v1;
use MailPoet\API\JSON\Endpoint as APIEndpoint;
use MailPoet\API\JSON\Error as APIError;
use MailPoet\Config\AccessControl;
use MailPoet\Mailer\Mailer;
use MailPoet\Models\Newsletter;
use MailPoet\Models\SendingQueue as SendingQueueModel;
use MailPoet\Models\Subscriber;
use MailPoet\Newsletter\Scheduler\Scheduler;
use MailPoet\Models\SendingQueue as SendingQueueModel;
use MailPoet\Util\Helpers;
if(!defined('ABSPATH')) exit;
class SendingQueue extends APIEndpoint {
public $permissions = array(
'global' => AccessControl::PERMISSION_MANAGE_EMAILS
);
function add($data = array()) {
$newsletter_id = (isset($data['newsletter_id'])
? (int)$data['newsletter_id']

View File

@ -1,11 +1,12 @@
<?php
namespace MailPoet\API\JSON\v1;
use MailPoet\API\JSON\Endpoint as APIEndpoint;
use MailPoet\API\JSON\Error as APIError;
use MailPoet\Config\AccessControl;
use MailPoet\Config\Installer;
use MailPoet\Services\Bridge;
use MailPoet\Util\License\License;
use MailPoet\WP\DateTime;
if(!defined('ABSPATH')) exit;
@ -13,6 +14,9 @@ if(!defined('ABSPATH')) exit;
class Services extends APIEndpoint {
public $bridge;
public $date_time;
public $permissions = array(
'global' => AccessControl::PERMISSION_MANAGE_SETTINGS
);
function __construct() {
$this->bridge = new Bridge();

View File

@ -1,24 +1,31 @@
<?php
namespace MailPoet\API\JSON\v1;
use MailPoet\API\JSON\Endpoint as APIEndpoint;
use MailPoet\API\JSON\Error as APIError;
use MailPoet\Config\AccessControl;
use MailPoet\Models\Setting;
use MailPoet\Services\Bridge;
if(!defined('ABSPATH')) exit;
class Settings extends APIEndpoint {
public $permissions = array(
'global' => AccessControl::PERMISSION_MANAGE_SETTINGS
);
function get() {
return $this->successResponse(Setting::getAll());
}
function set($settings = array()) {
if(empty($settings)) {
return $this->badRequest(array(
APIError::BAD_REQUEST =>
__('You have not specified any settings to be saved.', 'mailpoet')
));
return $this->badRequest(
array(
APIError::BAD_REQUEST =>
__('You have not specified any settings to be saved.', 'mailpoet')
));
} else {
foreach($settings as $name => $value) {
Setting::setValue($name, $value);

View File

@ -1,13 +1,19 @@
<?php
namespace MailPoet\API\JSON\v1;
use MailPoet\API\JSON\Endpoint as APIEndpoint;
use MailPoet\Config\AccessControl;
use MailPoet\Config\Activator;
use MailPoet\WP\Hooks;
if(!defined('ABSPATH')) exit;
class Setup extends APIEndpoint {
public $permissions = array(
'global' => AccessControl::PERMISSION_MANAGE_SETTINGS
);
function reset() {
try {
$activator = new Activator();

View File

@ -1,21 +1,22 @@
<?php
namespace MailPoet\API\JSON\v1;
use MailPoet\API\JSON\Endpoint as APIEndpoint;
use MailPoet\API\JSON\Error as APIError;
use MailPoet\API\JSON\Access as APIAccess;
use MailPoet\Form\Util\FieldNameObfuscator;
use MailPoet\Config\AccessControl;
use MailPoet\Listing;
use MailPoet\Models\Subscriber;
use MailPoet\Form\Util\FieldNameObfuscator;
use MailPoet\Models\Form;
use MailPoet\Models\StatisticsForms;
use MailPoet\Models\Subscriber;
if(!defined('ABSPATH')) exit;
class Subscribers extends APIEndpoint {
public $permissions = array(
'subscribe' => APIAccess::ALL
'global' => AccessControl::PERMISSION_MANAGE_SUBSCRIBERS,
'methods' => array('subscribe' => AccessControl::NO_ACCESS_RESTRICTION)
);
function get($data = array()) {

View File

@ -0,0 +1,104 @@
<?php
namespace MailPoet\Config;
use MailPoet\WP\Hooks as WPHooks;
if(!defined('ABSPATH')) exit;
require_once(ABSPATH . 'wp-includes/pluggable.php');
class AccessControl {
const PERMISSION_ACCESS_PLUGIN_ADMIN = 'access_plugin_admin';
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 NO_ACCESS_RESTRICTION = 'no_access_restriction';
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();
}
private function getDefaultPermissions() {
return array(
self::PERMISSION_ACCESS_PLUGIN_ADMIN => WPHooks::applyFilters(
'mailpoet_permission_access_plugin_admin',
array(
'administrator',
'editor'
)
),
self::PERMISSION_MANAGE_SETTINGS => WPHooks::applyFilters(
'mailpoet_permission_manage_settings',
array(
'administrator'
)
),
self::PERMISSION_MANAGE_EMAILS => WPHooks::applyFilters(
'mailpoet_permission_manage_emails',
array(
'administrator',
'editor'
)
),
self::PERMISSION_MANAGE_SUBSCRIBERS => WPHooks::applyFilters(
'mailpoet_permission_manage_subscribers',
array(
'administrator'
)
),
self::PERMISSION_MANAGE_FORMS => WPHooks::applyFilters(
'mailpoet_permission_manage_forms',
array(
'administrator'
)
),
self::PERMISSION_MANAGE_SEGMENTS => WPHooks::applyFilters(
'mailpoet_permission_manage_segments',
array(
'administrator'
)
),
self::PERMISSION_UPDATE_PLUGIN => WPHooks::applyFilters(
'mailpoet_permission_update_plugin',
array(
'administrator'
)
),
);
}
function getUserRoles() {
$user = wp_get_current_user();
return $user->roles;
}
function getUserCapabilities() {
$user = wp_get_current_user();
return array_keys($user->allcaps);
}
function getUserFirstCapability() {
return (!empty($this->user_capabilities)) ?
$this->user_capabilities[0] :
null;
}
function validatePermission($permission) {
if($permission === self::NO_ACCESS_RESTRICTION) return true;
if(empty($this->permissions[$permission])) return false;
$permitted_roles = array_intersect(
$this->user_roles,
$this->permissions[$permission]
);
return (!empty($permitted_roles));
}
}

View File

@ -1,10 +1,11 @@
<?php
namespace MailPoet\Config;
if(!defined('ABSPATH')) exit;
class Activator {
static function activate() {
function activate() {
$migrator = new Migrator();
$migrator->up();
@ -14,7 +15,7 @@ class Activator {
update_option('mailpoet_db_version', Env::$version);
}
static function deactivate() {
function deactivate() {
$migrator = new Migrator();
$migrator->down();
}

View File

@ -1,12 +1,11 @@
<?php
namespace MailPoet\Config;
use MailPoet\Models\Setting;
use MailPoet\Util\Url;
class Changelog {
function __construct() {
}
function init() {
$doing_ajax = (bool)(defined('DOING_AJAX') && DOING_AJAX);

View File

@ -1,4 +1,5 @@
<?php
namespace MailPoet\Config;
if(!defined('ABSPATH')) exit;
@ -31,7 +32,6 @@ class Env {
static $db_collation;
static $db_charset_collate;
static $db_timezone_offset;
static $required_permission = 'manage_options';
static function init($file, $version) {
global $wpdb;

View File

@ -13,17 +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']);
$this->access_control = new AccessControl();
}
function init() {
@ -135,7 +136,11 @@ class Initializer {
// if current db version and plugin version differ
if(version_compare($current_db_version, Env::$version) !== 0) {
Activator::activate();
if(!$this->access_control->validatePermission(AccessControl::PERMISSION_UPDATE_PLUGIN)) {
throw new \Exception(__('You do not have permission to activate/deactivate MailPoet plugin.', 'mailpoet'));
}
$activator = new Activator();
$activator->activate();
}
}
@ -185,7 +190,7 @@ 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();
}
@ -218,11 +223,11 @@ class Initializer {
}
function setupJSONAPI() {
API\API::JSON()->init();
API\API::JSON($this->access_control)->init();
}
function setupRouter() {
$router = new Router\Router();
$router = new Router\Router($this->access_control);
$router->init();
}
@ -246,7 +251,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

@ -187,8 +187,9 @@ class MP2Migrator {
*
*/
private function eraseMP3Data() {
Activator::deactivate();
Activator::activate();
$activator = new Activator();
$activator->deactivate();
$activator->activate();
$this->deleteSegments();
$this->resetMigrationCounters();

View File

@ -1,4 +1,5 @@
<?php
namespace MailPoet\Config;
use MailPoet\Cron\CronTrigger;
@ -25,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->getUserFirstCapability();
$subscribers_feature = new SubscribersFeature();
$this->subscribers_over_limit = $subscribers_feature->check();
$this->checkMailPoetAPIKey();
@ -45,134 +55,204 @@ class Menu {
}
function setup() {
if(!$this->access_control->validatePermission(AccessControl::PERMISSION_ACCESS_PLUGIN_ADMIN)) 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',
Env::$required_permission,
$main_page_slug,
$this->user_capability,
self::MAIN_PAGE_SLUG,
null,
$this->assets_url . '/img/menu_icon.png',
30
);
$newsletters_page = add_submenu_page(
$main_page_slug,
$this->setPageTitle(__('Emails', 'mailpoet')),
__('Emails', 'mailpoet'),
Env::$required_permission,
$main_page_slug,
array(
$this,
'newsletters'
)
);
// Emails page
if($this->access_control->validatePermission(AccessControl::PERMISSION_MANAGE_EMAILS)) {
$newsletters_page = add_submenu_page(
self::MAIN_PAGE_SLUG,
$this->setPageTitle(__('Emails', 'mailpoet')),
__('Emails', 'mailpoet'),
$this->user_capability,
self::MAIN_PAGE_SLUG,
array(
$this,
'newsletters'
)
);
// add limit per page to screen options
add_action('load-' . $newsletters_page, function() {
add_screen_option('per_page', array(
'label' => _x(
'Number of newsletters per page',
'newsletters per page (screen options)',
'mailpoet'
),
'option' => 'mailpoet_newsletters_per_page'
));
});
// add limit per page to screen options
add_action('load-' . $newsletters_page, function() {
add_screen_option('per_page', array(
'label' => _x(
'Number of newsletters per page',
'newsletters per page (screen options)',
'mailpoet'
),
'option' => 'mailpoet_newsletters_per_page'
));
});
$forms_page = add_submenu_page(
$main_page_slug,
$this->setPageTitle(__('Forms', 'mailpoet')),
__('Forms', 'mailpoet'),
Env::$required_permission,
'mailpoet-forms',
array(
$this,
'forms'
)
);
// add limit per page to screen options
add_action('load-' . $forms_page, function() {
add_screen_option('per_page', array(
'label' => _x(
'Number of forms per page',
'forms per page (screen options)',
'mailpoet'
),
'option' => 'mailpoet_forms_per_page'
));
});
// newsletter editor
add_submenu_page(
true,
$this->setPageTitle(__('Newsletter', 'mailpoet')),
__('Newsletter Editor', 'mailpoet'),
$this->user_capability,
'mailpoet-newsletter-editor',
array(
$this,
'newletterEditor'
)
);
}
$subscribers_page = add_submenu_page(
$main_page_slug,
$this->setPageTitle(__('Subscribers', 'mailpoet')),
__('Subscribers', 'mailpoet'),
Env::$required_permission,
'mailpoet-subscribers',
array(
$this,
'subscribers'
)
);
// add limit per page to screen options
add_action('load-' . $subscribers_page, function() {
add_screen_option('per_page', array(
'label' => _x(
'Number of subscribers per page',
'subscribers per page (screen options)',
'mailpoet'
),
'option' => 'mailpoet_subscribers_per_page'
));
});
// Forms page
if($this->access_control->validatePermission(AccessControl::PERMISSION_MANAGE_FORMS)) {
$forms_page = add_submenu_page(
self::MAIN_PAGE_SLUG,
$this->setPageTitle(__('Forms', 'mailpoet')),
__('Forms', 'mailpoet'),
$this->user_capability,
'mailpoet-forms',
array(
$this,
'forms'
)
);
$segments_page = add_submenu_page(
$main_page_slug,
$this->setPageTitle(__('Lists', 'mailpoet')),
__('Lists', 'mailpoet'),
Env::$required_permission,
'mailpoet-segments',
array(
$this,
'segments'
)
);
// add limit per page to screen options
add_action('load-' . $forms_page, function() {
add_screen_option('per_page', array(
'label' => _x(
'Number of forms per page',
'forms per page (screen options)',
'mailpoet'
),
'option' => 'mailpoet_forms_per_page'
));
});
// add limit per page to screen options
add_action('load-' . $segments_page, function() {
add_screen_option('per_page', array(
'label' => _x(
'Number of segments per page',
'segments per page (screen options)',
'mailpoet'
),
'option' => 'mailpoet_segments_per_page'
));
});
// 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(
self::MAIN_PAGE_SLUG,
$this->setPageTitle(__('Subscribers', 'mailpoet')),
__('Subscribers', 'mailpoet'),
$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(
'label' => _x(
'Number of subscribers per page',
'subscribers per page (screen options)',
'mailpoet'
),
'option' => 'mailpoet_subscribers_per_page'
));
});
// 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(
self::MAIN_PAGE_SLUG,
$this->setPageTitle(__('Lists', 'mailpoet')),
__('Lists', 'mailpoet'),
$this->user_capability,
'mailpoet-segments',
array(
$this,
'segments'
)
);
// add limit per page to screen options
add_action('load-' . $segments_page, function() {
add_screen_option('per_page', array(
'label' => _x(
'Number of segments per page',
'segments per page (screen options)',
'mailpoet'
),
'option' => 'mailpoet_segments_per_page'
));
});
}
// Settings page
if($this->access_control->validatePermission(AccessControl::PERMISSION_MANAGE_SETTINGS)) {
add_submenu_page(
self::MAIN_PAGE_SLUG,
$this->setPageTitle(__('Settings', 'mailpoet')),
__('Settings', 'mailpoet'),
$this->user_capability,
'mailpoet-settings',
array(
$this,
'settings'
)
);
}
// Help page
add_submenu_page(
$main_page_slug,
$this->setPageTitle(__('Settings', 'mailpoet')),
__('Settings', 'mailpoet'),
Env::$required_permission,
'mailpoet-settings',
array(
$this,
'settings'
)
);
add_submenu_page(
$main_page_slug,
self::MAIN_PAGE_SLUG,
$this->setPageTitle(__('Help', 'mailpoet')),
__('Help', 'mailpoet'),
Env::$required_permission,
$this->user_capability,
'mailpoet-help',
array(
$this,
@ -180,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'),
Env::$required_permission,
$this->user_capability,
'mailpoet-premium',
array(
$this,
@ -193,35 +274,12 @@ class Menu {
)
);
add_submenu_page(
'admin.php?page=mailpoet-subscribers',
$this->setPageTitle(__('Import', 'mailpoet')),
__('Import', 'mailpoet'),
Env::$required_permission,
'mailpoet-import',
array(
$this,
'import'
)
);
add_submenu_page(
true,
$this->setPageTitle(__('Export', 'mailpoet')),
__('Export', 'mailpoet'),
Env::$required_permission,
'mailpoet-export',
array(
$this,
'export'
)
);
// Welcome page
add_submenu_page(
true,
$this->setPageTitle(__('Welcome', 'mailpoet')),
__('Welcome', 'mailpoet'),
Env::$required_permission,
$this->user_capability,
'mailpoet-welcome',
array(
$this,
@ -229,23 +287,12 @@ class Menu {
)
);
add_submenu_page(
true,
$this->setPageTitle(__('Migration', 'mailpoet')),
'',
Env::$required_permission,
'mailpoet-migration',
array(
$this,
'migration'
)
);
// Update page
add_submenu_page(
true,
$this->setPageTitle(__('Update', 'mailpoet')),
__('Update', 'mailpoet'),
Env::$required_permission,
$this->user_capability,
'mailpoet-update',
array(
$this,
@ -253,27 +300,16 @@ class Menu {
)
);
// Migration page
add_submenu_page(
true,
$this->setPageTitle(__('Form Editor', 'mailpoet')),
__('Form Editor', 'mailpoet'),
Env::$required_permission,
'mailpoet-form-editor',
$this->setPageTitle(__('Migration', 'mailpoet')),
'',
$this->user_capability,
'mailpoet-migration',
array(
$this,
'formEditor'
)
);
add_submenu_page(
true,
$this->setPageTitle(__('Newsletter', 'mailpoet')),
__('Newsletter Editor', 'mailpoet'),
Env::$required_permission,
'mailpoet-newsletter-editor',
array(
$this,
'newletterEditor'
'migration'
)
);
}
@ -293,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,
@ -328,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';
@ -352,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);
@ -505,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'));
@ -597,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;
}
@ -611,9 +647,12 @@ class Menu {
true,
'MailPoet',
'MailPoet',
Env::$required_permission,
$access_control->getUserFirstCapability(),
$_REQUEST['page'],
array(__CLASS__, 'errorPageCallback')
array(
__CLASS__,
'errorPageCallback'
)
);
}
@ -624,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);
}
@ -633,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

@ -3,7 +3,7 @@
namespace MailPoet\Form;
use MailPoet\API\JSON\API;
use MailPoet\Config\Renderer;
use MailPoet\Config\Renderer as ConfigRenderer;
use MailPoet\Form\Renderer as FormRenderer;
use MailPoet\Models\Form;
use MailPoet\Util\Security;
@ -174,7 +174,7 @@ class Widget extends \WP_Widget {
$data['api_version'] = API::CURRENT_VERSION;
// render form
$renderer = new Renderer();
$renderer = new ConfigRenderer();
try {
$output = $renderer->render('form/widget.html', $data);
$output = do_shortcode($output);

View File

@ -1,6 +1,8 @@
<?php
namespace MailPoet\Router\Endpoints;
use MailPoet\Config\AccessControl;
use MailPoet\Cron\CronHelper;
use MailPoet\Cron\Daemon;
@ -17,6 +19,9 @@ class CronDaemon {
self::ACTION_PING_RESPONSE
);
public $data;
public $permissions = array(
'global' => AccessControl::NO_ACCESS_RESTRICTION
);
function __construct($data) {
$this->data = $data;

View File

@ -1,6 +1,8 @@
<?php
namespace MailPoet\Router\Endpoints;
use MailPoet\Config\AccessControl;
use MailPoet\Subscription as UserSubscription;
if(!defined('ABSPATH')) exit;
@ -16,6 +18,9 @@ class Subscription {
self::ACTION_UNSUBSCRIBE
);
public $data;
public $permissions = array(
'global' => AccessControl::NO_ACCESS_RESTRICTION
);
function __construct($data) {
$this->data = $data;

View File

@ -1,6 +1,8 @@
<?php
namespace MailPoet\Router\Endpoints;
use MailPoet\Config\AccessControl;
use MailPoet\Models\Newsletter;
use MailPoet\Models\NewsletterLink;
use MailPoet\Models\SendingQueue;
@ -20,6 +22,9 @@ class Track {
self::ACTION_OPEN
);
public $data;
public $permissions = array(
'global' => AccessControl::NO_ACCESS_RESTRICTION
);
function __construct($data) {
$this->data = $this->_processTrackData($data);
@ -38,8 +43,8 @@ class Track {
function _processTrackData($data) {
$data = (object)Links::transformUrlDataObject($data);
if(empty($data->queue_id) ||
empty($data->subscriber_id) ||
empty($data->subscriber_token)
empty($data->subscriber_id) ||
empty($data->subscriber_token)
) {
return false;
}

View File

@ -1,7 +1,8 @@
<?php
namespace MailPoet\Router\Endpoints;
use MailPoet\Config\Env;
use MailPoet\Config\AccessControl;
use MailPoet\Models\Newsletter;
use MailPoet\Models\SendingQueue;
use MailPoet\Models\Subscriber;
@ -17,8 +18,12 @@ class ViewInBrowser {
const ACTION_VIEW = 'view';
public $allowed_actions = array(self::ACTION_VIEW);
public $data;
public $permissions = array(
'global' => AccessControl::NO_ACCESS_RESTRICTION
);
function __construct($data) {
function __construct($data, AccessControl $access_control) {
$this->access_control = $access_control;
$this->data = $this->_processBrowserPreviewData($data);
}
@ -69,8 +74,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

View File

@ -1,6 +1,8 @@
<?php
namespace MailPoet\Router;
use MailPoet\Config\AccessControl;
use MailPoet\Util\Helpers;
if(!defined('ABSPATH')) exit;
@ -12,19 +14,21 @@ class Router {
public $data;
const NAME = 'mailpoet_router';
const RESPONSE_ERROR = 404;
const RESPONE_FORBIDDEN = 403;
function __construct($api_data = false) {
function __construct(AccessControl $access_control, $api_data = false) {
$api_data = ($api_data) ? $api_data : $_GET;
$this->api_request = isset($api_data[self::NAME]);
$this->endpoint = isset($api_data['endpoint']) ?
Helpers::underscoreToCamelCase($api_data['endpoint']) :
false;
$this->action = isset($api_data['action']) ?
$this->endpoint_action = isset($api_data['action']) ?
Helpers::underscoreToCamelCase($api_data['action']) :
false;
$this->data = isset($api_data['data']) ?
self::decodeRequestData($api_data['data']) :
false;
$this->access_control = $access_control;
}
function init() {
@ -33,15 +37,18 @@ class Router {
if(!$this->endpoint || !class_exists($endpoint_class)) {
return $this->terminateRequest(self::RESPONSE_ERROR, __('Invalid router endpoint', 'mailpoet'));
}
$endpoint = new $endpoint_class($this->data);
if(!method_exists($endpoint, $this->action) || !in_array($this->action, $endpoint->allowed_actions)) {
$endpoint = new $endpoint_class($this->data, $this->access_control);
if(!method_exists($endpoint, $this->endpoint_action) || !in_array($this->endpoint_action, $endpoint->allowed_actions)) {
return $this->terminateRequest(self::RESPONSE_ERROR, __('Invalid router endpoint action', 'mailpoet'));
}
if(!$this->validatePermissions($this->endpoint_action, $endpoint->permissions)) {
return $this->terminateRequest(self::RESPONE_FORBIDDEN, __('You do not have the required permissions.', 'mailpoet'));
}
do_action('mailpoet_conflict_resolver_router_url_query_parameters');
return call_user_func(
array(
$endpoint,
$this->action
$this->endpoint_action
)
);
}
@ -74,4 +81,11 @@ class Router {
status_header($code, $message);
exit;
}
function validatePermissions($endpoint_action, $permissions) {
// validate action permission if defined, otherwise validate global permission
return(!empty($permissions['actions'][$endpoint_action])) ?
$this->access_control->validatePermission($permissions['actions'][$endpoint_action]) :
$this->access_control->validatePermission($permissions['global']);
}
}

View File

@ -2,14 +2,15 @@
namespace MailPoet\Subscription;
use MailPoet\API\JSON\API;
use MailPoet\API\API as API;
use MailPoet\API\JSON\Response as APIResponse;
use MailPoet\Config\AccessControl;
use MailPoet\Util\Url as UrlHelper;
class Form {
static function onSubmit($request_data = false) {
$request_data = ($request_data) ? $request_data : $_REQUEST;
$api = new API();
$api = API::JSON(new AccessControl());
$api->setRequestData($request_data);
$form_id = (!empty($request_data['data']['form_id'])) ? (int)$request_data['data']['form_id'] : false;
$response = $api->processRoute();

View File

@ -2,10 +2,11 @@
namespace MailPoet\Test\API;
use MailPoet\API\API;
use MailPoet\Config\AccessControl;
class APITest extends \MailPoetTest {
function testItCallsJSONAPI() {
expect(API::JSON())->isInstanceOf('MailPoet\API\JSON\API');
expect(API::JSON(new AccessControl()))->isInstanceOf('MailPoet\API\JSON\API');
}
function testItCallsMPAPI() {

View File

@ -1,13 +1,19 @@
<?php
namespace MailPoet\Test\API\JSON;
use Codeception\Util\Stub;
use MailPoet\API\JSON\API;
use MailPoet\API\JSON\SuccessResponse;
use Helper\WordPressHooks as WPHooksHelper;
use MailPoet\API\API;
use MailPoet\API\JSON\API as JSONAPI;
use MailPoet\API\JSON\Response;
use MailPoet\API\JSON\Response as APIResponse;
use MailPoet\API\JSON\SuccessResponse;
use MailPoet\Config\AccessControl;
use MailPoet\WP\Hooks;
// required to be able to use wp_delete_user()
require_once(ABSPATH.'wp-admin/includes/user.php');
require_once(ABSPATH . 'wp-admin/includes/user.php');
require_once('APITestNamespacedEndpointStubV1.php');
require_once('APITestNamespacedEndpointStubV2.php');
@ -22,30 +28,16 @@ class APITest extends \MailPoetTest {
} else {
$this->wp_user_id = $wp_user_id;
}
$this->api = new API();
}
function testItChecksPermissions() {
// logged out user
expect($this->api->checkPermissions())->false();
// give administrator role to wp user
$wp_user = get_user_by('id', $this->wp_user_id);
$wp_user->add_role('administrator');
wp_set_current_user($wp_user->ID, $wp_user->user_login);
// administrator should have permission
expect($this->api->checkPermissions())->true();
$this->api = API::JSON(new AccessControl());
}
function testItCallsAPISetupAction() {
$called = false;
add_action(
Hooks::addAction(
'mailpoet_api_setup',
function ($api) use (&$called) {
function($api) use (&$called) {
$called = true;
expect($api instanceof API)->true();
expect($api instanceof JSONAPI)->true();
}
);
$api = Stub::makeEmptyExcept(
@ -136,11 +128,101 @@ class APITest extends \MailPoetTest {
);
$this->api->setRequestData($data);
$response = $this->api->processRoute();
expect($response->getData()['data'])->equals($data['api_version']);
}
function testItValidatesPermissionBeforeProcessingEndpointMethod() {
$namespace = array(
'name' => 'MailPoet\API\JSON\v1',
'version' => 'v1'
);
$data = array(
'endpoint' => 'a_p_i_test_namespaced_endpoint_stub_v1',
'method' => 'restricted',
'api_version' => 'v1',
'data' => array('test' => 'data')
);
$access_control = new AccessControl();
$access_control->user_roles = $access_control->permissions[AccessControl::PERMISSION_MANAGE_SETTINGS];
$api = Stub::make(
new \MailPoet\API\JSON\API($access_control),
array(
'validatePermissions' => function($method, $permissions) use ($data) {
expect($method)->equals($data['method']);
expect($permissions)->equals(
array(
'global' => AccessControl::NO_ACCESS_RESTRICTION,
'methods' => array(
'test' => AccessControl::NO_ACCESS_RESTRICTION,
'restricted' => AccessControl::PERMISSION_MANAGE_SETTINGS
)
)
);
return true;
}
)
);
$api->addEndpointNamespace($namespace['name'], $namespace['version']);
$api->setRequestData($data);
$response = $api->processRoute();
expect($response->getData()['data'])->equals($data['data']);
}
function testItReturnsForbiddenResponseWhenPermissionFailsValidation() {
$namespace = array(
'name' => 'MailPoet\API\JSON\v1',
'version' => 'v1'
);
$data = array(
'endpoint' => 'a_p_i_test_namespaced_endpoint_stub_v1',
'method' => 'restricted',
'api_version' => 'v1',
'data' => array('test' => 'data')
);
$access_control = new AccessControl();
$access_control->user_roles = array();
$api = new \MailPoet\API\JSON\API($access_control);
$api->addEndpointNamespace($namespace['name'], $namespace['version']);
$api->setRequestData($data);
$response = $api->processRoute();
expect($response->status)->equals(Response::STATUS_FORBIDDEN);
}
function testItValidatesGlobalPermission() {
$access_control = new AccessControl();
$permissions = array(
'global' => AccessControl::PERMISSION_MANAGE_SETTINGS,
);
$access_control->user_roles = array();
$api = new JSONAPI($access_control);
expect($api->validatePermissions(null, $permissions))->false();
$access_control->user_roles = $access_control->permissions[AccessControl::PERMISSION_MANAGE_SETTINGS];
$api = new JSONAPI($access_control);
expect($api->validatePermissions(null, $permissions))->true();
}
function testItValidatesEndpointMethodPermission() {
$access_control = new AccessControl();
$permissions = array(
'global' => null,
'methods' => array(
'test' => AccessControl::PERMISSION_MANAGE_SETTINGS
)
);
$access_control->user_roles = array();
$api = new JSONAPI($access_control);
expect($api->validatePermissions('test', $permissions))->false();
$access_control->user_roles = $access_control->permissions[AccessControl::PERMISSION_MANAGE_SETTINGS];
$api = new JSONAPI($access_control);
expect($api->validatePermissions('test', $permissions))->true();
}
function _after() {
WPHooksHelper::releaseAllHooks();
wp_delete_user($this->wp_user_id);
}
}

View File

@ -3,16 +3,24 @@
namespace MailPoet\API\JSON\v1;
use MailPoet\API\JSON\Endpoint as APIEndpoint;
use MailPoet\API\JSON\Access as APIAccess;
use MailPoet\Config\AccessControl;
if(!defined('ABSPATH')) exit;
class APITestNamespacedEndpointStubV1 extends APIEndpoint {
public $permissions = array(
'test' => APIAccess::ALL
'global' => AccessControl::NO_ACCESS_RESTRICTION,
'methods' => array(
'test' => AccessControl::NO_ACCESS_RESTRICTION,
'restricted' => AccessControl::PERMISSION_MANAGE_SETTINGS
)
);
function test($data) {
return $this->successResponse($data);
}
function restricted($data) {
return $this->successResponse($data);
}
}

View File

@ -2,14 +2,17 @@
namespace MailPoet\API\JSON\v2;
use MailPoet\API\JSON\Access as APIAccess;
use MailPoet\API\JSON\Endpoint as APIEndpoint;
use MailPoet\Config\AccessControl;
if(!defined('ABSPATH')) exit;
class APITestNamespacedEndpointStubV2 extends APIEndpoint {
public $permissions = array(
'testVersion' => APIAccess::ALL
'global' => AccessControl::NO_ACCESS_RESTRICTION,
'methods' => array(
'test' => AccessControl::NO_ACCESS_RESTRICTION
)
);
function testVersion() {

View File

@ -0,0 +1,115 @@
<?php
namespace MailPoet\Test\Config;
use Helper\WordPressHooks as WPHooksHelper;
use MailPoet\Config\AccessControl;
use MailPoet\WP\Hooks;
class AccessControlTest extends \MailPoetTest {
function testItSetsDefaultPermissionsUponInitialization() {
$default_permissions = array(
AccessControl::PERMISSION_ACCESS_PLUGIN_ADMIN => array(
'administrator',
'editor'
),
AccessControl::PERMISSION_MANAGE_SETTINGS => array(
'administrator'
),
AccessControl::PERMISSION_MANAGE_EMAILS => array(
'administrator',
'editor'
),
AccessControl::PERMISSION_MANAGE_SUBSCRIBERS => array(
'administrator'
),
AccessControl::PERMISSION_MANAGE_FORMS => array(
'administrator'
),
AccessControl::PERMISSION_MANAGE_SEGMENTS => array(
'administrator'
),
AccessControl::PERMISSION_UPDATE_PLUGIN => array(
'administrator'
)
);
$access_control = new AccessControl();
expect($access_control->permissions)->equals($default_permissions);
}
function testItAllowsSettingCustomPermissions() {
Hooks::addFilter(
'mailpoet_permission_access_plugin_admin',
function() {
return array('custom_access_plugin_admin_role');
}
);
Hooks::addFilter(
'mailpoet_permission_manage_settings',
function() {
return array('custom_manage_settings_role');
}
);
Hooks::addFilter(
'mailpoet_permission_manage_emails',
function() {
return array('custom_manage_emails_role');
}
);
Hooks::addFilter(
'mailpoet_permission_manage_subscribers',
function() {
return array('custom_manage_subscribers_role');
}
);
Hooks::addFilter(
'mailpoet_permission_manage_forms',
function() {
return array('custom_manage_forms_role');
}
);
Hooks::addFilter(
'mailpoet_permission_manage_segments',
function() {
return array('custom_manage_segments_role');
}
);
Hooks::addFilter(
'mailpoet_permission_update_plugin',
function() {
return array('custom_update_plugin_role');
}
);
$access_control = new AccessControl();
expect($access_control->permissions)->equals(
array(
AccessControl::PERMISSION_ACCESS_PLUGIN_ADMIN => array(
'custom_access_plugin_admin_role'
),
AccessControl::PERMISSION_MANAGE_SETTINGS => array(
'custom_manage_settings_role'
),
AccessControl::PERMISSION_MANAGE_EMAILS => array(
'custom_manage_emails_role'
),
AccessControl::PERMISSION_MANAGE_SUBSCRIBERS => array(
'custom_manage_subscribers_role'
),
AccessControl::PERMISSION_MANAGE_FORMS => array(
'custom_manage_forms_role'
),
AccessControl::PERMISSION_MANAGE_SEGMENTS => array(
'custom_manage_segments_role'
),
AccessControl::PERMISSION_UPDATE_PLUGIN => array(
'custom_update_plugin_role'
),
)
);
}
function _after() {
WPHooksHelper::releaseAllHooks();
}
}

View File

@ -1,7 +1,9 @@
<?php
namespace MailPoet\Test\Config;
use Codeception\Util\Stub;
use MailPoet\Config\AccessControl;
use MailPoet\Config\Menu;
use MailPoet\Config\Renderer;
use MailPoet\Config\ServicesChecker;
@ -39,7 +41,7 @@ class MenuTest extends \MailPoetTest {
function testItChecksMailpoetAPIKey() {
$renderer = Stub::make(new Renderer());
$assets_url = '';
$menu = new Menu($renderer, $assets_url);
$menu = new Menu($renderer, $assets_url, new AccessControl());
$_REQUEST['page'] = 'mailpoet-newsletters';
$checker = Stub::make(
@ -62,7 +64,7 @@ class MenuTest extends \MailPoetTest {
function testItChecksPremiumKey() {
$renderer = Stub::make(new Renderer());
$assets_url = '';
$menu = new Menu($renderer, $assets_url);
$menu = new Menu($renderer, $assets_url, new AccessControl());
$_REQUEST['page'] = 'mailpoet-newsletters';
$checker = Stub::make(

View File

@ -1,7 +1,9 @@
<?php
namespace MailPoet\Test\Router\Endpoints;
use Codeception\Util\Stub;
use MailPoet\Config\AccessControl;
use MailPoet\Models\Newsletter;
use MailPoet\Models\SendingQueue;
use MailPoet\Models\Subscriber;
@ -33,7 +35,7 @@ class ViewInBrowserTest extends \MailPoetTest {
'preview' => false
);
// instantiate class
$this->view_in_browser = new ViewInBrowser($this->browser_preview_data);
$this->view_in_browser = new ViewInBrowser($this->browser_preview_data, new AccessControl());
}
function testItAbortsWhenBrowserPreviewDataIsMissing() {
@ -123,6 +125,7 @@ class ViewInBrowserTest extends \MailPoetTest {
}
function testItDoesNotRequireWpAdministratorToBeOnProcessedListWhenPreviewIsEnabled() {
$view_in_browser = $this->view_in_browser;
$data = (object)array_merge(
$this->browser_preview_data,
array(
@ -132,19 +135,25 @@ class ViewInBrowserTest extends \MailPoetTest {
)
);
$data->preview = true;
// when WP user is not logged, false should be returned
expect($this->view_in_browser->_validateBrowserPreviewData($data))->false();
expect($view_in_browser->_validateBrowserPreviewData($data))->false();
// when WP user is logged in but does not have 'manage options' permission, false should be returned
wp_set_current_user(1);
$wp_user = wp_get_current_user();
$wp_user->remove_role('administrator');
$view_in_browser->access_control = new AccessControl();
expect($this->view_in_browser->_validateBrowserPreviewData($data))->false();
// when WP user is logged and has 'manage options' permission, data should be returned
$wp_user->add_role('administrator');
expect($this->view_in_browser->_validateBrowserPreviewData($data))->equals($data);
$view_in_browser->access_control = new AccessControl();
expect($view_in_browser->_validateBrowserPreviewData($data))->equals($data);
}
function testItSetsSubscriberToLoggedInWPUserWhenPreviewIsEnabled() {
$view_in_browser = $this->view_in_browser;
$data = (object)array_merge(
$this->browser_preview_data,
array(
@ -155,7 +164,8 @@ class ViewInBrowserTest extends \MailPoetTest {
);
$data->preview = true;
wp_set_current_user(1);
$result = $this->view_in_browser->_validateBrowserPreviewData($data);
$view_in_browser->access_control = new AccessControl();
$result = $view_in_browser->_validateBrowserPreviewData($data);
expect($result->subscriber->id)->equals(1);
}

View File

@ -1,24 +1,26 @@
<?php
namespace MailPoet\Test\Router;
use Codeception\Util\Stub;
use MailPoet\Config\AccessControl;
use MailPoet\Router\Router;
require_once('RouterTestMockEndpoint.php');
class RouterTest extends \MailPoetTest {
public $access_control;
public $router_data;
public $router;
function __construct() {
parent::__construct();
function _before() {
$this->router_data = array(
Router::NAME => '',
'endpoint' => 'router_test_mock_endpoint',
'action' => 'test',
'data' => base64_encode(json_encode(array('data' => 'dummy data')))
);
$this->router = new Router($this->router_data);
$this->access_control = new AccessControl();
$this->router = new Router($this->access_control, $this->router_data);
}
function testItCanGetAPIDataFromGetRequest() {
@ -26,10 +28,10 @@ class RouterTest extends \MailPoetTest {
$url = 'http://example.com/?' . Router::NAME . '&endpoint=view_in_browser&action=view&data='
. base64_encode(json_encode($data));
parse_str(parse_url($url, PHP_URL_QUERY), $_GET);
$router = new Router();
$router = new Router($this->access_control);
expect($router->api_request)->equals(true);
expect($router->endpoint)->equals('viewInBrowser');
expect($router->action)->equals('view');
expect($router->endpoint_action)->equals('view');
expect($router->data)->equals($data);
}
@ -37,8 +39,8 @@ class RouterTest extends \MailPoetTest {
$router_data = $this->router_data;
unset($router_data[Router::NAME]);
$router = Stub::construct(
new Router(),
array($router_data)
'\MailPoet\Router\Router',
array($this->access_control, $router_data)
);
$result = $router->init();
expect($result)->null();
@ -48,8 +50,8 @@ class RouterTest extends \MailPoetTest {
$router_data = $this->router_data;
$router_data['endpoint'] = 'invalid_endpoint';
$router = Stub::construct(
new Router(),
array($router_data),
'\MailPoet\Router\Router',
array($this->access_control, $router_data),
array(
'terminateRequest' => function($code, $error) {
return array(
@ -72,8 +74,8 @@ class RouterTest extends \MailPoetTest {
$router_data = $this->router_data;
$router_data['action'] = 'invalid_action';
$router = Stub::construct(
new Router(),
array($router_data),
'\MailPoet\Router\Router',
array($this->access_control, $router_data),
array(
'terminateRequest' => function($code, $error) {
return array(
@ -92,6 +94,87 @@ class RouterTest extends \MailPoetTest {
);
}
function testItValidatesGlobalPermission() {
$access_control = new AccessControl();
$router = $this->router;
$permissions = array(
'global' => AccessControl::PERMISSION_MANAGE_SETTINGS,
);
$access_control->user_roles = array();
$router->access_control = $access_control;
expect($router->validatePermissions(null, $permissions))->false();
$access_control->user_roles = $access_control->permissions[AccessControl::PERMISSION_MANAGE_SETTINGS];
$router->access_control = $access_control;
expect($router->validatePermissions(null, $permissions))->true();
}
function testItValidatesEndpointActionPermission() {
$access_control = new AccessControl();
$router = $this->router;
$permissions = array(
'global' => null,
'actions' => array(
'test' => AccessControl::PERMISSION_MANAGE_SETTINGS
)
);
$access_control->user_roles = array();
$router->access_control = $access_control;
expect($router->validatePermissions('test', $permissions))->false();
$access_control->user_roles = $access_control->permissions[AccessControl::PERMISSION_MANAGE_SETTINGS];
$router->access_control = $access_control;
expect($router->validatePermissions('test', $permissions))->true();
}
function testItValidatesPermissionBeforeProcessingEndpointAction() {
$router = Stub::construct(
'\MailPoet\Router\Router',
array($this->access_control, $this->router_data),
array(
'validatePermissions' => function($action, $permissions) {
expect($action)->equals($this->router_data['action']);
expect($permissions)->equals(
array(
'global' => AccessControl::NO_ACCESS_RESTRICTION
)
);
return true;
}
)
);
$result = $router->init();
expect($result)->equals(
array('data' => 'dummy data')
);
}
function testItReturnsForbiddenResponseWhenPermissionFailsValidation() {
$router = Stub::construct(
'\MailPoet\Router\Router',
array($this->access_control, $this->router_data),
array(
'validatePermissions' => false,
'terminateRequest' => function($code, $error) {
return array(
$code,
$error
);
}
)
);
$result = $router->init();
expect($result)->equals(
array(
403,
'You do not have the required permissions.'
)
);
}
function testItCallsEndpointAction() {
$data = array('data' => 'dummy data');
$result = $this->router->init();
@ -99,8 +182,7 @@ class RouterTest extends \MailPoetTest {
}
function testItExecutesUrlParameterConflictResolverAction() {
$data = array('data' => 'dummy data');
$result = $this->router->init();
$this->router->init();
expect((boolean)did_action('mailpoet_conflict_resolver_router_url_query_parameters'))->true();
}

View File

@ -2,12 +2,17 @@
namespace MailPoet\Router\Endpoints;
use MailPoet\Config\AccessControl;
class RouterTestMockEndpoint {
const ACTION_TEST = 'test';
public $allowed_actions = array(
self::ACTION_TEST
);
public $data;
public $permissions = array(
'global' => AccessControl::NO_ACCESS_RESTRICTION
);
function __construct($data) {
$this->data = $data;