Commit d4e133d3 authored by Dominik Hebeler's avatar Dominik Hebeler

Humanverification now uses Cache functionality

parent d2714c3d
......@@ -6,7 +6,7 @@ use Captcha;
use Carbon;
use Illuminate\Hashing\BcryptHasher as Hasher;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Redis;
use Illuminate\Support\Facades\Cache;
use Input;
class HumanVerification extends Controller
......@@ -15,25 +15,24 @@ class HumanVerification extends Controller
const EXPIRELONG = 60 * 60 * 24 * 14;
const EXPIRESHORT = 60 * 60 * 72;
public static function captcha(Request $request, Hasher $hasher, $id, $url = null)
public static function captcha(Request $request, Hasher $hasher, $id, $uid, $url = null)
{
$redis = Redis::connection('redisCache');
if ($url != null) {
$url = base64_decode(str_replace("<<SLASH>>", "/", $url));
} else {
$url = $request->input('url');
}
$userlist = Cache::get(HumanVerification::PREFIX . "." . $id, []);
$user = null;
if (sizeof($userlist) === 0 || empty($userlist[$uid])) {
return redirect('/');
} else {
$user = $userlist[$uid];
}
if ($request->getMethod() == 'POST') {
$user = $redis->hgetall(HumanVerification::PREFIX . "." . $id);
$user = ['uid' => $user["uid"],
'id' => $user["id"],
'unusedResultPages' => intval($user["unusedResultPages"]),
'whitelist' => filter_var($user["whitelist"], FILTER_VALIDATE_BOOLEAN),
'locked' => filter_var($user["locked"], FILTER_VALIDATE_BOOLEAN),
"lockedKey" => $user["lockedKey"],
];
$lockedKey = $user["lockedKey"];
$key = $request->input('captcha');
......@@ -41,11 +40,11 @@ class HumanVerification extends Controller
if (!$hasher->check($key, $lockedKey)) {
$captcha = Captcha::create("default", true);
$pipeline = $redis->pipeline();
$pipeline->hset(HumanVerification::PREFIX . "." . $id, 'lockedKey', $captcha["key"]);
$pipeline->expire(HumanVerification::PREFIX . "." . $id, $user["whitelist"] ? HumanVerification::EXPIRELONG : HumanVerification::EXPIRESHORT);
$pipeline->execute();
$user["lockedKey"] = $captcha["key"];
HumanVerification::saveUser($user);
return view('humanverification.captcha')->with('title', 'Bestätigung notwendig')
->with('uid', $user["uid"])
->with('id', $id)
->with('url', $url)
->with('image', $captcha["img"])
......@@ -54,13 +53,11 @@ class HumanVerification extends Controller
# If we can unlock the Account of this user we will redirect him to the result page
if ($user !== null && $user["locked"]) {
# The Captcha was correct. We can remove the key from the user
# If the sum of all users with that ip is too high we need to whitelist the user or they will receive a captcha again on the next request
$sum = 0;
$users = [];
$pipeline = $redis->pipeline();
$pipeline->hmset(HumanVerification::PREFIX . "." . $id, ['locked' => "0", 'lockedKey' => "", 'whitelist' => '1']);
$pipeline->expire(HumanVerification::PREFIX . "." . $id, $user["whitelist"] ? HumanVerification::EXPIRELONG : HumanVerification::EXPIRESHORT);
$pipeline->execute();
# Additionally we will whitelist him so he is not counted towards botnetwork
$user["locked"] = false;
$user["lockedKey"] = "";
$user["whitelist"] = true;
HumanVerification::saveUser($user);
return redirect($url);
} else {
return redirect('/');
......@@ -68,11 +65,11 @@ class HumanVerification extends Controller
}
}
$captcha = Captcha::create("default", true);
$pipeline = $redis->pipeline();
$pipeline->hset(HumanVerification::PREFIX . "." . $id, 'lockedKey', $captcha["key"]);
$pipeline->expire(HumanVerification::PREFIX . "." . $id, $user["whitelist"] ? HumanVerification::EXPIRELONG : HumanVerification::EXPIRESHORT);
$pipeline->execute();
$user["lockedKey"] = $captcha["key"];
HumanVerification::saveUser($user);
return view('humanverification.captcha')->with('title', 'Bestätigung notwendig')
->with('uid', $user["uid"])
->with('id', $id)
->with('url', $url)
->with('image', $captcha["img"]);
......@@ -104,9 +101,38 @@ class HumanVerification extends Controller
return redirect($url);
}
private static function saveUser($user)
{
$userList = Cache::get(HumanVerification::PREFIX . "." . $user["id"], []);
$userList[$user["uid"]] = $user;
if ($user["whitelist"]) {
$user["expiration"] = now()->addWeeks(2);
} else {
$user["expiration"] = now()->addHours(72);
}
Cache::put(HumanVerification::PREFIX . "." . $user["id"], $userList, now()->addWeeks(2));
}
private static function deleteUser($user)
{
$userList = Cache::get(HumanVerification::PREFIX . "." . $user["id"], []);
$newUserList = [];
$changed = false;
foreach ($userList as $uid => $userTmp) {
if ($userTmp["uid"] !== $user["uid"]) {
$newUserList[$userTmp["uid"]] = $userTmp;
} else {
$changed = true;
}
}
if ($changed) {
Cache::put(HumanVerification::PREFIX . "." . $user["id"], $userList, now()->addWeeks(2));
}
}
private static function removeUser($request, $uid)
{
$redis = Redis::connection('redisCache');
$ip = $request->ip();
$id = "";
if (HumanVerification::couldBeSpammer($ip)) {
......@@ -115,60 +141,34 @@ class HumanVerification extends Controller
$id = hash("sha512", $ip);
}
$userList = $redis->smembers(HumanVerification::PREFIX . "." . $id);
$pipe = $redis->pipeline();
foreach ($userList as $userid) {
$pipe->hgetall(HumanVerification::PREFIX . "." . $userid);
$userlist = Cache::get(HumanVerification::PREFIX . "." . $id, []);
$user = null;
if (sizeof($userlist) === 0 || empty($userlist[$uid])) {
return;
} else {
$user = $userlist[$uid];
}
$usersData = $pipe->execute();
$user = [];
$users = [];
$sum = 0;
foreach ($usersData as $userTmp) {
if (empty($userTmp)) {
continue;
}
$userNew = ['uid' => $userTmp["uid"],
'id' => $userTmp["id"],
'unusedResultPages' => intval($userTmp["unusedResultPages"]),
'whitelist' => filter_var($userTmp["whitelist"], FILTER_VALIDATE_BOOLEAN),
'locked' => filter_var($userTmp["locked"], FILTER_VALIDATE_BOOLEAN),
"lockedKey" => $userTmp["lockedKey"],
];
if ($uid === $userTmp["uid"]) {
$user = $userNew;
} else {
$users[] = $userNew;
}
if ($userNew["whitelist"]) {
foreach ($userlist as $uidTmp => $userTmp) {
if (!empty($userTmp) && !empty($userTmp["whitelist"]) && !$userTmp["whitelist"]) {
$sum += intval($userTmp["unusedResultPages"]);
}
}
if (empty($user)) {
return;
}
$pipeline = $redis->pipeline();
# Check if we have to whitelist the user or if we can simply delete the data
if ($user["unusedResultPages"] < $sum && !$user["whitelist"]) {
# Whitelist
$pipeline->hset(HumanVerification::PREFIX . "." . $uid, 'whitelist', "1");
$user["whitelist"] = true;
}
if ($user["whitelist"]) {
$pipeline->hset(HumanVerification::PREFIX . "." . $uid, 'unusedResultPages', "0");
$user["unusedResultPages"] = 0;
HumanVerification::saveUser($user);
} else {
$pipeline->del(HumanVerification::PREFIX . "." . $uid);
$pipeline->srem(HumanVerification::PREFIX . "." . $id, $uid);
HumanVerification::deleteUser($user);
}
$pipeline->expire(HumanVerification::PREFIX . "." . $uid, $user["whitelist"] ? HumanVerification::EXPIRELONG : HumanVerification::EXPIRESHORT);
$pipeline->expire(HumanVerification::PREFIX . "." . $id, HumanVerification::EXPIRELONG);
$pipeline->execute();
}
private static function checkId($request, $id)
......
......@@ -6,7 +6,7 @@ use Captcha;
use Closure;
use Cookie;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Redis;
use Illuminate\Support\Facades\Cache;
use URL;
class HumanVerification
......@@ -24,7 +24,6 @@ class HumanVerification
$user = null;
$update = true;
$prefix = "humanverification";
$redis = Redis::connection('redisCache');
try {
$ip = $request->ip();
$id = "";
......@@ -50,56 +49,35 @@ class HumanVerification
}
# Get all Users of this IP
$userList = $redis->smembers($prefix . "." . $id);
$pipe = $redis->pipeline();
foreach ($userList as $userid) {
$pipe->hgetall($prefix . "." . $userid);
}
$usersData = $pipe->execute();
$users = Cache::get($prefix . "." . $id, []);
$users = $this->removeOldUsers($users);
$user = [];
$users = [];
# Lock out everyone in a Bot network
# Find out how many requests this IP has made
$sum = 0;
foreach ($usersData as $index => $userTmp) {
if (empty($userTmp)) {
// This is a key that has been expired and should be deleted
$redis->srem($prefix . "." . $id, $userList[$index]);
continue;
}
$userNew = ['uid' => $userTmp["uid"],
'id' => $userTmp["id"],
'unusedResultPages' => intval($userTmp["unusedResultPages"]),
'whitelist' => filter_var($userTmp["whitelist"], FILTER_VALIDATE_BOOLEAN),
'locked' => filter_var($userTmp["locked"], FILTER_VALIDATE_BOOLEAN),
"lockedKey" => $userTmp["lockedKey"],
];
if ($uid === $userTmp["uid"]) {
$user = $userNew;
} else {
$users[] = $userNew;
}
if (!$userNew["whitelist"]) {
$sum += intval($userTmp["unusedResultPages"]);
}
}
# If this user doesn't have an entry we will create one
if (empty($user)) {
$user =
[
if (empty($users[$uid])) {
$user = [
'uid' => $uid,
'id' => $id,
'unusedResultPages' => 0,
'whitelist' => false,
'locked' => false,
"lockedKey" => "",
"expiration" => now()->addWeeks(2),
];
} else {
$user = $users[$uid];
}
# Lock out everyone in a Bot network
# Find out how many requests this IP has made
$sum = 0;
// Defines if this is the only user using that IP Adress
$alone = true;
foreach ($users as $uid => $userTmp) {
if (!$userTmp["whitelist"]) {
$sum += $userTmp["unusedResultPages"];
if ($userTmp["uid"] != $uid) {
$alone = false;
}
}
}
# A lot of automated requests are from websites that redirect users to our result page.
......@@ -116,14 +94,6 @@ class HumanVerification
}
// Defines if this is the only user using that IP Adress
$alone = true;
foreach ($users as $userTmp) {
if ($userTmp["uid"] != $uid && !$userTmp["whitelist"]) {
$alone = false;
}
}
if ((!$alone && $sum >= 50 && !$user["whitelist"]) || $refererLock) {
$user["locked"] = true;
}
......@@ -136,7 +106,8 @@ class HumanVerification
new Response(
view('humanverification.captcha')
->with('title', "Bestätigung erforderlich")
->with('id', $uid)
->with('uid', $uid)
->with('id', $id)
->with('url', url()->full())
->with('image', $captcha["img"])
);
......@@ -156,31 +127,14 @@ class HumanVerification
}
}
} catch (\Predis\Connection\ConnectionException $e) {
$update = false;
} finally {
if ($update) {
// Update the user in the database
$pipeline = $redis->pipeline();
$pipeline->hmset($prefix . "." . $user['uid'], $user);
$pipeline->sadd($prefix . "." . $user["id"], $user["uid"]);
// Expire in two weeks
$expireLong = 60 * 60 * 24 * 14;
// Expire in 72h
$expireShort = 60 * 60 * 72;
if ($user["whitelist"]) {
$pipeline->expire($prefix . "." . $user['uid'], $expireLong);
$user["expiration"] = now()->addWeeks(2);
} else {
$pipeline->expire($prefix . "." . $user['uid'], $expireShort);
$user["expiration"] = now()->addHours(72);
}
$pipeline->expire($prefix . "." . $user["id"], $expireLong);
$pipeline->execute();
$this->setUser($prefix, $user);
}
}
......@@ -188,4 +142,38 @@ class HumanVerification
return $next($request);
}
public function setUser($prefix, $user)
{
// Lock must be acquired within 2 seconds
$userList = Cache::get($prefix . "." . $user["id"], []);
$userList[$user["uid"]] = $user;
Cache::put($prefix . "." . $user["id"], $userList, now()->addWeeks(2));
}
public function removeOldUsers($userList)
{
$newUserlist = [];
$now = now();
$id = "";
$changed = false;
foreach ($userList as $uid => $user) {
$id = $user["id"];
if ($now < $user["expiration"]) {
$newUserlist[$user["uid"]] = $user;
} else {
$changed = true;
}
}
if ($changed) {
// Lock must be acquired within 2 seconds
Cache::lock($prefix . "." . $user["id"])->block(2, function () {
Cache::put($prefix . "." . $user["id"], $newUserlist, now()->addWeeks(2));
});
}
return $newUserlist;
}
}
......@@ -135,13 +135,6 @@ return [
'port' => env('REDIS_PORT', 6379),
'database' => 0,
],
'redisCache' => [
'host' => env('REDIS_CACHE_HOST', 'localhost'),
'password' => env('REDIS_CACHE_PASSWORD', env('REDIS_PASSWORD', null)),
'port' => env('REDIS_CACHE_PORT', 6379),
'database' => 2,
],
],
];
......@@ -7,8 +7,9 @@
<p>@lang('captcha.2')</p>
<p>@lang('captcha.3')</p>
<p>@lang('captcha.4')</p>
<form method="post" action="{{ LaravelLocalization::getLocalizedURL(LaravelLocalization::getCurrentLocale(), route('verification', ['id' => $id])) }}">
<form method="post" action="{{ LaravelLocalization::getLocalizedURL(LaravelLocalization::getCurrentLocale(), route('verification', ['id' => $id, 'uid' => $uid])) }}">
<input type="hidden" name="url" value="{!! $url !!}">
<input type="hidden" name="uid" value="{{ $uid }}">
<input type="hidden" name="id" value="{{ $id }}">
<p><img src="{{ $image }}" /></p>
@if(isset($errorMessage))
......
......@@ -6,6 +6,6 @@ Route::group(
'middleware' => [ 'localeSessionRedirect', 'localizationRedirect' ]*/
],
function () {
Route::match(['get', 'post'], 'meta/verification/{id}/{url?}', 'HumanVerification@captcha')->name('verification');
Route::match(['get', 'post'], 'meta/verification/{id}/{uid}/{url?}', 'HumanVerification@captcha')->name('verification');
}
);
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment