multi language support

support for small devices
This commit is contained in:
2024-03-02 15:09:07 +01:00
parent 4d479cedaf
commit b3b58ce103
14 changed files with 613 additions and 339 deletions

View File

@ -5,278 +5,277 @@ namespace Bdp\Modules\LimitLoginAttempts\Controllers;
use ProtectLogin\Modules\LimitLoginAttempts\Requests\IpAddress;
class LoginHandler {
public const DIRECT_ADDR = 'REMOTE_ADDR';
public const PROXY_ADDR = 'HTTP_X_FORWARDED_FOR';
public const DIRECT_ADDR = 'REMOTE_ADDR';
public const PROXY_ADDR = 'HTTP_X_FORWARDED_FOR';
public function onSuccessFullLogin($user, $password) {
if (!is_wp_error($user) && $this->isLoginAllowedFromIp() ) {
return $user;
}
public function onSuccessFullLogin($user, $password) {
if (!is_wp_error($user) && $this->isLoginAllowedFromIp() ) {
return $user;
}
global $limit_login_my_error_shown;
$limit_login_my_error_shown = true;
global $limit_login_my_error_shown;
$limit_login_my_error_shown = true;
$error = new \WP_Error();
// This error should be the same as in "shake it" filter below
$error->add('too_many_retries', $this->composeErrorMessage());
return $error;
}
$error = new \WP_Error();
// This error should be the same as in "shake it" filter below
$error->add('too_many_retries', $this->composeErrorMessage());
return $error;
}
public function onFailedLogin(string $username) {
$ip = $this->getAddress();
public function onFailedLogin(string $username) {
$ip = $this->getAddress();
/* if currently locked-out, do not add to retries */
$lockouts = get_option('protect_login_limit_login_lockouts', []);
/* if currently locked-out, do not add to retries */
$lockouts = get_option('protect_login_limit_login_lockouts', []);
if(isset($lockouts[$ip]) && time() < $lockouts[$ip]) {
return;
}
if(isset($lockouts[$ip]) && time() < $lockouts[$ip]) {
return;
}
/* Get the arrays with retries and retries-valid information */
$retries = get_option('kompass_limit_login_retries', []);
$valid = get_option('kompass_limit_login_retries_valid', []);
/* Get the arrays with retries and retries-valid information */
$retries = get_option('kompass_limit_login_retries', []);
$valid = get_option('kompass_limit_login_retries_valid', []);
/* Check validity and add one to retries */
if (isset($retries[$ip])) { //} && isset($valid[$ip]) && time() < $valid[$ip]) {
$retries[$ip] ++;
} else {
$retries[$ip] = 1;
}
/* Check validity and add one to retries */
if (isset($retries[$ip])) { //} && isset($valid[$ip]) && time() < $valid[$ip]) {
$retries[$ip] ++;
} else {
$retries[$ip] = 1;
}
update_option('kompass_limit_login_retries', $retries);
update_option('kompass_limit_login_retries', $retries);
/* lockout? */
if($retries[$ip] % get_option('kompass_limit_login_allowed_retries', 0) != 0) {
return;
}
/* lockout? */
if($retries[$ip] % get_option('kompass_limit_login_allowed_retries', 0) != 0) {
return;
}
$retries_long = get_option('kompass_limit_login_allowed_retries', 1)
* get_option('kompass_limit_login_allowed_lockouts', 1);
$retries_long = get_option('kompass_limit_login_allowed_retries', 1)
* get_option('kompass_limit_login_allowed_lockouts', 1);
if ($retries[$ip] >= $retries_long) {
$lockouts[$ip] = time() + get_option('kompass_limit_login_long_duration', 86400);
if ($retries[$ip] >= $retries_long) {
$lockouts[$ip] = time() + get_option('kompass_limit_login_long_duration', 86400);
} else {
$lockouts[$ip] = time() + get_option('kompass_limit_login_lockout_duration', 900);
}
} else {
$lockouts[$ip] = time() + get_option('kompass_limit_login_lockout_duration', 900);
}
update_option('kompass_limit_login_lockouts', $lockouts);
update_option('kompass_limit_login_lockouts', $lockouts);
/* do any notification */
$this->notify($username);
/* do any notification */
$this->notify($username);
}
}
private function notifyByEmail($user)
{
$ip = $this->getAddress();
private function notifyByEmail($user)
{
$ip = $this->getAddress();
$lockouts = get_option('kompass_limit_login_lockouts', []);
if (!isset($lockouts[$ip])) {
return;
}
$lockouts = get_option('kompass_limit_login_lockouts', []);
if (!isset($lockouts[$ip])) {
return;
}
$blocked_until = $lockouts[$ip];
$blocked_until = $lockouts[$ip];
$retries = get_option('kompass_limit_login_retries', []);
$currentRetries = $retries[$ip];
$retries = get_option('kompass_limit_login_retries', []);
$currentRetries = $retries[$ip];
$notify_after = get_option('kompass_limit_login_notify_email_after', 1);
if ($currentRetries % $notify_after !== 0) {
return;
}
$notify_after = get_option('kompass_limit_login_notify_email_after', 1);
if ($currentRetries % $notify_after !== 0) {
return;
}
$blogname = get_option('blogname', 'none');
$blogname = get_option('blogname', 'none');
$subject = sprintf(__("[%s] Too many failed login attempts"
, 'limit-login-attempts')
, $blogname);
$subject = sprintf(__("[%s] Too many failed login attempts"
, 'limit-login-attempts')
, $blogname);
$message = 'Neue Sperrung auf deiner Webseite: ' . PHP_EOL .
'IP-Adresse: ' . $ip . PHP_EOL .
'Gesperrt bis: ' . date('d.m.Y H:i', $blocked_until);
$message = 'Neue Sperrung auf deiner Webseite: ' . PHP_EOL .
'IP-Adresse: ' . $ip . PHP_EOL .
'Gesperrt bis: ' . date('d.m.Y H:i', $blocked_until);
$admin_email = get_option('admin_email');
wp_mail($admin_email, $subject, $message);
}
$admin_email = get_option('admin_email');
wp_mail($admin_email, $subject, $message);
}
/* Handle notification in event of lockout */
private function notify($user) {
$args = get_option('kompass_limit_login_lockout_notify', []);
/* Handle notification in event of lockout */
private function notify($user) {
$args = get_option('kompass_limit_login_lockout_notify', []);
if (!is_array($args)) {
$args = [$args];
}
foreach ($args as $mode) {
switch (trim($mode)) {
case 'email':
$this->notifyByEmail($user);
break;
}
}
}
foreach ($args as $mode) {
switch (trim($mode)) {
case 'email':
$this->notifyByEmail($user);
break;
}
}
}
private function composeErrorMessage() {
$ip = $this->getAddress();
$lockouts = get_option('kompass_limit_login_lockouts');
$lockouts = get_option('protect_login_limit_login_lockouts');
$msg = __('<strong>ERROR</strong>: Too many failed login attempts.', 'limit-login-attempts') . ' ';
$msg = __('Too many failed login attempts.', BDP_LV_PLUGIN_SLUG) . ' ';
if (!is_array($lockouts) || !isset($lockouts[$ip]) || time() >= $lockouts[$ip]) {
/* Huh? No timeout active? */
$msg .= __('Please try again later.', 'limit-login-attempts');
$msg .= __('Please try again later.', BDP_LV_PLUGIN_SLUG);
return $msg;
}
$when = ceil(($lockouts[$ip] - time()) / 60);
if ($when > 60) {
$when = ceil($when / 60);
$msg .= sprintf(_n('Please try again in %d hour.', 'Please try again in %d hours.', $when, 'limit-login-attempts'), $when);
$msg .= sprintf(_n('Please try again in %d hour.', 'Please try again in %d hours.', $when, BDP_LV_PLUGIN_SLUG), $when);
} else {
$msg .= sprintf(_n('Please try again in %d minute.', 'Please try again in %d minutes.', $when, 'limit-login-attempts'), $when);
$msg .= sprintf(_n('Please try again in %d minute.', 'Please try again in %d minutes.', $when, BDP_LV_PLUGIN_SLUG), $when);
}
return $msg;
}
private static function getAddress($typeName = '') {
global $limitLoginAttemptsSettings;
private static function getAddress($typeName = '') {
global $limitLoginAttemptsSettings;
$typeOriginal = $typeName;
if (empty($typeName)) {
$typeName = get_option('kompass_limit_loginclient_type', self::DIRECT_ADDR);
}
$typeOriginal = $typeName;
if (empty($typeName)) {
$typeName = get_option('kompass_limit_loginclient_type', self::DIRECT_ADDR);
}
if (isset($_SERVER[$typeName]) && filter_var($_SERVER[$typeName], FILTER_VALIDATE_IP)) {
return $_SERVER[$typeName];
}
if (isset($_SERVER[$typeName]) && filter_var($_SERVER[$typeName], FILTER_VALIDATE_IP)) {
return $_SERVER[$typeName];
}
/*
* Not found. Did we get proxy type from option?
* If so, try to fall back to direct address.
*/
if ( empty($typeName) && $typeOriginal == self::PROXY_ADDR
&& isset($_SERVER[self::DIRECT_ADDR])
&& filter_var($_SERVER[self::DIRECT_ADDR], FILTER_VALIDATE_IP)) {
/*
* Not found. Did we get proxy type from option?
* If so, try to fall back to direct address.
*/
if ( empty($typeName) && $typeOriginal == self::PROXY_ADDR
&& isset($_SERVER[self::DIRECT_ADDR])
&& filter_var($_SERVER[self::DIRECT_ADDR], FILTER_VALIDATE_IP)) {
/*
* NOTE: Even though we fall back to direct address -- meaning you
* can get a mostly working plugin when set to PROXY mode while in
* fact directly connected to Internet it is not safe!
*
* Client can itself send HTTP_X_FORWARDED_FOR header fooling us
* regarding which IP should be banned.
*/
/*
* NOTE: Even though we fall back to direct address -- meaning you
* can get a mostly working plugin when set to PROXY mode while in
* fact directly connected to Internet it is not safe!
*
* Client can itself send HTTP_X_FORWARDED_FOR header fooling us
* regarding which IP should be banned.
*/
return $_SERVER[self::DIRECT_ADDR];
}
return $_SERVER[self::DIRECT_ADDR];
}
return '';
return '';
}
}
public function isLoginAllowedFromIp() {
$ip = $this->getAddress();
public function isLoginAllowedFromIp() {
$ip = $this->getAddress();
if (in_array($ip, get_option('kompass_limit_login_blocklist', []))) {
return false;
}
if (in_array($ip, get_option('kompass_limit_login_blocklist', []))) {
return false;
}
if (in_array($ip, get_option('kompass_limit_login_allowlist', []))) {
return true;
}
if (in_array($ip, get_option('kompass_limit_login_allowlist', []))) {
return true;
}
/* lockout active? */
$lockouts = get_option('kompass_limit_login_lockouts', []);
return (!is_array($lockouts) || !isset($lockouts[$ip]) || time() >= $lockouts[$ip]);
}
/* lockout active? */
$lockouts = get_option('kompass_limit_login_lockouts', []);
return (!is_array($lockouts) || !isset($lockouts[$ip]) || time() >= $lockouts[$ip]);
}
public function checkFailedCookies($cookie_elements) {
$this->clearAuthCookie();
public function checkFailedCookies($cookie_elements) {
$this->clearAuthCookie();
/*
* Invalid username gets counted every time.
*/
/*
* Invalid username gets counted every time.
*/
$this->onFailedLogin($cookie_elements['username']);
}
$this->onFailedLogin($cookie_elements['username']);
}
private function clearAuthCookie() {
wp_clear_auth_cookie();
private function clearAuthCookie() {
wp_clear_auth_cookie();
if (!empty($_COOKIE[AUTH_COOKIE])) {
$_COOKIE[AUTH_COOKIE] = '';
}
if (!empty($_COOKIE[SECURE_AUTH_COOKIE])) {
$_COOKIE[SECURE_AUTH_COOKIE] = '';
}
if (!empty($_COOKIE[LOGGED_IN_COOKIE])) {
$_COOKIE[LOGGED_IN_COOKIE] = '';
}
}
if (!empty($_COOKIE[AUTH_COOKIE])) {
$_COOKIE[AUTH_COOKIE] = '';
}
if (!empty($_COOKIE[SECURE_AUTH_COOKIE])) {
$_COOKIE[SECURE_AUTH_COOKIE] = '';
}
if (!empty($_COOKIE[LOGGED_IN_COOKIE])) {
$_COOKIE[LOGGED_IN_COOKIE] = '';
}
}
public function onValidCookie($cookie_elements, $user) {
/*
* As all meta values get cached on user load this should not require
* any extra work for the common case of no stored value.
*/
public function onValidCookie($cookie_elements, $user) {
/*
* As all meta values get cached on user load this should not require
* any extra work for the common case of no stored value.
*/
if (get_user_meta($user->ID, 'kompass_limit_login_previous_cookie')) {
delete_user_meta($user->ID, 'kompass_limit_login_previous_cookie');
}
}
if (get_user_meta($user->ID, 'kompass_limit_login_previous_cookie')) {
delete_user_meta($user->ID, 'kompass_limit_login_previous_cookie');
}
}
function clearLoginCookie($cookie_elements) {
$this->clearAuthCookie();
function clearLoginCookie($cookie_elements) {
$this->clearAuthCookie();
/*
* Under some conditions an invalid auth cookie will be used multiple
* times, which results in multiple failed attempts from that one
* cookie.
*
* Unfortunately I've not been able to replicate this consistently and
* thus have not been able to make sure what the exact cause is.
*
* Probably it is because a reload of for example the admin dashboard
* might result in multiple requests from the browser before the invalid
* cookie can be cleard.
*
* Handle this by only counting the first attempt when the exact same
* cookie is attempted for a user.
*/
/*
* Under some conditions an invalid auth cookie will be used multiple
* times, which results in multiple failed attempts from that one
* cookie.
*
* Unfortunately I've not been able to replicate this consistently and
* thus have not been able to make sure what the exact cause is.
*
* Probably it is because a reload of for example the admin dashboard
* might result in multiple requests from the browser before the invalid
* cookie can be cleard.
*
* Handle this by only counting the first attempt when the exact same
* cookie is attempted for a user.
*/
extract($cookie_elements, EXTR_OVERWRITE);
extract($cookie_elements, EXTR_OVERWRITE);
// Check if cookie is for a valid user
$user = get_user_by('login', $username);
if (!$user) {
// "shouldn't happen" for this action
$this->onFailedLogin($username);
return;
}
// Check if cookie is for a valid user
$user = get_user_by('login', $username);
if (!$user) {
// "shouldn't happen" for this action
$this->onFailedLogin($username);
return;
}
$previous_cookie = get_user_meta($user->ID, 'kompass_limit_login_previous_cookie', true);
if ($previous_cookie && $previous_cookie == $cookie_elements) {
// Identical cookies, ignore this attempt
return;
}
$previous_cookie = get_user_meta($user->ID, 'kompass_limit_login_previous_cookie', true);
if ($previous_cookie && $previous_cookie == $cookie_elements) {
// Identical cookies, ignore this attempt
return;
}
// Store cookie
if ($previous_cookie)
update_user_meta($user->ID, 'kompass_limit_login_previous_cookie', $cookie_elements);
else
add_user_meta($user->ID, 'kompass_limit_login_previous_cookie', $cookie_elements, true);
// Store cookie
if ($previous_cookie)
update_user_meta($user->ID, 'kompass_limit_login_previous_cookie', $cookie_elements);
else
add_user_meta($user->ID, 'kompass_limit_login_previous_cookie', $cookie_elements, true);
$this->onFailedLogin($username);
}
$this->onFailedLogin($username);
}
public function handleCookies() {
if ($this->isLoginAllowedFromIp()) {
return;
}
public function handleCookies() {
if ($this->isLoginAllowedFromIp()) {
return;
}
$this->clearAuthCookie();
}
$this->clearAuthCookie();
}
}

View File

@ -8,7 +8,7 @@ class OptionsPage
public function __construct()
{
add_options_page(BDP_LV_PLUGIN_SLUG . '-limit-login-attempts',
'Protect Login',
__('Login-Protection', BDP_LV_PLUGIN_SLUG),
'site-health.php',
BDP_LV_PLUGIN_SLUG . '-limit-login-attempts',
[$this, 'limit_login_option_page'],2048);
@ -21,19 +21,52 @@ class OptionsPage
update_option('kompass_limit_login_lockouts', $allIps);
}
public static function addToBlocklist(string $ip)
{
$blockedIps = get_option('kompass_limit_login_blocklist', []);
$blockedIps[] = $ip;
update_option('kompass_limit_login_blocklist', $blockedIps);
}
public static function addToAllowlist(string $ip)
{
$allowedIps = get_option('kompass_limit_login_allowlist', []);
$allowedIps[] = $ip;
update_option('kompass_limit_login_allowlist', $allowedIps);
self::releaseIp($ip);
}
public function removeFromList(string $listType, string $ip){
update_option(
'kompass_limit_login_' . $listType,
array_diff(get_option('kompass_limit_login_' . $listType, []), [$ip])
);
}
public function getBlockedIps()
{
$ips = '';
foreach (get_option('kompass_limit_login_lockouts', []) as $ip => $blockedUntil) {
$ips .= '<tr>' .
'<td style="padding-right: 10px;">' . $ip . '</td>' .
'<td style="padding-right: 10px;">' . date('d.m.Y H:i', $blockedUntil) . ' Uhr</td>' .
'<td>
<a href="admin.php?page=bdp-kompass-limit-login-attempts&tab=tab4&action=release&ip=' .
base64_encode($ip) . '">Freigeben</a></td>' .
'</tr>';
};
$ips .= '<tr style="vertical-align: top;">' .
'<td style="padding-right: 50px;">' . $ip . '</td>';
if (in_array($ip, get_option('protect_login_limit_login_blocklist', []))) {
$ips .= '<td style="padding-right: 50px;">Dauerhaft blockiert</td>' .
'<td>' .
'Keine Aktion möglich' .
'</td></tr>';
} else {
$ips .= '<td style="padding-right: 50px;">' . date('d.m.Y', $blockedUntil) . '<br />' . date('H:i', $blockedUntil) . ' Uhr</td>' .
'<td>
<a href="admin.php?page=kompass-limit-login-attempts&tab=tab4&action=release&ip=' .
base64_encode($ip) . '">' . __('Release ip address', BDP_LV_PLUGIN_SLUG) . '</a><br />
<a href="admin.php?page=bdp-kompass-limit-login-attempts&tab=tab4&action=toBlock&ip=' .
base64_encode($ip) . '">' . __('Add ip address to blocklist', BDP_LV_PLUGIN_SLUG) . '</a><br />
<a href="admin.php?page=bdp-kompass-limit-login-attempts&tab=tab4&action=toAllow&ip=' .
base64_encode($ip) . '">' . __('Add ip address to allowlist and release', BDP_LV_PLUGIN_SLUG) . '</a><br /> ' .
'</td></tr>';
};
}
return $ips;
}
@ -44,14 +77,14 @@ class OptionsPage
if (isset($_POST['update_options'])) {
update_settings($_POST);
$showMessage = 'Die Einstellungen wurden gespeichert';
$showMessage = __('The settings were saved.', BDP_LV_PLUGIN_SLUG);
}
if (isset($_GET['action']) && $_GET['action'] == 'release') {
$showMessage = 'Die IP-Adresse wurde freigegeben.';
$showMessage = __('The ip address was released.', BDP_LV_PLUGIN_SLUG);
}
if(isset($_POST['save_kompass_balist_list_type'])) {
$showMessage = 'Die Liste wurde gespeichert.';
$showMessage = __('The list was saved.', BDP_LV_PLUGIN_SLUG);
}
if (null !== $showMessage && $errors === false) {
@ -62,15 +95,26 @@ class OptionsPage
if ($errors) {
echo '<div class="notice notice-error" style="padding: 5px 10px;">';
echo 'Beim Durchführen der Aktion ist ein Fehler aufgetreten.';
echo __('An error occured.', BDP_LV_PLUGIN_SLUG);
echo '</div>';
}
$tab = isset($_GET['tab']) ? $_GET['tab'] : 'tab1';
if (isset($_GET['action']) && $_GET['action'] == 'removeFromList') {
$this->removeFromList($_GET['list'], base64_decode($_GET['ip']));
if ($_GET['list'] == 'blocklist') {
$tab = 'tab2';
} else {
$tab = 'tab3';
}
}
?>
<div class="wrap">
<h1 class="wp-heading-inline">Protect Login - Einstellungen</h1>
<h1 class="wp-heading-inline">
<?= __('Login-Protection', BDP_LV_PLUGIN_SLUG); ?> - <?= __('Settings', BDP_LV_PLUGIN_SLUG); ?></h1>
<hr class="wp-header-end">
<?= kompass_print_tab_header($tab); ?>
@ -101,22 +145,39 @@ class OptionsPage
if (isset($_GET['action']) && $_GET['action'] == 'release') {
$this->releaseIp(base64_decode($_GET['ip']));
}
if (isset($_GET['action']) && $_GET['action'] == 'toBlock') {
$this->addToBlocklist(base64_decode($_GET['ip']));
}
if (isset($_GET['action']) && $_GET['action'] == 'toAllow') {
$this->addToAllowlist(base64_decode($_GET['ip']));
}
$blockedIps = $this->getBlockedIps();
?>
<h3>Gesperrte IPs</h3>
<?php
echo '<h2>'. __('Blocked IP addresses', BDP_LV_PLUGIN_SLUG) .'</h2>';
if (strlen($blockedIps) == 0) {
echo '<div class="protect-login-no-blocked-ips">';
echo 'Derzeit sind keine Adressen gesperrt.';
echo '<div class="bdp-kompass-no-blocked-ips">';
echo __('There are no ip addresses blocked.', BDP_LV_PLUGIN_SLUG);
echo '</div>';
} else { ?>
<table>
<tr>
<th>IP</th>
<th>Gesperrt bis</th>
<th>Aktion</th>
</tr>
<?= $blockedIps ?>
<p style="width: 100%; text-align: right">
<input type="text" id="searchInput"
onkeyup="searchTable('myTable', this)"
placeholder="<?=__('Search for ip address', BDP_LV_PLUGIN_SLUG); ?>">
</p>
<table class="wp-list-table widefat fixed striped table-view-list" id="myTable">
<thead>
<tr>
<th scope="col" class="manage-column column-name"><?= __('IP address', BDP_LV_PLUGIN_SLUG); ?></th>
<th class="manage-column column-name"><?= __('Blocked until', BDP_LV_PLUGIN_SLUG); ?></th>
<th class="manage-column column-name"><?= __('Actions', BDP_LV_PLUGIN_SLUG); ?></th>
</tr>
</thead>
<tbody>
<?= $blockedIps ?>
</tbody>
</table>
<?php
}