You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
180 lines
5.8 KiB
180 lines
5.8 KiB
<?php |
|
|
|
class wfRESTAuthenticationController { |
|
|
|
const NONCE_AGE = 600; |
|
|
|
public static function generateNonce($tickOffset = 0) { |
|
add_filter('nonce_life', 'wfRESTAuthenticationController::nonceAge'); |
|
|
|
$i = wp_nonce_tick(); |
|
$salt = wp_salt('nonce'); |
|
$nonce = hash_hmac('sha256', ($i + $tickOffset) . '|wordfence-rest-api-auth', $salt); |
|
|
|
remove_filter('nonce_life', 'wfRESTAuthenticationController::nonceAge'); |
|
|
|
return $nonce; |
|
} |
|
|
|
public static function generateToken() { |
|
return new wfJWT(wfConfig::get('wordfenceCentralSiteID')); |
|
} |
|
|
|
public static function nonceAge() { |
|
return self::NONCE_AGE; |
|
} |
|
|
|
public function registerRoutes() { |
|
register_rest_route('wordfence/v1', '/authenticate', array( |
|
'methods' => WP_REST_Server::READABLE, |
|
'callback' => array($this, 'nonce'), |
|
)); |
|
register_rest_route('wordfence/v1', '/authenticate', array( |
|
'methods' => WP_REST_Server::CREATABLE, |
|
'callback' => array($this, 'authenticate'), |
|
)); |
|
register_rest_route('wordfence/v1', '/authenticate-premium', array( |
|
'methods' => WP_REST_Server::CREATABLE, |
|
'callback' => array($this, 'authenticatePremium'), |
|
)); |
|
} |
|
|
|
/** |
|
* @param WP_REST_Request $request |
|
* @return mixed|WP_REST_Response |
|
*/ |
|
public function nonce($request) { |
|
$response = rest_ensure_response(array( |
|
'nonce' => self::generateNonce(), |
|
'admin_url' => network_admin_url(), |
|
)); |
|
return $response; |
|
} |
|
|
|
/** |
|
* @param WP_REST_Request $request |
|
* @return mixed|WP_REST_Response |
|
*/ |
|
public function authenticate($request) { |
|
require_once(WORDFENCE_PATH . '/crypto/vendor/paragonie/sodium_compat/autoload-fast.php'); |
|
|
|
$siteID = wfConfig::get('wordfenceCentralSiteID'); |
|
if (!$siteID) { |
|
return new WP_Error('rest_forbidden_context', |
|
__('Site is not connected to Wordfence Central.', 'wordfence'), |
|
array('status' => rest_authorization_required_code())); |
|
} |
|
|
|
// verify signature. |
|
$data = $request->get_param('data'); |
|
$dataChunks = explode('|', $data, 2); |
|
if (count($dataChunks) !== 2) { |
|
return new WP_Error('rest_forbidden_context', |
|
__('Data is invalid.', 'wordfence'), |
|
array('status' => rest_authorization_required_code())); |
|
} |
|
if (!preg_match('/[0-9a-f]{64}/i', $dataChunks[0])) { |
|
return new WP_Error('rest_forbidden_context', |
|
__('Nonce format is invalid.', 'wordfence'), |
|
array('status' => rest_authorization_required_code())); |
|
} |
|
if (!preg_match('/[0-9a-f\-]{36}/i', $dataChunks[1])) { |
|
return new WP_Error('rest_forbidden_context', |
|
__('Site ID is invalid.', 'wordfence'), |
|
array('status' => rest_authorization_required_code())); |
|
} |
|
if (!hash_equals($siteID, $dataChunks[1])) { |
|
return new WP_Error('rest_forbidden_context', |
|
__('Site ID is invalid.', 'wordfence'), |
|
array('status' => rest_authorization_required_code())); |
|
} |
|
|
|
$signature = $request->get_param('signature'); |
|
$nonce1 = self::generateNonce(); |
|
$nonce2 = self::generateNonce(-1); |
|
$verfiedNonce = hash_equals($nonce1, $dataChunks[0]) || hash_equals($nonce2, $dataChunks[0]); |
|
|
|
if (!$verfiedNonce) { |
|
return new WP_Error('rest_forbidden_context', |
|
__('Nonce is invalid.', 'wordfence'), |
|
array('status' => rest_authorization_required_code())); |
|
} |
|
$signature = pack('H*', $signature); |
|
if (!ParagonIE_Sodium_Compat::crypto_sign_verify_detached($signature, $data, wfConfig::get('wordfenceCentralPK'))) { |
|
return new WP_Error('rest_forbidden_context', |
|
__('Signature is invalid.', 'wordfence'), |
|
array('status' => rest_authorization_required_code())); |
|
} |
|
|
|
$response = rest_ensure_response(array( |
|
'token' => (string) self::generateToken(), |
|
)); |
|
return $response; |
|
} |
|
|
|
/** |
|
* @param WP_REST_Request $request |
|
* @return mixed|WP_REST_Response |
|
*/ |
|
public function authenticatePremium($request) { |
|
require_once(WORDFENCE_PATH . '/crypto/vendor/paragonie/sodium_compat/autoload-fast.php'); |
|
|
|
// verify signature. |
|
$data = $request->get_param('data'); |
|
$dataChunks = explode('|', $data, 2); |
|
if (count($dataChunks) !== 2) { |
|
return new WP_Error('rest_forbidden_context', |
|
__('Data is invalid.', 'wordfence'), |
|
array('status' => rest_authorization_required_code())); |
|
} |
|
if (!preg_match('/[0-9a-f]{64}/i', $dataChunks[0])) { |
|
return new WP_Error('rest_forbidden_context', |
|
__('Nonce format is invalid.', 'wordfence'), |
|
array('status' => rest_authorization_required_code())); |
|
} |
|
if (!is_email($dataChunks[1])) { |
|
return new WP_Error('rest_forbidden_context', |
|
__('Email address is invalid.', 'wordfence'), |
|
array('status' => rest_authorization_required_code())); |
|
} |
|
|
|
$adminEmail = $dataChunks[1]; |
|
|
|
$signature = $request->get_param('signature'); |
|
$nonce1 = self::generateNonce(); |
|
$nonce2 = self::generateNonce(-1); |
|
$verfiedNonce = hash_equals($nonce1, $dataChunks[0]) || hash_equals($nonce2, $dataChunks[0]); |
|
|
|
if (!$verfiedNonce) { |
|
return new WP_Error('rest_forbidden_context', |
|
__('Nonce is invalid.', 'wordfence'), |
|
array('status' => rest_authorization_required_code())); |
|
} |
|
$signature = pack('H*', $signature); |
|
if (!ParagonIE_Sodium_Compat::crypto_sign_verify_detached($signature, $data, WORDFENCE_CENTRAL_PUBLIC_KEY)) { |
|
return new WP_Error('rest_forbidden_context', |
|
__('Signature is invalid.', 'wordfence'), |
|
array('status' => rest_authorization_required_code())); |
|
} |
|
|
|
$user_query = new WP_User_Query(array( |
|
'role' => 'administrator', |
|
'search' => $adminEmail, |
|
'search_columns' => array('user_email') |
|
)); |
|
$users = $user_query->get_results(); |
|
if (is_array($users) && count($users) === 1) { |
|
$jwt = new wfJWT('wordfence-central-premium'); |
|
$jwt->addClaims(array('email' => $adminEmail)); |
|
$response = rest_ensure_response(array( |
|
'token' => (string) $jwt, |
|
)); |
|
return $response; |
|
} |
|
|
|
return new WP_Error('rest_forbidden_context', |
|
__('Admin user with this email address not found.', 'wordfence'), |
|
array('status' => rest_authorization_required_code())); |
|
} |
|
|
|
} |