Commit 6d1f99d1 authored by Dominik Hebeler's avatar Dominik Hebeler
Browse files

Merge branch '865-optimize-human-verification' into 'development'

Resolve "Optimize Human Verification"

Closes #865

See merge request !1387
parents 0638fedf 4b01570c
...@@ -31,13 +31,6 @@ class Kernel extends ConsoleKernel ...@@ -31,13 +31,6 @@ class Kernel extends ConsoleKernel
DB::table('monthlyrequests')->truncate(); DB::table('monthlyrequests')->truncate();
DB::disconnect('mysql'); DB::disconnect('mysql');
})->monthlyOn(1, '00:00'); })->monthlyOn(1, '00:00');
// Delete all of the old humanverification entries
$schedule->call(function () {
DB::delete('DELETE FROM humanverification WHERE updated_at < (now() - interval 72 hour) AND whitelist = 0 ORDER BY updated_at DESC');
DB::delete('DELETE FROM humanverification WHERE updated_at < (now() - interval 2 week) AND whitelist = 1 ORDER BY updated_at DESC');
DB::disconnect('mysql');
})->everyThirtyMinutes();
} }
/** /**
......
...@@ -4,15 +4,21 @@ namespace App\Http\Controllers; ...@@ -4,15 +4,21 @@ namespace App\Http\Controllers;
use Captcha; use Captcha;
use Carbon; use Carbon;
use DB;
use Illuminate\Hashing\BcryptHasher as Hasher; use Illuminate\Hashing\BcryptHasher as Hasher;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Redis;
use Input; use Input;
class HumanVerification extends Controller class HumanVerification extends Controller
{ {
const PREFIX = "humanverification";
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, $url = null)
{ {
$redis = Redis::connection('redisCache');
if ($url != null) { if ($url != null) {
$url = base64_decode(str_replace("<<SLASH>>", "/", $url)); $url = base64_decode(str_replace("<<SLASH>>", "/", $url));
} else { } else {
...@@ -20,15 +26,26 @@ class HumanVerification extends Controller ...@@ -20,15 +26,26 @@ class HumanVerification extends Controller
} }
if ($request->getMethod() == 'POST') { if ($request->getMethod() == 'POST') {
$user = DB::table('humanverification')->where('uid', $id)->first();
$lockedKey = $user->lockedKey; $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'); $key = $request->input('captcha');
$key = strtolower($key); $key = strtolower($key);
if (!$hasher->check($key, $lockedKey)) { if (!$hasher->check($key, $lockedKey)) {
$captcha = Captcha::create("default", true); $captcha = Captcha::create("default", true);
DB::table('humanverification')->where('uid', $id)->update(['lockedKey' => $captcha["key"]]); $pipeline = $redis->pipeline();
$pipeline->hset(HumanVerification::PREFIX . "." . $id, 'lockedKey', $captcha["key"]);
$pipeline->expire(HumanVerification::PREFIX . "." . $id, $user["whitelist"] ? HumanVerification::EXPIRELONG : HumanVerification::EXPIRESHORT);
$pipeline->execute();
return view('humanverification.captcha')->with('title', 'Bestätigung notwendig') return view('humanverification.captcha')->with('title', 'Bestätigung notwendig')
->with('id', $id) ->with('id', $id)
->with('url', $url) ->with('url', $url)
...@@ -36,9 +53,15 @@ class HumanVerification extends Controller ...@@ -36,9 +53,15 @@ class HumanVerification extends Controller
->with('errorMessage', 'Fehler: Falsches Captcha eingegeben!'); ->with('errorMessage', 'Fehler: Falsches Captcha eingegeben!');
} else { } else {
# If we can unlock the Account of this user we will redirect him to the result page # If we can unlock the Account of this user we will redirect him to the result page
if ($user !== null && $user->locked === 1) { if ($user !== null && $user["locked"]) {
# The Captcha was correct. We can remove the key from the user # The Captcha was correct. We can remove the key from the user
DB::table('humanverification')->where('uid', $id)->update(['locked' => false, 'lockedKey' => "", 'whitelist' => 1]); # 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();
return redirect($url); return redirect($url);
} else { } else {
return redirect('/'); return redirect('/');
...@@ -46,7 +69,10 @@ class HumanVerification extends Controller ...@@ -46,7 +69,10 @@ class HumanVerification extends Controller
} }
} }
$captcha = Captcha::create("default", true); $captcha = Captcha::create("default", true);
DB::table('humanverification')->where('uid', $id)->update(['lockedKey' => $captcha["key"]]); $pipeline = $redis->pipeline();
$pipeline->hset(HumanVerification::PREFIX . "." . $id, 'lockedKey', $captcha["key"]);
$pipeline->expire(HumanVerification::PREFIX . "." . $id, $user["whitelist"] ? HumanVerification::EXPIRELONG : HumanVerification::EXPIRESHORT);
$pipeline->execute();
return view('humanverification.captcha')->with('title', 'Bestätigung notwendig') return view('humanverification.captcha')->with('title', 'Bestätigung notwendig')
->with('id', $id) ->with('id', $id)
->with('url', $url) ->with('url', $url)
...@@ -81,30 +107,63 @@ class HumanVerification extends Controller ...@@ -81,30 +107,63 @@ class HumanVerification extends Controller
private static function removeUser($request, $uid) private static function removeUser($request, $uid)
{ {
$redis = Redis::conection('redisCache');
$id = hash("sha512", $request->ip()); $id = hash("sha512", $request->ip());
$sum = DB::table('humanverification')->where('id', $id)->where('whitelist', false)->sum('unusedResultPages'); $userList = $redis->smembers(HumanVerification::PREFIX . "." . $id);
$user = DB::table('humanverification')->where('uid', $uid)->first(); $pipe = $redis->pipeline();
foreach ($userList as $userid) {
$pipe->hgetall(HumanVerification::PREFIX . "." . $userid);
}
$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"]) {
$sum += intval($userTmp["unusedResultPages"]);
}
}
if ($user === null) { if (empty($user)) {
return; return;
} }
$pipeline = $redis->pipeline();
# Check if we have to whitelist the user or if we can simply delete the data # Check if we have to whitelist the user or if we can simply delete the data
if ($user->unusedResultPages < $sum && $user->whitelist === 0) { if ($user["unusedResultPages"] < $sum && !$user["whitelist"]) {
# Whitelist # Whitelist
DB::table('humanverification')->where('uid', $uid)->update(['whitelist' => true, 'whitelistCounter' => 0]); $pipeline->hset(HumanVerification::PREFIX . "." . $uid, 'whitelist', "1");
$user->whitelist = 1; $user["whitelist"] = true;
$user->whitelistCounter = 0;
} }
if ($user->whitelist === 1) { if ($user["whitelist"]) {
DB::table('humanverification')->where('uid', $uid)->update(['unusedResultPages' => 0]); $pipeline->hset(HumanVerification::PREFIX . "." . $uid, 'unusedResultPages', "0");
} else { } else {
DB::table('humanverification')->where('uid', $uid)->where('updated_at', '<', Carbon::NOW()->subSeconds(2))->delete(); $pipeline->hdel(HumanVerification::PREFIX . "." . $uid);
$pipeline->srem(HumanVerification::PREFIX . "." . $id, $uid);
} }
$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) private static function checkId($request, $id)
......
...@@ -12,7 +12,6 @@ class MetaGerSearch extends Controller ...@@ -12,7 +12,6 @@ class MetaGerSearch extends Controller
{ {
public function search(Request $request, MetaGer $metager) public function search(Request $request, MetaGer $metager)
{ {
$focus = $request->input("focus", "web"); $focus = $request->input("focus", "web");
if ($focus === "maps") { if ($focus === "maps") {
......
...@@ -3,10 +3,10 @@ ...@@ -3,10 +3,10 @@
namespace App\Http\Middleware; namespace App\Http\Middleware;
use Captcha; use Captcha;
use Carbon;
use Closure; use Closure;
use DB; use Cookie;
use Illuminate\Http\Response; use Illuminate\Http\Response;
use Illuminate\Support\Facades\Redis;
use URL; use URL;
class HumanVerification class HumanVerification
...@@ -22,8 +22,9 @@ class HumanVerification ...@@ -22,8 +22,9 @@ class HumanVerification
{ {
// The specific user // The specific user
$user = null; $user = null;
$newUser = true;
$update = true; $update = true;
$prefix = "humanverification";
$redis = Redis::connection('redisCache');
try { try {
$id = hash("sha512", $request->ip()); $id = hash("sha512", $request->ip());
$uid = hash("sha512", $request->ip() . $_SERVER["AGENT"]); $uid = hash("sha512", $request->ip() . $_SERVER["AGENT"]);
...@@ -35,47 +36,61 @@ class HumanVerification ...@@ -35,47 +36,61 @@ class HumanVerification
* If someone that uses a bot finds this out we * If someone that uses a bot finds this out we
* might have to change it at some point. * might have to change it at some point.
*/ */
if ($request->filled('password') || $request->filled('key') || $request->filled('appversion') || !env('BOT_PROTECTION', false)) { if ($request->filled('password') || $request->filled('key') || Cookie::get('key') !== null || $request->filled('appversion') || !env('BOT_PROTECTION', false)) {
$update = false; $update = false;
return $next($request); return $next($request);
} }
$users = DB::select('select * from humanverification where id = ?', [$id]); # 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();
$user = [];
$users = [];
# Lock out everyone in a Bot network # Lock out everyone in a Bot network
# Find out how many requests this IP has made # Find out how many requests this IP has made
$sum = 0; $sum = 0;
foreach ($users as $userTmp) { foreach ($usersData as $index => $userTmp) {
if ($uid == $userTmp->uid) { if (empty($userTmp)) {
$user = ['uid' => $userTmp->uid, // This is a key that has been expired and should be deleted
'id' => $userTmp->id, $redis->srem($prefix . "." . $id, $userList[$index]);
'unusedResultPages' => intval($userTmp->unusedResultPages), continue;
'whitelist' => filter_var($userTmp->whitelist, FILTER_VALIDATE_BOOLEAN),
'whitelistCounter' => $userTmp->whitelistCounter,
'locked' => filter_var($userTmp->locked, FILTER_VALIDATE_BOOLEAN),
"lockedKey" => $userTmp->lockedKey,
'updated_at' => Carbon::now(),
];
$newUser = false;
} }
if ($userTmp->whitelist === 0) { $userNew = ['uid' => $userTmp["uid"],
$sum += $userTmp->unusedResultPages; '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 ($user === null) { # If this user doesn't have an entry we will create one
if (empty($user)) {
$user = $user =
[ [
'uid' => $uid, 'uid' => $uid,
'id' => $id, 'id' => $id,
'unusedResultPages' => 0, 'unusedResultPages' => 0,
'whitelist' => false, 'whitelist' => false,
'whitelistCounter' => 0,
'locked' => false, 'locked' => false,
"lockedKey" => "", "lockedKey" => "",
'updated_at' => Carbon::now(),
]; ];
} }
...@@ -96,7 +111,7 @@ class HumanVerification ...@@ -96,7 +111,7 @@ class HumanVerification
// Defines if this is the only user using that IP Adress // Defines if this is the only user using that IP Adress
$alone = true; $alone = true;
foreach ($users as $userTmp) { foreach ($users as $userTmp) {
if ($userTmp->uid != $uid && !$userTmp->whitelist) { if ($userTmp["uid"] != $uid && !$userTmp["whitelist"]) {
$alone = false; $alone = false;
} }
...@@ -133,41 +148,34 @@ class HumanVerification ...@@ -133,41 +148,34 @@ class HumanVerification
} }
} }
} catch (\Illuminate\Database\QueryException $e) { } catch (\Predis\Connection\ConnectionException $e) {
// Failure in contacting metager3.de $update = false;
} finally { } finally {
if ($update) { if ($update) {
// Update the user in the database // Update the user in the database
if ($newUser) { $pipeline = $redis->pipeline();
DB::table('humanverification')->insert(
[ $pipeline->hmset($prefix . "." . $user['uid'], $user);
'uid' => $user["uid"], $pipeline->sadd($prefix . "." . $user["id"], $user["uid"]);
'id' => $user["id"],
'unusedResultPages' => $user['unusedResultPages'], // Expire in two weeks
'whitelist' => $user["whitelist"], $expireLong = 60 * 60 * 24 * 14;
'whitelistCounter' => $user["whitelistCounter"], // Expire in 72h
'locked' => $user["locked"], $expireShort = 60 * 60 * 72;
"lockedKey" => $user["lockedKey"],
'updated_at' => $user["updated_at"], if ($user["whitelist"]) {
] $pipeline->expire($prefix . "." . $user['uid'], $expireLong);
);
} else { } else {
DB::table('humanverification')->where('uid', $uid)->update( $pipeline->expire($prefix . "." . $user['uid'], $expireShort);
[
'uid' => $user["uid"],
'id' => $user["id"],
'unusedResultPages' => $user['unusedResultPages'],
'whitelist' => $user["whitelist"],
'whitelistCounter' => $user["whitelistCounter"],
'locked' => $user["locked"],
"lockedKey" => $user["lockedKey"],
'updated_at' => $user["updated_at"],
]
);
} }
$pipeline->expire($prefix . "." . $user["id"], $expireLong);
$pipeline->execute();
} }
DB::disconnect('mysql');
} }
$request->request->add(['verification_id' => $user["uid"], 'verification_count' => $user["unusedResultPages"]]); $request->request->add(['verification_id' => $user["uid"], 'verification_count' => $user["unusedResultPages"]]);
return $next($request); return $next($request);
......
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