API Security

- added APIAccess class to define access levels of API Endpoints (permissions)
- use "mailpoet_token" for all nonce (just as before)
- merged setupPublic/setupAdmin methods in API in order to avoid duplication
- check permission if access level is not all
- fixed ABSPATH check in some classes
This commit is contained in:
Jonathan Labreuille
2016-10-19 14:37:18 +02:00
parent 5d0ee43921
commit 0ca5b7a79f
8 changed files with 60 additions and 16 deletions

View File

@ -13,26 +13,25 @@ class API {
private $_data = array(); private $_data = array();
function init() { function init() {
// Admin API (Ajax only) // Admin Security token
add_action(
'admin_head',
array($this, 'setToken')
);
// ajax (logged in users)
add_action( add_action(
'wp_ajax_mailpoet', 'wp_ajax_mailpoet',
array($this, 'setupAdmin') array($this, 'setupAjax')
); );
// ajax (logged out users)
// Public API (Ajax)
add_action( add_action(
'wp_ajax_nopriv_mailpoet', 'wp_ajax_nopriv_mailpoet',
array($this, 'setupPublic') array($this, 'setupAjax')
); );
} }
function setupAdmin() { function setupAjax() {
$this->getRequestData();
$this->checkPermissions();
$this->processRoute();
}
function setupPublic() {
$this->getRequestData(); $this->getRequestData();
$this->checkToken(); $this->checkToken();
$this->processRoute(); $this->processRoute();
@ -88,6 +87,18 @@ class API {
function processRoute() { function processRoute() {
try { try {
$endpoint = new $this->_endpoint_class(); $endpoint = new $this->_endpoint_class();
// 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->_method, $permissions) === false
||
$permissions[$this->_method] !== Access::ALL
) {
$this->checkPermissions();
}
$response = $endpoint->{$this->_method}($this->_data); $response = $endpoint->{$this->_method}($this->_data);
$response->send(); $response->send();
} catch(\Exception $e) { } catch(\Exception $e) {
@ -117,9 +128,7 @@ class API {
} }
function checkToken() { function checkToken() {
$action = $this->_endpoint.'_'.$this->_method; $is_valid_token = wp_verify_nonce($this->_token, 'mailpoet_token');
$is_valid_token = wp_verify_nonce($this->_token, $action);
if($is_valid_token === false) { if($is_valid_token === false) {
$error_response = new ErrorResponse( $error_response = new ErrorResponse(
@ -132,4 +141,13 @@ class API {
$error_response->send(); $error_response->send();
} }
} }
function setToken() {
$global = '<script type="text/javascript">';
$global .= 'var mailpoet_token = "';
$global .= Security::generateToken();
$global .= '";';
$global .= '</script>';
echo $global;
}
} }

12
lib/API/Access.php Normal file
View File

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

View File

@ -5,6 +5,8 @@ if(!defined('ABSPATH')) exit;
abstract class Endpoint { abstract class Endpoint {
public $permissions = array();
function successResponse( function successResponse(
$data = array(), $meta = array(), $status = Response::STATUS_OK $data = array(), $meta = array(), $status = Response::STATUS_OK
) { ) {

View File

@ -2,6 +2,7 @@
namespace MailPoet\API\Endpoints; namespace MailPoet\API\Endpoints;
use \MailPoet\API\Endpoint as APIEndpoint; use \MailPoet\API\Endpoint as APIEndpoint;
use \MailPoet\API\Error as APIError; use \MailPoet\API\Error as APIError;
use \MailPoet\API\Access as APIAccess;
use MailPoet\Listing; use MailPoet\Listing;
use MailPoet\Models\Subscriber; use MailPoet\Models\Subscriber;
@ -15,6 +16,11 @@ use MailPoet\Models\StatisticsForms;
if(!defined('ABSPATH')) exit; if(!defined('ABSPATH')) exit;
class Subscribers extends APIEndpoint { class Subscribers extends APIEndpoint {
public $permissions = array(
'subscribe' => APIAccess::ALL
);
function get($data = array()) { function get($data = array()) {
$id = (isset($data['id']) ? (int)$data['id'] : false); $id = (isset($data['id']) ? (int)$data['id'] : false);
$subscriber = Subscriber::findOne($id); $subscriber = Subscriber::findOne($id);

View File

@ -6,6 +6,8 @@ use MailPoet\Cron\Workers\SendingQueue\SendingQueue as SendingQueueWorker;
if(!defined('ABSPATH')) exit; if(!defined('ABSPATH')) exit;
require_once(ABSPATH . 'wp-includes/pluggable.php'); require_once(ABSPATH . 'wp-includes/pluggable.php');
require_once(ABSPATH . 'wp-includes/pluggable.php');
class Daemon { class Daemon {
public $daemon; public $daemon;
public $request_data; public $request_data;

View File

@ -13,6 +13,8 @@ use MailPoet\Newsletter\Scheduler\Scheduler as NewsletterScheduler;
if(!defined('ABSPATH')) exit; if(!defined('ABSPATH')) exit;
require_once(ABSPATH . 'wp-includes/pluggable.php'); require_once(ABSPATH . 'wp-includes/pluggable.php');
require_once(ABSPATH . 'wp-includes/pluggable.php');
class Scheduler { class Scheduler {
public $timer; public $timer;
const UNCONFIRMED_SUBSCRIBER_RESCHEDULE_TIMEOUT = 5; const UNCONFIRMED_SUBSCRIBER_RESCHEDULE_TIMEOUT = 5;

View File

@ -161,7 +161,7 @@ class Widget extends \WP_Widget {
); );
// generate security token // generate security token
$data['token'] = Security::generateToken('subscribers_subscribe'); $data['token'] = Security::generateToken();
// render form // render form
$renderer = new Renderer(); $renderer = new Renderer();

View File

@ -5,6 +5,8 @@ use MailPoet\Models\Setting;
if(!defined('ABSPATH')) exit; if(!defined('ABSPATH')) exit;
require_once(ABSPATH . 'wp-includes/pluggable.php'); require_once(ABSPATH . 'wp-includes/pluggable.php');
require_once(ABSPATH . 'wp-includes/pluggable.php');
class Mailer { class Mailer {
public $mailer_config; public $mailer_config;
public $sender; public $sender;