Merged in feature/MAW-855-import-code-into-aws (pull request #2)
code import from pantheon * code import from pantheon
This commit is contained in:
@@ -3068,6 +3068,23 @@
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
acknowledgeAdminUser: function (issueID) {
|
||||
var self = this;
|
||||
this.ajax('wordfence_acknowledgeAdminUser', {
|
||||
issueID: issueID
|
||||
}, function(res) {
|
||||
if (res.ok) {
|
||||
self.loadIssues(function() {
|
||||
self.colorboxModal((self.isSmallScreen ? '300px' : '400px'), __("Successfully acknowledged admin"), sprintf(__("The admin user %s will no longer show up in future scans."), res.user_login));
|
||||
});
|
||||
} else if (res.errorMsg) {
|
||||
self.loadIssues(function() {
|
||||
WFAD.colorboxError(res.errorMsg, res.tokenInvalid);
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
windowHasFocus: function() {
|
||||
if (typeof document.hasFocus === 'function') {
|
||||
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@@ -237,6 +237,7 @@ echo wfView::create('scanner/site-cleaning-high-sense')->render();
|
||||
echo wfView::create('scanner/site-cleaning-beta-sigs')->render();
|
||||
echo wfView::create('scanner/no-issues')->render();
|
||||
echo wfView::create('scanner/issue-wfUpgrade')->render();
|
||||
echo wfView::create('scanner/issue-wfUpgradeError')->render();
|
||||
echo wfView::create('scanner/issue-wfPluginUpgrade')->render();
|
||||
echo wfView::create('scanner/issue-wfThemeUpgrade')->render();
|
||||
echo wfView::create('scanner/issue-wfPluginRemoved')->render();
|
||||
|
||||
@@ -113,14 +113,14 @@ if (!isset($sendingDiagnosticEmail)) {
|
||||
))) ?></td>
|
||||
<td>
|
||||
<?php if ($infoOnly): ?>
|
||||
<div class="wf-result-info"><?php echo nl2br(esc_html($result['message'])); ?></div>
|
||||
<div class="wf-result-info"><?php echo (is_array($result['message']) && isset($result['message']['escaped']) ? $result['message']['escaped'] : nl2br(esc_html($result['message']))); ?></div>
|
||||
<?php elseif ($result['test']): ?>
|
||||
<div class="wf-result-success"><?php echo nl2br(esc_html($result['message'])); ?></div>
|
||||
<div class="wf-result-success"><?php echo (is_array($result['message']) && isset($result['message']['escaped']) ? $result['message']['escaped'] : nl2br(esc_html($result['message']))); ?></div>
|
||||
<?php else: ?>
|
||||
<div class="wf-result-error"><?php echo nl2br(esc_html($result['message'])); ?></div>
|
||||
<div class="wf-result-error"><?php echo (is_array($result['message']) && isset($result['message']['escaped']) ? $result['message']['escaped'] : nl2br(esc_html($result['message']))); ?></div>
|
||||
<?php endif ?>
|
||||
<?php if (isset($result['detail']) && !empty($result['detail'])): ?>
|
||||
<p><strong><?php esc_html_e('Additional Detail', 'wordfence'); ?></strong><br><?php echo nl2br(esc_html($result['detail'])); ?></p>
|
||||
<p><strong><?php esc_html_e('Additional Detail', 'wordfence'); ?></strong><br><?php echo (is_array($result['detail']) && isset($result['detail']['escaped']) ? $result['detail']['escaped'] : nl2br(esc_html($result['detail']))); ?></p>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -156,15 +156,15 @@ if (!isset($sendingDiagnosticEmail)) {
|
||||
))) ?></div>
|
||||
<div class="wf-right">
|
||||
<?php if ($infoOnly): ?>
|
||||
<div class="wf-result-info"><?php echo nl2br(esc_html($result['message'])); ?></div>
|
||||
<div class="wf-result-info"><?php echo (is_array($result['message']) && isset($result['message']['escaped']) ? $result['message']['escaped'] : nl2br(esc_html($result['message']))); ?></div>
|
||||
<?php elseif ($result['test']): ?>
|
||||
<div class="wf-result-success"><?php echo nl2br(esc_html($result['message'])); ?></div>
|
||||
<div class="wf-result-success"><?php echo (is_array($result['message']) && isset($result['message']['escaped']) ? $result['message']['escaped'] : nl2br(esc_html($result['message']))); ?></div>
|
||||
<?php else: ?>
|
||||
<div class="wf-result-error"><?php echo nl2br(esc_html($result['message'])); ?></div>
|
||||
<div class="wf-result-error"><?php echo (is_array($result['message']) && isset($result['message']['escaped']) ? $result['message']['escaped'] : nl2br(esc_html($result['message']))); ?></div>
|
||||
<?php endif ?>
|
||||
<?php if (isset($result['detail']) && !empty($result['detail'])): ?>
|
||||
<p><a href="#" onclick="jQuery('#wf-diagnostics-detail-<?php echo esc_attr($key); ?>').show(); jQuery(this).hide(); return false;" role="button"><?php esc_html_e('View Additional Detail', 'wordfence'); ?></a></p>
|
||||
<pre class="wf-pre wf-split-word" id="wf-diagnostics-detail-<?php echo esc_attr($key); ?>" style="max-width: 600px; display: none;"><?php echo esc_html($result['detail']); ?></pre>
|
||||
<pre class="wf-pre wf-split-word" id="wf-diagnostics-detail-<?php echo esc_attr($key); ?>" style="max-width: 600px; display: none;"><?php echo (is_array($result['detail']) && isset($result['detail']['escaped']) ? $result['detail']['escaped'] : nl2br(esc_html($result['detail']))); ?></pre>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
@@ -37,6 +37,17 @@ class wfCentralAPIRequest {
|
||||
$this->body = $body;
|
||||
$this->args = $args;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles an internal error when making a Central API request (e.g., a second sodium_compat library with an
|
||||
* incompatible interface loading instead or in addition to ours).
|
||||
*
|
||||
* @param Exception|Throwable $e
|
||||
*/
|
||||
public static function handleInternalCentralAPIError($e) {
|
||||
error_log('Wordfence encountered an internal Central API error: ' . $e->getMessage());
|
||||
error_log('Wordfence stack trace: ' . $e->getTraceAsString());
|
||||
}
|
||||
|
||||
public function execute() {
|
||||
$args = array(
|
||||
@@ -259,7 +270,7 @@ class wfCentralAuthenticatedAPIRequest extends wfCentralAPIRequest {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (empty($token)) {
|
||||
if (empty($token)) {
|
||||
if (isset($e)) {
|
||||
throw $e;
|
||||
} else {
|
||||
@@ -406,9 +417,16 @@ class wfCentral {
|
||||
try {
|
||||
$response = $request->execute();
|
||||
return $response;
|
||||
} catch (wfCentralAPIException $e) {
|
||||
}
|
||||
catch (wfCentralAPIException $e) {
|
||||
error_log($e);
|
||||
}
|
||||
catch (Exception $e) {
|
||||
wfCentralAPIRequest::handleInternalCentralAPIError($e);
|
||||
}
|
||||
catch (Throwable $t) {
|
||||
wfCentralAPIRequest::handleInternalCentralAPIError($t);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -437,9 +455,16 @@ class wfCentral {
|
||||
try {
|
||||
$response = $request->execute();
|
||||
return $response;
|
||||
} catch (wfCentralAPIException $e) {
|
||||
}
|
||||
catch (wfCentralAPIException $e) {
|
||||
error_log($e);
|
||||
}
|
||||
catch (Exception $e) {
|
||||
wfCentralAPIRequest::handleInternalCentralAPIError($e);
|
||||
}
|
||||
catch (Throwable $t) {
|
||||
wfCentralAPIRequest::handleInternalCentralAPIError($t);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -459,9 +484,16 @@ class wfCentral {
|
||||
try {
|
||||
$response = $request->execute();
|
||||
return $response;
|
||||
} catch (wfCentralAPIException $e) {
|
||||
}
|
||||
catch (wfCentralAPIException $e) {
|
||||
error_log($e);
|
||||
}
|
||||
catch (Exception $e) {
|
||||
wfCentralAPIRequest::handleInternalCentralAPIError($e);
|
||||
}
|
||||
catch (Throwable $t) {
|
||||
wfCentralAPIRequest::handleInternalCentralAPIError($t);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -484,9 +516,16 @@ class wfCentral {
|
||||
try {
|
||||
$response = $request->execute();
|
||||
return $response;
|
||||
} catch (wfCentralAPIException $e) {
|
||||
}
|
||||
catch (wfCentralAPIException $e) {
|
||||
error_log($e);
|
||||
}
|
||||
catch (Exception $e) {
|
||||
wfCentralAPIRequest::handleInternalCentralAPIError($e);
|
||||
}
|
||||
catch (Throwable $t) {
|
||||
wfCentralAPIRequest::handleInternalCentralAPIError($t);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -501,9 +540,13 @@ class wfCentral {
|
||||
|
||||
try {
|
||||
$request->execute();
|
||||
} catch (Exception $e) {
|
||||
}
|
||||
catch (Exception $e) {
|
||||
// We can safely ignore an error here for now.
|
||||
}
|
||||
catch (Throwable $t) {
|
||||
wfCentralAPIRequest::handleInternalCentralAPIError($t);
|
||||
}
|
||||
}
|
||||
|
||||
protected static $syncConfig = true;
|
||||
@@ -540,9 +583,16 @@ class wfCentral {
|
||||
try {
|
||||
$response = $request->execute();
|
||||
return $response;
|
||||
} catch (wfCentralAPIException $e) {
|
||||
}
|
||||
catch (wfCentralAPIException $e) {
|
||||
error_log($e);
|
||||
}
|
||||
catch (Exception $e) {
|
||||
wfCentralAPIRequest::handleInternalCentralAPIError($e);
|
||||
}
|
||||
catch (Throwable $t) {
|
||||
wfCentralAPIRequest::handleInternalCentralAPIError($t);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -598,6 +648,14 @@ class wfCentral {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
catch (Exception $e) {
|
||||
wfCentralAPIRequest::handleInternalCentralAPIError($e);
|
||||
return false;
|
||||
}
|
||||
catch (Throwable $t) {
|
||||
wfCentralAPIRequest::handleInternalCentralAPIError($t);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
$wfdb = new wfDB();
|
||||
|
||||
@@ -699,7 +699,7 @@ class wfConfig {
|
||||
|
||||
global $wpdb;
|
||||
$dbh = $wpdb->dbh;
|
||||
$useMySQLi = (is_object($dbh) && $wpdb->use_mysqli && wfConfig::get('allowMySQLi', true) && WORDFENCE_ALLOW_DIRECT_MYSQLI);
|
||||
$useMySQLi = wfUtils::useMySQLi();
|
||||
|
||||
if (!self::$tableExists) {
|
||||
return;
|
||||
|
||||
@@ -671,16 +671,21 @@ class wfDiagnostic
|
||||
$message = __('wp_remote_post() test back to this server failed! Response was: ', 'wordfence') . $result->get_error_message();
|
||||
}
|
||||
else {
|
||||
$message = __('wp_remote_post() test back to this server failed! Response was: ', 'wordfence') . $result['response']['code'] . " " . $result['response']['message'] . "\n";
|
||||
$message .= __('This additional info may help you diagnose the issue. The response headers we received were:', 'wordfence') . "\n";
|
||||
$message = __('wp_remote_post() test back to this server failed! Response was: ', 'wordfence') . '<br>' . $result['response']['code'] . " " . $result['response']['message'] . '<br><br>';
|
||||
if ($this->_detectBlockedByCloudflare($result)) {
|
||||
$message .= __('Cloudflare appears to be blocking your site from connecting to itself.', 'wordfence') . '<br>' . sprintf(' <a href="%s" target="_blank" rel="noopener noreferrer">', wfSupportController::esc_supportURL(wfSupportController::ITEM_DIAGNOSTICS_CLOUDFLARE_BLOCK)) . __('Get help with Cloudflare compatibility', 'wordfence') . '</a><br><br>';
|
||||
}
|
||||
$message .= __('This additional info may help you diagnose the issue. The response headers we received were:', 'wordfence') . '<br><br>';
|
||||
if (isset($result['http_response']) && is_object($result['http_response']) && method_exists($result['http_response'], 'get_response_object') && is_object($result['http_response']->get_response_object()) && property_exists($result['http_response']->get_response_object(), 'raw')) {
|
||||
$detail = str_replace("\r\n", "\n", $result['http_response']->get_response_object()->raw);
|
||||
}
|
||||
}
|
||||
|
||||
$message = wp_kses($message, array('a' => array('href' => array(), 'target' => array(), 'rel' => array()), 'span' => array('class' => array()), 'em' => array(), 'code' => array(), 'br' => array()));
|
||||
|
||||
return array(
|
||||
'test' => false,
|
||||
'message' => $message,
|
||||
'message' => array('escaped' => $message),
|
||||
'detail' => $detail,
|
||||
);
|
||||
}
|
||||
@@ -698,11 +703,14 @@ class wfDiagnostic
|
||||
$handle = $interceptor->getHandle();
|
||||
$errorNumber = curl_errno($handle);
|
||||
if ($errorNumber === 6 /* COULDNT_RESOLVE_HOST */) {
|
||||
$detail = sprintf(/* translators: error message from failed request */ __('This likely indicates that the server either does not support IPv6 or does not have an IPv6 address assigned or associated with the domain. Original error message: %s', 'wordfence'), is_array($result['message']) ? $result['message']['escaped'] : $result['message']);
|
||||
$detail = wp_kses($detail, array('a' => array('href' => array(), 'target' => array(), 'rel' => array()), 'span' => array('class' => array()), 'em' => array(), 'code' => array(), 'br' => array()));
|
||||
|
||||
return array(
|
||||
'test' => false,
|
||||
'infoOnly' => true,
|
||||
'message' => __('IPv6 DNS resolution failed', 'wordfence'),
|
||||
'detail' => sprintf(/* translators: error message from failed request */ __('This likely indicates that the server either does not support IPv6 or does not have an IPv6 address assigned or associated with the domain. Original error message: %s', 'wordfence'), $result['message'])
|
||||
'detail' => array('escaped' => $detail),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -721,6 +729,34 @@ class wfDiagnostic
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks for markers in $result that indicate it was challenged/blocked by Cloudflare.
|
||||
*
|
||||
* @param $result
|
||||
* @return bool
|
||||
*/
|
||||
private function _detectBlockedByCloudflare($result) {
|
||||
$headers = $result['headers'];
|
||||
if (isset($headers['cf-mitigated']) && strtolower($headers['cf-mitigated']) == 'challenge' /* managed challenge */) { //$headers is an instance of Requests_Utility_CaseInsensitiveDictionary
|
||||
return true;
|
||||
}
|
||||
|
||||
$body = $result['body'];
|
||||
$search = array(
|
||||
'/cdn-cgi/styles/challenges.css', //managed challenge
|
||||
'/cdn-cgi/challenge-platform', //managed challenge
|
||||
'/cdn-cgi/styles/cf.errors.css', //block
|
||||
'cf-error-details', //block
|
||||
'Cloudflare Ray ID', //block
|
||||
);
|
||||
foreach ($search as $s) {
|
||||
if (stripos($body, $s) !== false) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function serverIP() {
|
||||
$serverIPs = wfUtils::serverIPs();
|
||||
return array(
|
||||
|
||||
@@ -78,13 +78,14 @@ class wfIssues {
|
||||
'wfPluginRemoved' => wfIssues::SEVERITY_CRITICAL,
|
||||
'wfPluginUpgrade' => wfIssues::SEVERITY_MEDIUM,
|
||||
'wfThemeUpgrade' => wfIssues::SEVERITY_MEDIUM,
|
||||
'wfUpgradeError' => wfIssues::SEVERITY_MEDIUM,
|
||||
'wfUpgrade' => wfIssues::SEVERITY_HIGH,
|
||||
'wpscan_directoryList' => wfIssues::SEVERITY_HIGH,
|
||||
'wpscan_fullPathDiscl' => wfIssues::SEVERITY_HIGH,
|
||||
);
|
||||
|
||||
public static function validIssueTypes() {
|
||||
return array('checkHowGetIPs', 'checkSpamIP', 'commentBadURL', 'configReadable', 'coreUnknown', 'database', 'diskSpace', 'wafStatus', 'easyPassword', 'file', 'geoipSupport', 'knownfile', 'optionBadURL', 'postBadTitle', 'postBadURL', 'publiclyAccessible', 'spamvertizeCheck', 'suspiciousAdminUsers', 'timelimit', 'wfPluginAbandoned', 'wfPluginRemoved', 'wfPluginUpgrade', 'wfPluginVulnerable', 'wfThemeUpgrade', 'wfUpgrade', 'wpscan_directoryList', 'wpscan_fullPathDiscl', 'skippedPaths');
|
||||
return array('checkHowGetIPs', 'checkSpamIP', 'commentBadURL', 'configReadable', 'coreUnknown', 'database', 'diskSpace', 'wafStatus', 'easyPassword', 'file', 'geoipSupport', 'knownfile', 'optionBadURL', 'postBadTitle', 'postBadURL', 'publiclyAccessible', 'spamvertizeCheck', 'suspiciousAdminUsers', 'timelimit', 'wfPluginAbandoned', 'wfPluginRemoved', 'wfPluginUpgrade', 'wfPluginVulnerable', 'wfThemeUpgrade', 'wfUpgradeError', 'wfUpgrade', 'wpscan_directoryList', 'wpscan_fullPathDiscl', 'skippedPaths');
|
||||
}
|
||||
|
||||
public static function statusPrep(){
|
||||
@@ -530,13 +531,13 @@ class wfIssues {
|
||||
}
|
||||
|
||||
public function deleteAllUpdateIssues() {
|
||||
$issues = $this->getDB()->querySelect("SELECT id, status, ignoreP, ignoreC FROM {$this->issuesTable} WHERE status = 'new' AND (type = 'wfUpgrade' OR type = 'wfPluginUpgrade' OR type = 'wfThemeUpgrade')");
|
||||
$issues = $this->getDB()->querySelect("SELECT id, status, ignoreP, ignoreC FROM {$this->issuesTable} WHERE status = 'new' AND (type = 'wfUpgrade' OR type = 'wfUpgradeError' OR type = 'wfPluginUpgrade' OR type = 'wfThemeUpgrade')");
|
||||
$this->clearEmailedStatus($issues);
|
||||
|
||||
$this->getDB()->queryWrite("DELETE FROM {$this->issuesTable} WHERE status = 'new' AND (type = 'wfUpgrade' OR type = 'wfPluginUpgrade' OR type = 'wfThemeUpgrade')");
|
||||
$this->getDB()->queryWrite("DELETE FROM {$this->issuesTable} WHERE status = 'new' AND (type = 'wfUpgrade' OR type = 'wfUpgradeError' OR type = 'wfPluginUpgrade' OR type = 'wfThemeUpgrade')");
|
||||
|
||||
if (wfCentral::isConnected()) {
|
||||
wfCentral::deleteIssueTypes(array('wfUpgrade', 'wfPluginUpgrade', 'wfThemeUpgrade'));
|
||||
wfCentral::deleteIssueTypes(array('wfUpgrade', 'wfUpgradeError', 'wfPluginUpgrade', 'wfThemeUpgrade'));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1625,7 +1625,7 @@ class wfScanEngine {
|
||||
$counter = 0;
|
||||
$query = "select ID from " . $wpdb->users;
|
||||
$dbh = $wpdb->dbh;
|
||||
$useMySQLi = (is_object($dbh) && $wpdb->use_mysqli && wfConfig::get('allowMySQLi', true) && WORDFENCE_ALLOW_DIRECT_MYSQLI);
|
||||
$useMySQLi = wfUtils::useMySQLi();
|
||||
if ($useMySQLi) { //If direct-access MySQLi is available, we use it to minimize the memory footprint instead of letting it fetch everything into an array first
|
||||
$result = $dbh->query($query);
|
||||
if (!is_object($result)) {
|
||||
@@ -1889,36 +1889,50 @@ class wfScanEngine {
|
||||
|
||||
foreach ($this->pluginRepoStatus as $slug => $status) {
|
||||
if ($status === false) {
|
||||
$result = plugins_api('plugin_information', array(
|
||||
'slug' => $slug,
|
||||
'fields' => array(
|
||||
'short_description' => false,
|
||||
'description' => false,
|
||||
'sections' => false,
|
||||
'tested' => true,
|
||||
'requires' => true,
|
||||
'rating' => false,
|
||||
'ratings' => false,
|
||||
'downloaded' => false,
|
||||
'downloadlink' => false,
|
||||
'last_updated' => true,
|
||||
'added' => false,
|
||||
'tags' => false,
|
||||
'compatibility' => true,
|
||||
'homepage' => true,
|
||||
'versions' => false,
|
||||
'donate_link' => false,
|
||||
'reviews' => false,
|
||||
'banners' => false,
|
||||
'icons' => false,
|
||||
'active_installs' => false,
|
||||
'group' => false,
|
||||
'contributors' => false,
|
||||
),
|
||||
));
|
||||
unset($result->versions);
|
||||
unset($result->screenshots);
|
||||
$this->pluginRepoStatus[$slug] = $result;
|
||||
try {
|
||||
$result = plugins_api('plugin_information', array(
|
||||
'slug' => $slug,
|
||||
'fields' => array(
|
||||
'short_description' => false,
|
||||
'description' => false,
|
||||
'sections' => false,
|
||||
'tested' => true,
|
||||
'requires' => true,
|
||||
'rating' => false,
|
||||
'ratings' => false,
|
||||
'downloaded' => false,
|
||||
'downloadlink' => false,
|
||||
'last_updated' => true,
|
||||
'added' => false,
|
||||
'tags' => false,
|
||||
'compatibility' => true,
|
||||
'homepage' => true,
|
||||
'versions' => false,
|
||||
'donate_link' => false,
|
||||
'reviews' => false,
|
||||
'banners' => false,
|
||||
'icons' => false,
|
||||
'active_installs' => false,
|
||||
'group' => false,
|
||||
'contributors' => false,
|
||||
),
|
||||
));
|
||||
unset($result->versions);
|
||||
unset($result->screenshots);
|
||||
$this->pluginRepoStatus[$slug] = $result;
|
||||
}
|
||||
catch (Exception $e) {
|
||||
error_log(sprintf('Caught exception while attempting to refresh update status for slug %s: %s', $slug, $e->getMessage()));
|
||||
$this->pluginRepoStatus[$slug] = false;
|
||||
wfConfig::set(wfUpdateCheck::LAST_UPDATE_CHECK_ERROR_KEY, sprintf('%s [%s]', $e->getMessage(), $slug), false);
|
||||
wfConfig::set(wfUpdateCheck::LAST_UPDATE_CHECK_ERROR_SLUG_KEY, $slug, false);
|
||||
}
|
||||
catch (Throwable $t) {
|
||||
error_log(sprintf('Caught error while attempting to refresh update status for slug %s: %s', $slug, $t->getMessage()));
|
||||
$this->pluginRepoStatus[$slug] = false;
|
||||
wfConfig::set(wfUpdateCheck::LAST_UPDATE_CHECK_ERROR_KEY, sprintf('%s [%s]', $t->getMessage(), $slug), false);
|
||||
wfConfig::set(wfUpdateCheck::LAST_UPDATE_CHECK_ERROR_SLUG_KEY, $slug, false);
|
||||
}
|
||||
|
||||
$this->forkIfNeeded();
|
||||
}
|
||||
@@ -1929,7 +1943,39 @@ class wfScanEngine {
|
||||
$haveIssues = wfIssues::STATUS_SECURE;
|
||||
|
||||
if (!$this->isFullScan()) {
|
||||
$this->deleteNewIssues(array('wfUpgrade', 'wfPluginUpgrade', 'wfThemeUpgrade'));
|
||||
$this->deleteNewIssues(array('wfUpgradeError', 'wfUpgrade', 'wfPluginUpgrade', 'wfThemeUpgrade'));
|
||||
}
|
||||
|
||||
if ($lastError = wfConfig::get(wfUpdateCheck::LAST_UPDATE_CHECK_ERROR_KEY)) {
|
||||
$lastSlug = wfConfig::get(wfUpdateCheck::LAST_UPDATE_CHECK_ERROR_SLUG_KEY);
|
||||
$longMsg = sprintf(/* translators: error message. */ __("The update check performed during the scan encountered an error: %s", 'wordfence'), esc_html($lastError));
|
||||
if ($lastSlug === false) {
|
||||
$longMsg .= ' ' . __('Wordfence cannot detect if the installed plugins and themes are up to date. This might be caused by a PHP compatibility issue in one or more plugins/themes.', 'wordfence');
|
||||
}
|
||||
else {
|
||||
$longMsg .= ' ' . __('Wordfence cannot detect if this plugin/theme is up to date. This might be caused by a PHP compatibility issue in the plugin.', 'wordfence');
|
||||
}
|
||||
$longMsg .= ' ' . sprintf(
|
||||
/* translators: Support URL. */
|
||||
__('<a href="%s" target="_blank" rel="noopener noreferrer">Get more information.<span class="screen-reader-text"> (' . esc_html__('opens in new tab', 'wordfence') . ')</span></a>', 'wordfence'), wfSupportController::esc_supportURL(wfSupportController::ITEM_SCAN_RESULT_UPDATE_CHECK_FAILED));
|
||||
|
||||
$ignoreKey = ($lastSlug === false ? 'wfUpgradeErrorGeneral' : sprintf('wfUpgradeError-%s', $lastSlug));
|
||||
|
||||
$added = $this->addIssue(
|
||||
'wfUpgradeError',
|
||||
wfIssues::SEVERITY_MEDIUM,
|
||||
$ignoreKey,
|
||||
$ignoreKey,
|
||||
($lastSlug === false ? __("Update Check Encountered Error", 'wordfence') : sprintf(/* translators: plugin/theme slug. */ __("Update Check Encountered Error on '%s'", 'wordfence'), esc_html($lastSlug))),
|
||||
$longMsg,
|
||||
array()
|
||||
);
|
||||
if ($added == wfIssues::ISSUE_ADDED || $added == wfIssues::ISSUE_UPDATED) {
|
||||
$haveIssues = wfIssues::STATUS_PROBLEM;
|
||||
}
|
||||
else if ($haveIssues != wfIssues::STATUS_PROBLEM && ($added == wfIssues::ISSUE_IGNOREP || $added == wfIssues::ISSUE_IGNOREC)) {
|
||||
$haveIssues = wfIssues::STATUS_IGNORED;
|
||||
}
|
||||
}
|
||||
|
||||
// WordPress core updates needed
|
||||
@@ -2029,8 +2075,12 @@ class wfScanEngine {
|
||||
$statusArray['version'] = null;
|
||||
wordfence::status(3, 'error', "Unable to determine version for plugin $slug");
|
||||
}
|
||||
$lastUpdateTimestamp = strtotime($statusArray['last_updated']);
|
||||
if ($lastUpdateTimestamp > 0 && (time() - $lastUpdateTimestamp) > 63072000 /* ~2 years */) {
|
||||
|
||||
if (array_key_exists('last_updated', $statusArray) &&
|
||||
is_string($statusArray['last_updated']) &&
|
||||
($lastUpdateTimestamp = strtotime($statusArray['last_updated'])) &&
|
||||
(time() - $lastUpdateTimestamp) > 63072000 /* ~2 years */) {
|
||||
|
||||
$statusArray['dateUpdated'] = wfUtils::formatLocalTime(get_option('date_format'), $lastUpdateTimestamp);
|
||||
$severity = wfIssues::SEVERITY_MEDIUM;
|
||||
$statusArray['abandoned'] = true;
|
||||
@@ -2433,7 +2483,6 @@ class wfScanEngine {
|
||||
wfConfig::set('wfKillRequested', 0, wfConfig::DONT_AUTOLOAD);
|
||||
wordfence::status(4, 'info', __("Entering start scan routine", 'wordfence'));
|
||||
if (wfScanner::shared()->isRunning()) {
|
||||
wfUtils::getScanFileError();
|
||||
return __("A scan is already running. Use the stop scan button if you would like to terminate the current scan.", 'wordfence');
|
||||
}
|
||||
wfConfig::set('currentCronKey', ''); //Ensure the cron key is cleared
|
||||
|
||||
@@ -73,8 +73,8 @@ class wfScanMonitor {
|
||||
if ($lastAttempt === null || $now - $lastAttempt < self::SCAN_START_TIMEOUT)
|
||||
return;
|
||||
$lastSuccess = wfConfig::get(self::CONFIG_LAST_SUCCESS);
|
||||
self::setRemainingResumeAttempts(--$remainingAttempts);
|
||||
if ($lastSuccess === null || $lastAttempt > $lastSuccess) {
|
||||
self::setRemainingResumeAttempts(--$remainingAttempts);
|
||||
wordfence::status(2, 'info', sprintf(__('Attempting to resume scan stage (%d attempt(s) remaining)...', 'wordfence'), $remainingAttempts));
|
||||
self::resumeScan();
|
||||
}
|
||||
|
||||
@@ -149,6 +149,7 @@ class wfSupportController {
|
||||
const ITEM_SCAN_RESULT_PUBLIC_CONFIG = 'scan-result-public-config';
|
||||
const ITEM_SCAN_RESULT_PLUGIN_ABANDONED = 'scan-result-plugin-abandoned';
|
||||
const ITEM_SCAN_RESULT_PLUGIN_REMOVED = 'scan-result-plugin-removed';
|
||||
const ITEM_SCAN_RESULT_UPDATE_CHECK_FAILED = 'scan-result-update-check-failed';
|
||||
const ITEM_SCAN_RESULT_OPTION_MALWARE_URL = 'scan-result-option-malware-url';
|
||||
const ITEM_SCAN_RESULT_GEOIP_UPDATE = 'scan-result-geoip-update';
|
||||
const ITEM_SCAN_RESULT_WAF_DISABLED = 'scan-result-waf-disabled';
|
||||
@@ -178,6 +179,7 @@ class wfSupportController {
|
||||
const ITEM_DIAGNOSTICS_OPTION_BETA_TDF = 'diagnostics-option-beta-tdf';
|
||||
const ITEM_DIAGNOSTICS_OPTION_WORDFENCE_TRANSLATIONS = 'diagnostics-option-wordfence-translations';
|
||||
const ITEM_DIAGNOSTICS_IPV6 = 'diagnostics-ipv6';
|
||||
const ITEM_DIAGNOSTICS_CLOUDFLARE_BLOCK = 'compatibility-cloudflare';
|
||||
|
||||
const ITEM_MODULE_LOGIN_SECURITY = 'module-login-security';
|
||||
const ITEM_MODULE_LOGIN_SECURITY_2FA = 'module-login-security-2fa';
|
||||
@@ -341,6 +343,7 @@ class wfSupportController {
|
||||
case self::ITEM_SCAN_RESULT_PUBLIC_CONFIG:
|
||||
case self::ITEM_SCAN_RESULT_PLUGIN_ABANDONED:
|
||||
case self::ITEM_SCAN_RESULT_PLUGIN_REMOVED:
|
||||
case self::ITEM_SCAN_RESULT_UPDATE_CHECK_FAILED:
|
||||
case self::ITEM_SCAN_RESULT_OPTION_MALWARE_URL:
|
||||
case self::ITEM_SCAN_RESULT_GEOIP_UPDATE:
|
||||
case self::ITEM_SCAN_RESULT_WAF_DISABLED:
|
||||
@@ -370,6 +373,7 @@ class wfSupportController {
|
||||
case self::ITEM_DIAGNOSTICS_OPTION_BETA_TDF:
|
||||
case self::ITEM_DIAGNOSTICS_OPTION_WORDFENCE_TRANSLATIONS:
|
||||
case self::ITEM_DIAGNOSTICS_IPV6:
|
||||
case self::ITEM_DIAGNOSTICS_CLOUDFLARE_BLOCK:
|
||||
|
||||
case self::ITEM_MODULE_LOGIN_SECURITY:
|
||||
case self::ITEM_MODULE_LOGIN_SECURITY_2FA:
|
||||
|
||||
@@ -6,6 +6,9 @@ class wfUpdateCheck {
|
||||
const VULN_SEVERITY_MEDIUM = 40;
|
||||
const VULN_SEVERITY_LOW = 1;
|
||||
const VULN_SEVERITY_NONE = 0;
|
||||
|
||||
const LAST_UPDATE_CHECK_ERROR_KEY = 'lastUpdateCheckError';
|
||||
const LAST_UPDATE_CHECK_ERROR_SLUG_KEY = 'lastUpdateCheckErrorSlug';
|
||||
|
||||
private $needs_core_update = false;
|
||||
private $core_update_version = 0;
|
||||
@@ -169,6 +172,11 @@ class wfUpdateCheck {
|
||||
* @return $this
|
||||
*/
|
||||
public function checkAllUpdates($useCachedValued = true) {
|
||||
if (!$useCachedValued) {
|
||||
wfConfig::remove(self::LAST_UPDATE_CHECK_ERROR_KEY);
|
||||
wfConfig::remove(self::LAST_UPDATE_CHECK_ERROR_SLUG_KEY);
|
||||
}
|
||||
|
||||
return $this->checkCoreUpdates($useCachedValued)
|
||||
->checkPluginUpdates($useCachedValued)
|
||||
->checkThemeUpdates($useCachedValued);
|
||||
@@ -189,7 +197,7 @@ class wfUpdateCheck {
|
||||
require_once(ABSPATH . 'wp-admin/includes/update.php');
|
||||
}
|
||||
|
||||
include(ABSPATH . WPINC . '/version.php'); //defines $wp_version
|
||||
include(ABSPATH . WPINC . '/version.php'); /** @var $wp_version */
|
||||
|
||||
$update_core = get_preferred_from_update_core();
|
||||
if ($useCachedValued && isset($update_core->last_checked) && isset($update_core->version_checked) && 12 * HOUR_IN_SECONDS > (time() - $update_core->last_checked) && $update_core->version_checked == $wp_version) { //Duplicate of _maybe_update_core, which is a private call
|
||||
@@ -276,7 +284,19 @@ class wfUpdateCheck {
|
||||
return $update_plugins;
|
||||
if (!function_exists('wp_update_plugins'))
|
||||
require_once(ABSPATH . WPINC . '/update.php');
|
||||
wp_update_plugins();
|
||||
try {
|
||||
wp_update_plugins();
|
||||
}
|
||||
catch (Exception $e) {
|
||||
wfConfig::set(self::LAST_UPDATE_CHECK_ERROR_KEY, $e->getMessage(), false);
|
||||
wfConfig::remove(self::LAST_UPDATE_CHECK_ERROR_SLUG_KEY);
|
||||
error_log('Caught exception while attempting to refresh plugin update status: ' . $e->getMessage());
|
||||
}
|
||||
catch (Throwable $t) {
|
||||
wfConfig::set(self::LAST_UPDATE_CHECK_ERROR_KEY, $t->getMessage(), false);
|
||||
wfConfig::remove(self::LAST_UPDATE_CHECK_ERROR_SLUG_KEY);
|
||||
error_log('Caught error while attempting to refresh plugin update status: ' . $t->getMessage());
|
||||
}
|
||||
return get_site_transient('update_plugins');
|
||||
}
|
||||
|
||||
@@ -293,7 +313,7 @@ class wfUpdateCheck {
|
||||
|
||||
self::requirePluginsApi();
|
||||
|
||||
$update_plugins = $this->fetchPluginUpdates();
|
||||
$update_plugins = $this->fetchPluginUpdates($useCachedValued);
|
||||
|
||||
//Get the full plugin list
|
||||
if (!function_exists('get_plugins')) {
|
||||
@@ -357,7 +377,18 @@ class wfUpdateCheck {
|
||||
//Do nothing, use cached value
|
||||
}
|
||||
else {
|
||||
wp_update_themes();
|
||||
try {
|
||||
wp_update_themes();
|
||||
}
|
||||
catch (Exception $e) {
|
||||
wfConfig::set(self::LAST_UPDATE_CHECK_ERROR_KEY, $e->getMessage(), false);
|
||||
error_log('Caught exception while attempting to refresh theme update status: ' . $e->getMessage());
|
||||
}
|
||||
catch (Throwable $t) {
|
||||
wfConfig::set(self::LAST_UPDATE_CHECK_ERROR_KEY, $t->getMessage(), false);
|
||||
error_log('Caught error while attempting to refresh theme update status: ' . $t->getMessage());
|
||||
}
|
||||
|
||||
$update_themes = get_site_transient('update_themes');
|
||||
}
|
||||
|
||||
|
||||
@@ -1341,39 +1341,12 @@ class wfUtils {
|
||||
$string = str_replace(",", "\n", $string); // fix old format
|
||||
return implode("\n", array_unique(array_filter(array_map('trim', explode("\n", $string)))));
|
||||
}
|
||||
public static function getScanFileError() {
|
||||
$fileTime = wfConfig::get('scanFileProcessing');
|
||||
if (!$fileTime) {
|
||||
return;
|
||||
}
|
||||
list($file, $time) = unserialize($fileTime);
|
||||
if ($time+10 < time()) {
|
||||
$add = true;
|
||||
$excludePatterns = wordfenceScanner::getExcludeFilePattern(wordfenceScanner::EXCLUSION_PATTERNS_USER);
|
||||
if ($excludePatterns) {
|
||||
foreach ($excludePatterns as $pattern) {
|
||||
if (preg_match($pattern, $file)) {
|
||||
$add = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($add) {
|
||||
$files = wfConfig::get('scan_exclude') . "\n" . $file;
|
||||
wfConfig::set('scan_exclude', self::cleanupOneEntryPerLine($files));
|
||||
}
|
||||
|
||||
self::endProcessingFile();
|
||||
}
|
||||
}
|
||||
|
||||
public static function beginProcessingFile($file) {
|
||||
wfConfig::set('scanFileProcessing', serialize(array($file, time())));
|
||||
//Do nothing
|
||||
}
|
||||
|
||||
public static function endProcessingFile() {
|
||||
wfConfig::set('scanFileProcessing', null);
|
||||
if (wfScanner::shared()->useLowResourceScanning()) {
|
||||
usleep(10000); //10 ms
|
||||
}
|
||||
@@ -3148,7 +3121,20 @@ class wfUtils {
|
||||
}
|
||||
return $encoded;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether or not MySQLi should be used directly when needed. Returns true if there's a valid DB handle,
|
||||
* our database test succeeded, our constant is not set to prevent it, and then either $wpdb indicates it's using
|
||||
* mysqli (older WordPress versions) or we're on PHP 7+ (only mysqli is ever used).
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function useMySQLi() {
|
||||
global $wpdb;
|
||||
$dbh = $wpdb->dbh;
|
||||
$useMySQLi = (is_object($dbh) && (PHP_MAJOR_VERSION >= 7 || $wpdb->use_mysqli) && wfConfig::get('allowMySQLi', true) && WORDFENCE_ALLOW_DIRECT_MYSQLI);
|
||||
return $useMySQLi;
|
||||
}
|
||||
}
|
||||
|
||||
// GeoIP lib uses these as well
|
||||
|
||||
@@ -6,5 +6,5 @@ $wfPHPMinimumVersion = '5.5.0'; //The currently supported minimum
|
||||
$wfOpenSSLDeprecatingVersion = '1.0.1';
|
||||
$wfOpenSSLMinimumVersion = '1.0.1';
|
||||
|
||||
$wfWordPressDeprecatingVersion = '4.4.0';
|
||||
$wfWordPressDeprecatingVersion = '4.7.0';
|
||||
$wfWordPressMinimumVersion = '3.9.0';
|
||||
|
||||
@@ -6098,7 +6098,7 @@ HTML;
|
||||
'loadTwoFactor', 'sendTestEmail',
|
||||
'email_summary_email_address_debug', 'unblockNetwork',
|
||||
'sendDiagnostic', 'saveDisclosureState', 'saveWAFConfig', 'updateWAFRules', 'loadLiveTraffic', 'whitelistWAFParamKey',
|
||||
'disableDirectoryListing', 'fixFPD', 'deleteAdminUser', 'revokeAdminUser',
|
||||
'disableDirectoryListing', 'fixFPD', 'deleteAdminUser', 'revokeAdminUser', 'acknowledgeAdminUser',
|
||||
'hideFileHtaccess', 'saveDebuggingConfig',
|
||||
'whitelistBulkDelete', 'whitelistBulkEnable', 'whitelistBulkDisable',
|
||||
'dismissNotification', 'utilityScanForBlacklisted', 'dashboardShowMore',
|
||||
@@ -7608,6 +7608,35 @@ SQL
|
||||
'user_login' => $userLogin,
|
||||
);
|
||||
}
|
||||
|
||||
public static function ajax_acknowledgeAdminUser_callback() {
|
||||
$issueID = absint(!empty($_POST['issueID']) ? $_POST['issueID'] : 0);
|
||||
$wfIssues = new wfIssues();
|
||||
$issue = $wfIssues->getIssueByID($issueID);
|
||||
if (!$issue) {
|
||||
return array('errorMsg' => __("We could not find that issue in the database.", 'wordfence'));
|
||||
}
|
||||
$data = $issue['data'];
|
||||
if (empty($data['userID'])) {
|
||||
return array('errorMsg' => __("We could not find that user in the database.", 'wordfence'));
|
||||
}
|
||||
$user = new WP_User($data['userID']);
|
||||
if (!$user->exists()) {
|
||||
return array('errorMsg' => __("We could not find that user in the database.", 'wordfence'));
|
||||
}
|
||||
$userLogin = $user->user_login;
|
||||
|
||||
$adminUsers = new wfAdminUserMonitor();
|
||||
$adminUsers->addAdmin($data['userID']);
|
||||
|
||||
$wfIssues->deleteIssue($issueID);
|
||||
wfScanEngine::refreshScanNotification($wfIssues);
|
||||
|
||||
return array(
|
||||
'ok' => 1,
|
||||
'user_login' => $userLogin,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -9464,9 +9493,23 @@ if (file_exists(__DIR__.%1$s)) {
|
||||
}
|
||||
|
||||
$request = new wfCentralAPIRequest('/site/access-token', 'GET', $authGrant);
|
||||
$response = $request->execute();
|
||||
try {
|
||||
$response = $request->execute();
|
||||
}
|
||||
catch (Exception $e) {
|
||||
wfCentralAPIRequest::handleInternalCentralAPIError($e);
|
||||
}
|
||||
catch (Throwable $t) {
|
||||
wfCentralAPIRequest::handleInternalCentralAPIError($t);
|
||||
}
|
||||
|
||||
if ($response->isError()) {
|
||||
if (!isset($response)) {
|
||||
return array(
|
||||
'err' => 1,
|
||||
'errorMsg' => __('Internal error when connecting to Wordfence Central (see server error log)', 'wordfence'),
|
||||
);
|
||||
}
|
||||
else if ($response->isError()) {
|
||||
return $response->returnErrorArray();
|
||||
}
|
||||
|
||||
@@ -9539,9 +9582,23 @@ if (file_exists(__DIR__.%1$s)) {
|
||||
),
|
||||
),
|
||||
));
|
||||
$response = $request->execute();
|
||||
|
||||
if ($response->isError()) {
|
||||
try {
|
||||
$response = $request->execute();
|
||||
}
|
||||
catch (Exception $e) {
|
||||
wfCentralAPIRequest::handleInternalCentralAPIError($e);
|
||||
}
|
||||
catch (Throwable $t) {
|
||||
wfCentralAPIRequest::handleInternalCentralAPIError($t);
|
||||
}
|
||||
|
||||
if (!isset($response)) {
|
||||
return array(
|
||||
'err' => 1,
|
||||
'errorMsg' => __('Internal error when connecting to Wordfence Central (see server error log)', 'wordfence'),
|
||||
);
|
||||
}
|
||||
else if ($response->isError()) {
|
||||
return $response->returnErrorArray();
|
||||
}
|
||||
|
||||
@@ -9577,12 +9634,27 @@ if (file_exists(__DIR__.%1$s)) {
|
||||
'success' => 1,
|
||||
);
|
||||
|
||||
} catch (wfCentralAPIException $e) {
|
||||
}
|
||||
catch (wfCentralAPIException $e) {
|
||||
return array(
|
||||
'error' => 1,
|
||||
'errorMsg' => $e->getMessage(),
|
||||
);
|
||||
}
|
||||
catch (Exception $e) {
|
||||
wfCentralAPIRequest::handleInternalCentralAPIError($e);
|
||||
return array(
|
||||
'error' => 1,
|
||||
'errorMsg' => $e->getMessage(),
|
||||
);
|
||||
}
|
||||
catch (Throwable $t) {
|
||||
wfCentralAPIRequest::handleInternalCentralAPIError($t);
|
||||
return array(
|
||||
'error' => 1,
|
||||
'errorMsg' => $t->getMessage(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public static function ajax_wfcentral_step4_callback() {
|
||||
@@ -9647,12 +9719,27 @@ if (file_exists(__DIR__.%1$s)) {
|
||||
rawurlencode(wfConfig::get('wordfenceCentralSiteID')), rawurlencode($body['access-token'])),
|
||||
);
|
||||
|
||||
} catch (wfCentralAPIException $e) {
|
||||
}
|
||||
catch (wfCentralAPIException $e) {
|
||||
return array(
|
||||
'error' => 1,
|
||||
'errorMsg' => $e->getMessage(),
|
||||
);
|
||||
}
|
||||
catch (Exception $e) {
|
||||
wfCentralAPIRequest::handleInternalCentralAPIError($e);
|
||||
return array(
|
||||
'error' => 1,
|
||||
'errorMsg' => $e->getMessage(),
|
||||
);
|
||||
}
|
||||
catch (Throwable $t) {
|
||||
wfCentralAPIRequest::handleInternalCentralAPIError($t);
|
||||
return array(
|
||||
'error' => 1,
|
||||
'errorMsg' => $t->getMessage(),
|
||||
);
|
||||
}
|
||||
}
|
||||
public static function ajax_wfcentral_step6_callback() {
|
||||
$wfCentralUserSiteAccessToken = wfConfig::get('wordfenceCentralUserSiteAccessToken');
|
||||
@@ -9683,9 +9770,16 @@ if (file_exists(__DIR__.%1$s)) {
|
||||
sprintf('/site/%s', wfConfig::get('wordfenceCentralSiteID')),
|
||||
'DELETE');
|
||||
$response = $request->execute();
|
||||
} catch (wfCentralAPIException $e) {
|
||||
}
|
||||
catch (wfCentralAPIException $e) {
|
||||
|
||||
}
|
||||
catch (Exception $e) {
|
||||
wfCentralAPIRequest::handleInternalCentralAPIError($e);
|
||||
}
|
||||
catch (Throwable $t) {
|
||||
wfCentralAPIRequest::handleInternalCentralAPIError($t);
|
||||
}
|
||||
|
||||
wfRESTConfigController::disconnectConfig();
|
||||
|
||||
|
||||
@@ -181,7 +181,7 @@ class wordfenceURLHoover {
|
||||
if ($this->useDB) {
|
||||
global $wpdb;
|
||||
$dbh = $wpdb->dbh;
|
||||
$useMySQLi = (is_object($dbh) && $wpdb->use_mysqli && wfConfig::get('allowMySQLi', true) && WORDFENCE_ALLOW_DIRECT_MYSQLI);
|
||||
$useMySQLi = wfUtils::useMySQLi();
|
||||
if ($useMySQLi) { //If direct-access MySQLi is available, we use it to minimize the memory footprint instead of letting it fetch everything into an array first
|
||||
wordfence::status(4, 'info', __("Using MySQLi directly.", 'wordfence'));
|
||||
$result = $dbh->query("SELECT DISTINCT hostKey FROM {$this->table} ORDER BY hostKey ASC LIMIT 100000"); /* We limit to 100,000 prefixes since more than that cannot be reliably checked within the default max_execution_time */
|
||||
|
||||
@@ -6,10 +6,12 @@ class Controller_Permissions {
|
||||
const CAP_ACTIVATE_2FA_SELF = 'wf2fa_activate_2fa_self'; //Activate 2FA on its own user account
|
||||
const CAP_ACTIVATE_2FA_OTHERS = 'wf2fa_activate_2fa_others'; //Activate 2FA on user accounts other than its own
|
||||
const CAP_MANAGE_SETTINGS = 'wf2fa_manage_settings'; //Edit settings for the plugin
|
||||
|
||||
const SITE_BATCH_SIZE = 50; //The maximum number of sites to process during a single request
|
||||
|
||||
const SETTING_LAST_ROLE_CHANGE = 'wfls_last_role_change';
|
||||
const SETTING_LAST_ROLE_SYNC = 'wfls_last_role_sync';
|
||||
|
||||
private $network_roles = array();
|
||||
private $multisite_roles = null;
|
||||
|
||||
/**
|
||||
* Returns the singleton Controller_Permissions.
|
||||
@@ -23,15 +25,9 @@ class Controller_Permissions {
|
||||
}
|
||||
return $_shared;
|
||||
}
|
||||
|
||||
private function on_role_change() {
|
||||
update_site_option('wfls_last_role_change', time());
|
||||
if(is_multisite())
|
||||
update_site_option('wfls_role_batch_position', 0);
|
||||
}
|
||||
|
||||
public function install() {
|
||||
$this->on_role_change();
|
||||
$this->_on_role_change();
|
||||
if (is_multisite()) {
|
||||
//Super Admin automatically gets all capabilities, so we don't need to explicitly add them
|
||||
$this->_add_cap_multisite('administrator', self::CAP_ACTIVATE_2FA_SELF, $this->get_primary_sites());
|
||||
@@ -42,57 +38,102 @@ class Controller_Permissions {
|
||||
$this->_add_cap('administrator', self::CAP_MANAGE_SETTINGS);
|
||||
}
|
||||
}
|
||||
|
||||
public function uninstall() {
|
||||
if (Controller_Settings::shared()->get_bool(Controller_Settings::OPTION_DELETE_ON_DEACTIVATION)) {
|
||||
if (is_multisite()) {
|
||||
$sites = $this->get_sites();
|
||||
foreach ($sites as $id) {
|
||||
switch_to_blog($id);
|
||||
wp_clear_scheduled_hook('wordfence_ls_role_sync_cron');
|
||||
restore_current_blog();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static function _init_actions() {
|
||||
add_action('wordfence_ls_role_sync_cron', array(Controller_Permissions::shared(), '_role_sync_cron'));
|
||||
}
|
||||
|
||||
public function init() {
|
||||
global $wp_version;
|
||||
if(is_multisite()){
|
||||
if(version_compare($wp_version, '5.1.0', '>=')){
|
||||
if (is_multisite()) {
|
||||
if (version_compare($wp_version, '5.1.0', '>=')) {
|
||||
add_action('wp_initialize_site', array($this, '_wp_initialize_site'), 99);
|
||||
}
|
||||
else{
|
||||
else {
|
||||
add_action('wpmu_new_blog', array($this, '_wpmu_new_blog'), 10, 5);
|
||||
}
|
||||
add_action('init', array($this, 'check_role_sync'), 1);
|
||||
|
||||
add_action('init', array($this, '_validate_role_sync_cron'), 1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Syncs roles to the new multisite blog.
|
||||
*
|
||||
* @param $site_id
|
||||
* @param $user_id
|
||||
* @param $domain
|
||||
* @param $path
|
||||
* @param $network_id
|
||||
*/
|
||||
public function _wpmu_new_blog($site_id, $user_id, $domain, $path, $network_id) {
|
||||
$this->sync_roles($network_id, $site_id);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Syncs roles to the new multisite blog.
|
||||
*
|
||||
* @param $new_site
|
||||
*/
|
||||
public function _wp_initialize_site($new_site) {
|
||||
$this->sync_roles($new_site->site_id, $new_site->blog_id);
|
||||
}
|
||||
|
||||
public function check_role_sync() {
|
||||
//Trigger an initial update for existing installations
|
||||
$last_role_change=(int)get_site_option('wfls_last_role_change', 0);
|
||||
if($last_role_change===0)
|
||||
$this->on_role_change();
|
||||
//Process the current batch if necessary
|
||||
$position=(int)get_site_option('wfls_role_batch_position', 0);
|
||||
if($position===-1)
|
||||
return;
|
||||
$sites=$this->get_sites($position, self::SITE_BATCH_SIZE);
|
||||
if(empty($sites)){
|
||||
$position=-1;
|
||||
return;
|
||||
|
||||
/**
|
||||
* Creates the hourly cron (if needed) that handles syncing the roles/permissions for the current blog. Because crons
|
||||
* are specific to individual blogs on multisite rather than to the network itself, this will end up creating a cron
|
||||
* for every member blog of the multisite.
|
||||
*
|
||||
* If there is a new role change since the last sync, a one-off cron will be fired to sync it sooner than the normal
|
||||
* recurrence period.
|
||||
*
|
||||
* Multisite only.
|
||||
*
|
||||
*/
|
||||
public function _validate_role_sync_cron() {
|
||||
if (!wp_next_scheduled('wordfence_ls_role_sync_cron')) {
|
||||
wp_schedule_event(time(), 'hourly', 'wordfence_ls_role_sync_cron');
|
||||
}
|
||||
else{
|
||||
$network_id=get_current_site()->id;
|
||||
foreach($sites as $site){
|
||||
$site=(int)$site;
|
||||
$this->sync_roles($network_id, $site);
|
||||
else {
|
||||
$last_role_change = (int) get_site_option(self::SETTING_LAST_ROLE_CHANGE, 0);
|
||||
if ($last_role_change >= get_option(self::SETTING_LAST_ROLE_SYNC, 0)) {
|
||||
wp_schedule_single_event(time(), 'wordfence_ls_role_sync_cron'); //Force queue an update in case the normal cron is still a while out
|
||||
}
|
||||
$position=$site;
|
||||
}
|
||||
update_site_option('wfls_role_batch_position', $position);
|
||||
//Update the current site if not already up to date
|
||||
$site_id=get_current_blog_id();
|
||||
if($last_role_change>=get_option('wfls_last_role_sync', 0)&&$site_id>=$position){
|
||||
$this->sync_roles(get_current_site()->id, $site_id);
|
||||
update_option('wfls_last_role_sync', time());
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles syncing the roles/permissions for the current blog when the cron fires.
|
||||
*/
|
||||
public function _role_sync_cron() {
|
||||
$last_role_change = (int) get_site_option(self::SETTING_LAST_ROLE_CHANGE, 0);
|
||||
if ($last_role_change === 0) {
|
||||
$this->_on_role_change();
|
||||
}
|
||||
|
||||
if ($last_role_change >= get_option(self::SETTING_LAST_ROLE_SYNC, 0)) {
|
||||
$network_id = get_current_site()->id;
|
||||
$blog_id = get_current_blog_id();
|
||||
$this->sync_roles($network_id, $blog_id);
|
||||
update_option(self::SETTING_LAST_ROLE_SYNC, time());
|
||||
}
|
||||
}
|
||||
|
||||
private function _on_role_change() {
|
||||
update_site_option(self::SETTING_LAST_ROLE_CHANGE, time());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -121,9 +162,20 @@ class Controller_Permissions {
|
||||
return $wpdb->get_col("SELECT blogs.blog_id FROM {$wpdb->site} sites JOIN {$wpdb->blogs} blogs ON blogs.site_id=sites.id AND blogs.path=sites.path");
|
||||
}
|
||||
}
|
||||
|
||||
private function get_sites($from, $count) {
|
||||
|
||||
/**
|
||||
* Returns an array of all multisite `blog_id` values, optionally limiting the result to the subset between
|
||||
* ($from, $from + $count].
|
||||
*
|
||||
* @param int $from
|
||||
* @param int $count
|
||||
* @return array
|
||||
*/
|
||||
private function get_sites($from = 0, $count = 0) {
|
||||
global $wpdb;
|
||||
if ($from === 0 && $count === 0) {
|
||||
return $wpdb->get_col("SELECT `blog_id` FROM `{$wpdb->blogs}` WHERE `deleted` = 0 ORDER BY blog_id ");
|
||||
}
|
||||
return $wpdb->get_col($wpdb->prepare("SELECT `blog_id` FROM `{$wpdb->blogs}` WHERE `deleted` = 0 AND blog_id > %d ORDER BY blog_id LIMIT %d", $from, $count));
|
||||
}
|
||||
|
||||
@@ -162,7 +214,7 @@ class Controller_Permissions {
|
||||
}
|
||||
|
||||
public function allow_2fa_self($role_name) {
|
||||
$this->on_role_change();
|
||||
$this->_on_role_change();
|
||||
if (is_multisite()) {
|
||||
return $this->_add_cap_multisite($role_name, self::CAP_ACTIVATE_2FA_SELF, $this->get_primary_sites());
|
||||
}
|
||||
@@ -172,7 +224,7 @@ class Controller_Permissions {
|
||||
}
|
||||
|
||||
public function disallow_2fa_self($role_name) {
|
||||
$this->on_role_change();
|
||||
$this->_on_role_change();
|
||||
if (is_multisite()) {
|
||||
return $this->_remove_cap_multisite($role_name, self::CAP_ACTIVATE_2FA_SELF, $this->get_primary_sites());
|
||||
}
|
||||
@@ -272,22 +324,125 @@ class Controller_Permissions {
|
||||
$wp_roles->remove_cap($role_name, $cap);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the role capability info for the multisite blog IDs in `$includedSites` and appends it to
|
||||
* `$this->multisite_roles`. Role capability data that is already loaded will be skipped.
|
||||
*
|
||||
* @param array $includeSites An array of multisite blog IDs to load.
|
||||
*/
|
||||
private function _load_multisite_roles($includeSites) {
|
||||
global $wpdb;
|
||||
|
||||
$needed = array_diff($includeSites, array_keys($this->multisite_roles));
|
||||
if (empty($needed)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$suffix = "user_roles";
|
||||
$queries = array();
|
||||
foreach ($needed as $b) {
|
||||
$tables = $wpdb->tables('blog', true, $b);
|
||||
$queries[] = "SELECT CAST(option_name AS CHAR UNICODE) AS option_name, CAST(option_value AS CHAR UNICODE) AS option_value FROM {$tables['options']} WHERE option_name LIKE '%{$suffix}'";
|
||||
}
|
||||
|
||||
$chunks = array_chunk($queries, 50);
|
||||
$options = array();
|
||||
foreach ($chunks as $c) {
|
||||
$rows = $wpdb->get_results(implode(' UNION ', $c), OBJECT_K);
|
||||
foreach ($rows as $row) {
|
||||
$options[$row->option_name] = $row->option_value;
|
||||
}
|
||||
}
|
||||
|
||||
$extractor = new Utility_MultisiteConfigurationExtractor($wpdb->base_prefix, $suffix);
|
||||
foreach ($extractor->extract($options) as $site => $option) {
|
||||
$this->multisite_roles[$site] = maybe_unserialize($option);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of multisite roles. This is guaranteed to include the multisite blogs in `$includeSites` but may
|
||||
* include others from earlier calls that are cached.
|
||||
*
|
||||
* @param array $includeSites An array for multisite blog IDs.
|
||||
* @return array
|
||||
*/
|
||||
public function get_multisite_roles($includeSites) {
|
||||
if ($this->multisite_roles === null) {
|
||||
$this->multisite_roles = array();
|
||||
}
|
||||
|
||||
$this->_load_multisite_roles($includeSites);
|
||||
return $this->multisite_roles;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the sites + roles that a user has on multisite. The structure of the returned array has the keys as the
|
||||
* individual site IDs and the associated value as an array of the user's capabilities on that site.
|
||||
*
|
||||
* @param WP_User $user
|
||||
* @return array
|
||||
*/
|
||||
public function get_multisite_roles_for_user($user) {
|
||||
global $wpdb;
|
||||
$roles = array();
|
||||
$meta = get_user_meta($user->ID);
|
||||
if (is_array($meta)) {
|
||||
$extractor = new Utility_MultisiteConfigurationExtractor($wpdb->base_prefix, 'capabilities');
|
||||
foreach ($extractor->extract($meta) as $site => $capabilities) {
|
||||
if (!is_array($capabilities)) { continue; }
|
||||
$capabilities = array_map('maybe_unserialize', $capabilities);
|
||||
$localRoles = array();
|
||||
foreach ($capabilities as $entry) {
|
||||
foreach ($entry as $role => $state) {
|
||||
if ($state)
|
||||
$localRoles[$role] = true;
|
||||
}
|
||||
}
|
||||
$roles[$site] = array_keys($localRoles);
|
||||
}
|
||||
}
|
||||
return $roles;
|
||||
}
|
||||
|
||||
public function get_all_roles($user) {
|
||||
global $wpdb;
|
||||
if (is_multisite()) {
|
||||
$roles = array();
|
||||
if (is_super_admin($user->ID))
|
||||
$roles[] = 'super-admin';
|
||||
foreach (get_blogs_of_user($user->ID) as $id => $blog) {
|
||||
switch_to_blog($id);
|
||||
$blogUser = new \WP_User($user->ID);
|
||||
$roles = array_merge($roles, $blogUser->roles);
|
||||
restore_current_blog();
|
||||
if (is_super_admin($user->ID)) {
|
||||
$roles['super-admin'] = true;
|
||||
}
|
||||
return array_unique($roles);
|
||||
foreach ($this->get_multisite_roles_for_user($user) as $site => $siteRoles) {
|
||||
foreach ($siteRoles as $role) {
|
||||
$roles[$role] = true;
|
||||
}
|
||||
}
|
||||
return array_keys($roles);
|
||||
}
|
||||
else {
|
||||
return $user->roles;
|
||||
}
|
||||
}
|
||||
|
||||
public function does_user_have_multisite_capability($user, $capability) {
|
||||
$userRoles = $this->get_multisite_roles_for_user($user);
|
||||
if (in_array('super-admin', $userRoles)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$blogRoles = $this->get_multisite_roles(array_keys($userRoles));
|
||||
$blogs = get_blogs_of_user($user->ID);
|
||||
foreach ($blogs as $blogId => $blog) {
|
||||
$blogId = (int) $blogId;
|
||||
if (!array_key_exists($blogId, $userRoles) || !array_key_exists($blogId, $blogRoles)) { continue; } //Blog with ID `$blogId` should be ignored
|
||||
foreach ($userRoles[$blogId] as $userRole) {
|
||||
if (!array_key_exists($userRole, $blogRoles[$blogId]) || !array_key_exists('capabilities', $blogRoles[$blogId][$userRole])) { continue; } //Sanity check for needed keys, should not happen
|
||||
|
||||
$capabilities = $blogRoles[$blogId][$userRole]['capabilities'];
|
||||
if (array_key_exists($capability, $capabilities) && $capabilities[$capability]) { return true; }
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -188,42 +188,11 @@ class Controller_Users {
|
||||
*/
|
||||
public function can_activate_2fa($user) {
|
||||
if (is_multisite() && !is_super_admin($user->ID)) {
|
||||
$blogs = get_blogs_of_user($user->ID);
|
||||
foreach ($blogs as $id => $info) {
|
||||
if ($this->_user_can_for_blog($user, $id, Controller_Permissions::CAP_ACTIVATE_2FA_SELF)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
return Controller_Permissions::shared()->does_user_have_multisite_capability($user, Controller_Permissions::CAP_ACTIVATE_2FA_SELF);
|
||||
}
|
||||
return user_can($user, Controller_Permissions::CAP_ACTIVATE_2FA_SELF);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of current_user_can_for_blog that works for an arbitrary user.
|
||||
*
|
||||
* @param int $user_id
|
||||
* @param int $blog_id
|
||||
* @param string $capability
|
||||
* @return bool
|
||||
*/
|
||||
private function _user_can_for_blog($user_id, $blog_id, $capability) {
|
||||
$switched = is_multisite() ? switch_to_blog($blog_id) : false;
|
||||
|
||||
$user = new \WP_User($user_id);
|
||||
|
||||
$args = array_slice(func_get_args(), 2);
|
||||
$args = array_merge(array($capability), $args);
|
||||
|
||||
$can = call_user_func_array(array($user, 'has_cap'), $args);
|
||||
|
||||
if ($switched) {
|
||||
restore_current_blog();
|
||||
}
|
||||
|
||||
return $can;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not any user has 2FA activated.
|
||||
*
|
||||
|
||||
@@ -82,6 +82,8 @@ class Controller_WordfenceLS {
|
||||
add_action('init', array($this, '_wordpress_init'));
|
||||
if ($this->is_shortcode_enabled())
|
||||
add_action('wp_enqueue_scripts', array($this, '_handle_shortcode_prerequisites'));
|
||||
|
||||
Controller_Permissions::_init_actions();
|
||||
}
|
||||
|
||||
public function _wordpress_init() {
|
||||
@@ -214,6 +216,7 @@ END
|
||||
|
||||
public function _uninstall_plugin() {
|
||||
Controller_Time::shared()->uninstall();
|
||||
Controller_Permissions::shared()->uninstall();
|
||||
|
||||
foreach (array(self::VERSION_KEY) as $opt) {
|
||||
if (is_multisite() && function_exists('delete_network_option')) {
|
||||
@@ -348,7 +351,7 @@ END
|
||||
->enqueue();
|
||||
wp_enqueue_style('wordfence-ls-login', Model_Asset::css('login.css'), array(), WORDFENCE_LS_VERSION);
|
||||
wp_localize_script('wordfence-ls-login', 'WFLSVars', array(
|
||||
'ajaxurl' => admin_url('admin-ajax.php'),
|
||||
'ajaxurl' => Utility_URL::relative_admin_url('admin-ajax.php'),
|
||||
'nonce' => wp_create_nonce('wp-ajax'),
|
||||
'recaptchasitekey' => Controller_Settings::shared()->get(Controller_Settings::OPTION_RECAPTCHA_SITE_KEY),
|
||||
'useCAPTCHA' => $useCAPTCHA,
|
||||
@@ -361,7 +364,7 @@ END
|
||||
private function get_2fa_management_script_data() {
|
||||
return array(
|
||||
'WFLSVars' => array(
|
||||
'ajaxurl' => admin_url('admin-ajax.php'),
|
||||
'ajaxurl' => Utility_URL::relative_admin_url('admin-ajax.php'),
|
||||
'nonce' => wp_create_nonce('wp-ajax'),
|
||||
'modalTemplate' => Model_View::create('common/modal-prompt', array('title' => '${title}', 'message' => '${message}', 'primaryButton' => array('id' => 'wfls-generic-modal-close', 'label' => __('Close', 'wordfence'), 'link' => '#')))->render(),
|
||||
'modalNoButtonsTemplate' => Model_View::create('common/modal-prompt', array('title' => '${title}', 'message' => '${message}'))->render(),
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace WordfenceLS;
|
||||
|
||||
class Utility_MeasuredString {
|
||||
|
||||
public $string;
|
||||
public $length;
|
||||
|
||||
public function __construct($string) {
|
||||
$this->string = $string;
|
||||
$this->length = strlen($string);
|
||||
}
|
||||
|
||||
public function __toString() {
|
||||
return $this->string;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace WordfenceLS;
|
||||
|
||||
class Utility_Multisite {
|
||||
|
||||
/**
|
||||
* Returns an array of all active multisite blogs (if `$blogIds` is `null`) or a list of active multisite blogs
|
||||
* filtered to only those in `$blogIds` if non-null.
|
||||
*
|
||||
* @param array|null $blogIds
|
||||
* @return array
|
||||
*/
|
||||
public static function retrieve_active_sites($blogIds = null) {
|
||||
$args = array(
|
||||
'number' => '', /* WordPress core passes an empty string which appears to remove the result set limit */
|
||||
'update_site_meta_cache' => false, /* Defaults to true which is not desirable for this use case */
|
||||
//Ignore archived/spam/deleted sites
|
||||
'archived' => 0,
|
||||
'spam' => 0,
|
||||
'deleted' => 0
|
||||
);
|
||||
|
||||
if ($blogIds !== null) {
|
||||
$args['site__in'] = $blogIds;
|
||||
}
|
||||
|
||||
if (function_exists('get_sites')) {
|
||||
return get_sites($args);
|
||||
}
|
||||
|
||||
global $wpdb;
|
||||
if ($blogIds !== null) {
|
||||
$blogIdsQuery = implode(',', wp_parse_id_list($args['site__in']));
|
||||
return $wpdb->get_results("SELECT * FROM {$wpdb->blogs} WHERE blog_id IN ({$blogIdsQuery}) AND archived = 0 AND spam = 0 AND deleted = 0");
|
||||
}
|
||||
|
||||
return $wpdb->get_results("SELECT * FROM {$wpdb->blogs} WHERE archived = 0 AND spam = 0 AND deleted = 0");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
|
||||
namespace WordfenceLS;
|
||||
|
||||
class Utility_MultisiteConfigurationExtractor {
|
||||
|
||||
private $prefix, $suffix;
|
||||
private $suffixOffset;
|
||||
|
||||
public function __construct($prefix, $suffix) {
|
||||
$this->prefix = new Utility_MeasuredString($prefix);
|
||||
$this->suffix = new Utility_MeasuredString($suffix);
|
||||
$this->suffixOffset = -$this->suffix->length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a `get_user_meta` result array into a more usable format. The input array will be something similar to
|
||||
* [
|
||||
* 'wp_capabilities' => '...',
|
||||
* 'wp_3_capabilities' => '...',
|
||||
* 'wp_4_capabilities' => '...',
|
||||
* 'wp_10_capabilities' => '...',
|
||||
* ]
|
||||
*
|
||||
* This will return
|
||||
* [
|
||||
* 1 => '...',
|
||||
* 3 => '...',
|
||||
* 4 => '...',
|
||||
* 10 => '...',
|
||||
* ]
|
||||
*
|
||||
* @param array $values
|
||||
* @return array
|
||||
*/
|
||||
private function parseBlogIds($values) {
|
||||
$parsed = array();
|
||||
foreach ($values as $key => $value) {
|
||||
if (substr($key, $this->suffixOffset) === $this->suffix->string && strpos($key, (string) $this->prefix) === 0) {
|
||||
$blogId = substr($key, $this->prefix->length, strlen($key) - $this->prefix->length + $this->suffixOffset);
|
||||
if (empty($blogId)) {
|
||||
$parsed[1] = $value;
|
||||
}
|
||||
else if (substr($blogId, -1) === '_') {
|
||||
$parsed[(int) $blogId] = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $parsed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters $values, which is the resulting array from `$this->parseBlogIds` so it contains only the values for the
|
||||
* sites in $sites.
|
||||
*
|
||||
* @param array $values
|
||||
* @param array $sites
|
||||
* @return array
|
||||
*/
|
||||
private function filterValues($values, $sites) {
|
||||
$filtered = array();
|
||||
foreach ($sites as $site) {
|
||||
$blogId = (int) $site->blog_id;
|
||||
$filtered[$blogId] = $values[$blogId];
|
||||
}
|
||||
return $filtered;
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes a `get_user_meta` result array to re-key it so the keys are the numerical ID of all multisite blog IDs
|
||||
* in `$values` that are still in an active state.
|
||||
*
|
||||
* @param array $values
|
||||
* @return array
|
||||
*/
|
||||
public function extract($values) {
|
||||
$parsed = $this->parseBlogIds($values);
|
||||
if (empty($parsed))
|
||||
return $parsed;
|
||||
$sites = Utility_Multisite::retrieve_active_sites(array_keys($parsed));
|
||||
return $this->filterValues($parsed, $sites);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace WordfenceLS;
|
||||
|
||||
class Utility_URL {
|
||||
|
||||
/**
|
||||
* Similar to WordPress' `admin_url`, this returns a host-relative URL for the given path. It may be used to avoid
|
||||
* canonicalization issues with CORS (e.g., the site is configured for the www. variant of the URL but doesn't forward
|
||||
* the other).
|
||||
*
|
||||
* @param string $path
|
||||
* @return string
|
||||
*/
|
||||
public static function relative_admin_url($path = '') {
|
||||
$url = admin_url($path);
|
||||
$components = parse_url($url);
|
||||
$s = $components['path'];
|
||||
if (!empty($components['query'])) {
|
||||
$s .= '?' . $components['query'];
|
||||
}
|
||||
if (!empty($components['fragment'])) {
|
||||
$s .= '#' . $components['fragment'];
|
||||
}
|
||||
return $s;
|
||||
}
|
||||
}
|
||||
@@ -140,6 +140,10 @@
|
||||
if (heading.length > 0) {
|
||||
var dom = (type === 'error' ? $('<div id="login_error">') : $('<p class="message">'));
|
||||
dom.addClass('wfls-login-message');
|
||||
dom.addClass('notice');
|
||||
if (type === 'error') {
|
||||
dom.addClass('notice-error');
|
||||
}
|
||||
dom.html(messageHtml);
|
||||
heading.after(dom);
|
||||
dom.get(0).scrollIntoView();
|
||||
@@ -85,7 +85,8 @@ $recovery = $initializationData->get_recovery_codes();
|
||||
payload,
|
||||
function(response) {
|
||||
if (response.error) {
|
||||
WFLS.panelModal((WFLS.screenSize(500) ? '300px' : '400px'), '<?php echo \WordfenceLS\Text\Model_JavaScript::esc_js(__('Error Activating 2FA', 'wordfence')); ?>', response.error);
|
||||
WFLS.userIsActivating = false;
|
||||
WFLS.panelModal((WFLS.screenSize(500) ? '300px' : '400px'), '<?php echo \WordfenceLS\Text\Model_JavaScript::esc_js(__('Error Activating 2FA', 'wordfence')); ?>', response.error, {includeDefaultButtons: true});
|
||||
}
|
||||
else {
|
||||
$('#wfls-activation-controls').crossfade($('#wfls-deactivation-controls'));
|
||||
@@ -116,7 +117,7 @@ $recovery = $initializationData->get_recovery_codes();
|
||||
}
|
||||
},
|
||||
function(error) {
|
||||
WFLS.panelModal((WFLS.screenSize(500) ? '300px' : '400px'), '<?php echo \WordfenceLS\Text\Model_JavaScript::esc_js(__('Error Activating 2FA', 'wordfence')); ?>', '<?php echo \WordfenceLS\Text\Model_JavaScript::esc_js(__('An error was encountered while trying to activate two-factor authentication. Please try again.', 'wordfence')); ?>');
|
||||
WFLS.panelModal((WFLS.screenSize(500) ? '300px' : '400px'), '<?php echo \WordfenceLS\Text\Model_JavaScript::esc_js(__('Error Activating 2FA', 'wordfence')); ?>', '<?php echo \WordfenceLS\Text\Model_JavaScript::esc_js(__('An error was encountered while trying to activate two-factor authentication. Please try again.', 'wordfence')); ?>', {includeDefaultButtons: true});
|
||||
WFLS.userIsActivating = false;
|
||||
}
|
||||
);
|
||||
|
||||
@@ -26,8 +26,8 @@ if ($wfCoreActive && !(isset($wfCoreLoading) && $wfCoreLoading)) {
|
||||
else {
|
||||
define('WORDFENCE_LS_FROM_CORE', ($wfCoreActive && isset($wfCoreLoading) && $wfCoreLoading));
|
||||
|
||||
define('WORDFENCE_LS_VERSION', '1.1.4');
|
||||
define('WORDFENCE_LS_BUILD_NUMBER', '1690810710');
|
||||
define('WORDFENCE_LS_VERSION', '1.1.7');
|
||||
define('WORDFENCE_LS_BUILD_NUMBER', '1699289814');
|
||||
|
||||
define('WORDFENCE_LS_PLUGIN_BASENAME', plugin_basename(__FILE__));
|
||||
|
||||
|
||||
@@ -3,8 +3,8 @@ Contributors: mmaunder, wfryan, wfmatt, wfmattr
|
||||
Tags: security, waf, malware, 2fa, two factor, login security, firewall, brute force, scanner, scan, web application firewall, protection, stop hackers, prevent hacks, secure wordpress, wordpress security
|
||||
Requires at least: 3.9
|
||||
Requires PHP: 5.5
|
||||
Tested up to: 6.3
|
||||
Stable tag: 7.10.3
|
||||
Tested up to: 6.4
|
||||
Stable tag: 7.10.7
|
||||
License: GPLv3
|
||||
License URI: https://www.gnu.org/licenses/gpl-3.0.html
|
||||
|
||||
@@ -189,13 +189,42 @@ Secure your website with Wordfence.
|
||||
|
||||
== Changelog ==
|
||||
|
||||
= 7.10.7 - November 6, 2023 =
|
||||
* Fix: Compatibility fix for WordPress 6.4 on the login page styling
|
||||
|
||||
= 7.10.6 - October 30, 2023 =
|
||||
* Fix: Addressed an issue with multisite installations when the wp_options tables had different encodings/collations
|
||||
|
||||
= 7.10.5 - October 23, 2023 =
|
||||
* Improvement: Updated the bundled GeoIP database
|
||||
* Improvement: Added detection for Cloudflare reverse proxies blocking callbacks to the site
|
||||
* Change: Files are no longer excluded from future scans if a previous scan stopped during their processing
|
||||
* Fix: Added handling for the pending WordPress 6.4 change that removes $wpdb->use_mysqli
|
||||
* Fix: The WAF MySQLi storage engine will now work correctly when either DB_COLLATE or DB_CHARSET are not defined
|
||||
* Fix: Added additional error handling to Central calls to better handle request failures or conflicts
|
||||
* Fix: Addressed a warning that would occur if a non-repo plugin update hook did not provide a last updated date
|
||||
* Fix: Fixed an error in PHP 8 that could occur if the time correction offset was not numeric
|
||||
* Fix: 2FA AJAX calls now use an absolute path rather than a full URL to avoid CORS issues on sites that do not canonicalize www and non-www requests
|
||||
* Fix: Addressed a race condition where multiple concurrent hits on multisite could trigger overlapping role sync tasks
|
||||
* Fix: Improved performance when viewing the user list on large multisites
|
||||
* Fix: Fixed a UI bug where an invalid code on 2FA activation would leave the activate button disabled
|
||||
* Fix: Reverted a change on error modals to bring back the additional close button for better accessibility
|
||||
|
||||
= 7.10.4 - September 25, 2023 =
|
||||
* Improvement: "Admin created outside of WordPress" scan results may now be reviewed and approved
|
||||
* Improvement: The WAF storage engine may now be specified by setting the environmental variable "WFWAF_STORAGE_ENGINE"
|
||||
* Improvement: Detect when a plugin or theme with a custom update handler is broken and blocking update version checks
|
||||
* Change: Deprecated support for WordPress versions lower than 4.7.0
|
||||
* Change: Exclude parse errors of a damaged compiled rules file from reporting
|
||||
* Fix: Suppress PHP notices related to rule loading when running WP-CLI
|
||||
* Fix: Fixed an issue with the scan monitor cron that could leave it running unnecessarily
|
||||
|
||||
= 7.10.3 - July 31, 2023 =
|
||||
* Improvement: Updated GeoIP database
|
||||
* Fix: Added missing text domain to translation function call
|
||||
* Fix: Corrected inconsistent styling of switch controls
|
||||
* Change: Made MySQLi storage engine the default for Flywheel hosted sites
|
||||
|
||||
|
||||
= 7.10.2 - July 17, 2023 =
|
||||
* Fix: Prevented bundled sodium_compat library from conflicting with versions included with older WordPress versions
|
||||
|
||||
|
||||
@@ -17,8 +17,7 @@ class wfWAFStorageFile implements wfWAFStorageInterface {
|
||||
return true;
|
||||
}
|
||||
|
||||
$sapi = @php_sapi_name();
|
||||
if ($sapi == "cli") {
|
||||
if (wfWAFUtils::isCli()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
||||
@@ -1098,13 +1098,12 @@ class wfWAFUtils {
|
||||
$offset = wfWAF::getInstance()->getStorageEngine()->getConfig('timeoffset_ntp', false, 'synced');
|
||||
if ($offset === false) {
|
||||
$offset = wfWAF::getInstance()->getStorageEngine()->getConfig('timeoffset_wf', false, 'synced');
|
||||
if ($offset === false) { $offset = 0; }
|
||||
}
|
||||
}
|
||||
catch (Exception $e) {
|
||||
//Ignore
|
||||
}
|
||||
return time() + $offset;
|
||||
return time() + ((int) $offset);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1164,17 +1163,30 @@ class wfWAFUtils {
|
||||
'pass' => 'DB_PASSWORD',
|
||||
'database' => 'DB_NAME',
|
||||
'host' => 'DB_HOST',
|
||||
'charset' => 'DB_CHARSET',
|
||||
'collation' => 'DB_COLLATE'
|
||||
'charset' => array('constant' => 'DB_CHARSET', 'default' => ''),
|
||||
'collation' => array('constant' => 'DB_COLLATE', 'default' => ''),
|
||||
);
|
||||
$constants += $optionalConstants;
|
||||
foreach ($constants as $key => $constant) {
|
||||
unset($defaultValue);
|
||||
if (is_array($constant)) {
|
||||
$defaultValue = $constant['default'];
|
||||
$constant = $constant['constant'];
|
||||
}
|
||||
|
||||
if (array_key_exists($key, $return)) {
|
||||
continue;
|
||||
} else if (array_key_exists($constant, $parsedConstants)) {
|
||||
}
|
||||
else if (array_key_exists($constant, $parsedConstants)) {
|
||||
$return[$key] = $parsedConstants[$constant];
|
||||
} else if (!array_key_exists($key, $optionalConstants)){
|
||||
return ($return = false);
|
||||
}
|
||||
else if (!array_key_exists($key, $optionalConstants)){
|
||||
if (isset($defaultValue)) {
|
||||
$return[$key] = $defaultValue;
|
||||
}
|
||||
else {
|
||||
return ($return = false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1229,6 +1241,9 @@ class wfWAFUtils {
|
||||
public static function isVersionBelow($target, $compared) {
|
||||
return $compared === null || version_compare($compared, $target, '<');
|
||||
}
|
||||
|
||||
|
||||
public static function isCli() {
|
||||
return (@php_sapi_name()==='cli') || !array_key_exists('REQUEST_METHOD', $_SERVER);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -328,6 +328,11 @@ auEa+7b+FGTKs7dUo2BNGR7OVifK4GZ8w/ajS0TelhrSRi3BBQCGXLzUO/UURUAh
|
||||
public function loadRules() {
|
||||
$storageEngine = $this->getStorageEngine();
|
||||
if ($storageEngine instanceof wfWAFStorageFile) {
|
||||
$logLevel = error_reporting();
|
||||
if (wfWAFUtils::isCli()) { //Done to suppress errors from WP-CLI when the WAF is run on environments that have a server level constant to use the MySQLi storage engine that is not in place when running from the CLI
|
||||
error_reporting(0);
|
||||
}
|
||||
|
||||
// Acquire lock on this file so we're not including it while it's being written in another process.
|
||||
$handle = fopen($storageEngine->getRulesFile(), 'r');
|
||||
$locked = $handle !== false && flock($handle, LOCK_SH);
|
||||
@@ -337,6 +342,10 @@ auEa+7b+FGTKs7dUo2BNGR7OVifK4GZ8w/ajS0TelhrSRi3BBQCGXLzUO/UURUAh
|
||||
flock($handle, LOCK_UN);
|
||||
if ($handle !== false)
|
||||
fclose($handle);
|
||||
|
||||
if (wfWAFUtils::isCli()) {
|
||||
error_reporting($logLevel);
|
||||
}
|
||||
} else {
|
||||
$wafRules = $storageEngine->getRules();
|
||||
if (is_array($wafRules)) {
|
||||
|
||||
@@ -14,6 +14,7 @@ echo wfView::create('scanner/issue-base', array(
|
||||
'detailControls' => array(
|
||||
'<a href="#" class="wf-btn wf-btn-default wf-btn-callout-subtle" onclick="WFAD.deleteAdminUser(\'${id}\'); return false;" role="button">' . __('Delete User', 'wordfence') . '</a>',
|
||||
'<a href="#" class="wf-btn wf-btn-default wf-btn-callout-subtle" onclick="WFAD.revokeAdminUser(\'${id}\'); return false;" role="button">' . __('Revoke Capabilities', 'wordfence') . '</a>',
|
||||
'<a href="#" class="wf-btn wf-btn-default wf-btn-callout-subtle" onclick="WFAD.acknowledgeAdminUser(\'${id}\'); return false;" role="button">' . __('Acknowledge User', 'wordfence') . '</a>',
|
||||
'<a href="#" class="wf-btn wf-btn-default wf-btn-callout-subtle wf-issue-control-mark-fixed" role="button">' . __('Mark as Fixed', 'wordfence') . '</a>',
|
||||
),
|
||||
'textOutput' => (isset($textOutput) ? $textOutput : null),
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
if (!defined('WORDFENCE_VERSION')) { exit; }
|
||||
/**
|
||||
* Presents an issue template.
|
||||
*/
|
||||
echo wfView::create('scanner/issue-base', array(
|
||||
'internalType' => 'wfUpgradeError',
|
||||
'displayType' => __('Update Check Error', 'wordfence'),
|
||||
'iconSVG' => '<svg viewBox="0 0 20 20"><g><path d="M13.11 4.36L9.87 7.6 8 5.73l3.24-3.24c.35-.34 1.05-.2 1.56.32.52.51.66 1.21.31 1.55zm-8 1.77l.91-1.12 9.01 9.01-1.19.84c-.71.71-2.63 1.16-3.82 1.16H6.14L4.9 17.26c-.59.59-1.54.59-2.12 0-.59-.58-.59-1.53 0-2.12l1.24-1.24v-3.88c0-1.13.4-3.19 1.09-3.89zm7.26 3.97l3.24-3.24c.34-.35 1.04-.21 1.55.31.52.51.66 1.21.31 1.55l-3.24 3.25z"/></g></svg>',
|
||||
'summaryControls' => array(wfView::create('scanner/issue-control-ignore', array('ignoreC' => __('Ignore Update', 'wordfence'))), wfView::create('scanner/issue-control-show-details')),
|
||||
'detailPairs' => array(
|
||||
__('Details', 'wordfence') => '{{html longMsg}}',
|
||||
),
|
||||
'detailControls' => array(
|
||||
'<a href="#" class="wf-btn wf-btn-default wf-btn-callout-subtle wf-issue-control-mark-fixed" role="button">' . __('Mark as Fixed', 'wordfence') . '</a>',
|
||||
'<a href="' . esc_url(wfUtils::wpAdminURL('update-core.php')) . '" class="wf-btn wf-btn-default wf-btn-callout-subtle wf-issue-control-view-updates">' . __('View Updates', 'wordfence') . '</a>',
|
||||
),
|
||||
'textOutput' => (isset($textOutput) ? $textOutput : null),
|
||||
'textOutputDetailPairs' => array(
|
||||
__('Details', 'wordfence') => '$longMsg',
|
||||
),
|
||||
))->render();
|
||||
@@ -837,8 +837,10 @@ if (!is_dir(WFWAF_LOG_PATH)) {
|
||||
|
||||
|
||||
try {
|
||||
|
||||
if (!defined('WFWAF_STORAGE_ENGINE') && (WF_IS_WP_ENGINE || WF_IS_FLYWHEEL)) {
|
||||
if (!defined('WFWAF_STORAGE_ENGINE') && isset($_SERVER['WFWAF_STORAGE_ENGINE'])) {
|
||||
define('WFWAF_STORAGE_ENGINE', $_SERVER['WFWAF_STORAGE_ENGINE']);
|
||||
}
|
||||
else if (!defined('WFWAF_STORAGE_ENGINE') && (WF_IS_WP_ENGINE || WF_IS_FLYWHEEL)) {
|
||||
define('WFWAF_STORAGE_ENGINE', 'mysqli');
|
||||
}
|
||||
|
||||
@@ -1019,10 +1021,15 @@ catch (Exception $e) { // In PHP 5, Throwable does not exist
|
||||
);
|
||||
}
|
||||
catch (Throwable $t) {
|
||||
error_log("An unexpected error occurred during WAF execution: {$t}");
|
||||
$wf_waf_failure = array(
|
||||
'throwable' => $t
|
||||
);
|
||||
error_log("An unexpected exception occurred during WAF execution: {$t}");
|
||||
if (class_exists('ParseError') && $t instanceof ParseError) {
|
||||
//Do nothing
|
||||
}
|
||||
else {
|
||||
$wf_waf_failure = array(
|
||||
'throwable' => $t
|
||||
);
|
||||
}
|
||||
}
|
||||
if (wfWAF::getInstance() === null) {
|
||||
require_once __DIR__ . '/dummy.php';
|
||||
|
||||
@@ -4,7 +4,7 @@ Plugin Name: Wordfence Security
|
||||
Plugin URI: http://www.wordfence.com/
|
||||
Description: Wordfence Security - Anti-virus, Firewall and Malware Scan
|
||||
Author: Wordfence
|
||||
Version: 7.10.3
|
||||
Version: 7.10.7
|
||||
Author URI: http://www.wordfence.com/
|
||||
Text Domain: wordfence
|
||||
Domain Path: /languages
|
||||
@@ -38,8 +38,8 @@ if(defined('WP_INSTALLING') && WP_INSTALLING){
|
||||
if (!defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
define('WORDFENCE_VERSION', '7.10.3');
|
||||
define('WORDFENCE_BUILD_NUMBER', '1690810710');
|
||||
define('WORDFENCE_VERSION', '7.10.7');
|
||||
define('WORDFENCE_BUILD_NUMBER', '1699289814');
|
||||
define('WORDFENCE_BASENAME', function_exists('plugin_basename') ? plugin_basename(__FILE__) :
|
||||
basename(dirname(__FILE__)) . '/' . basename(__FILE__));
|
||||
|
||||
@@ -124,4 +124,4 @@ if(! defined('WORDFENCE_VERSIONONLY_MODE')){ //Used to get version from file.
|
||||
require_once(dirname(__FILE__) . '/lib/wordfenceConstants.php');
|
||||
require_once(dirname(__FILE__) . '/lib/wordfenceClass.php');
|
||||
wordfence::install_actions();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user