From cec37aceb6e131fb12f9536597bef1d665d90c9b Mon Sep 17 00:00:00 2001
From: Dominik Hebeler <dominik@suma-ev.de>
Date: Mon, 4 Mar 2019 10:39:26 +0100
Subject: [PATCH] Changed Human Verification to use Redis

---
 app/Http/Controllers/HumanVerification.php | 32 +++++++++++++++++-----
 app/Http/Middleware/HumanVerification.php  | 29 ++++++++++++--------
 2 files changed, 42 insertions(+), 19 deletions(-)

diff --git a/app/Http/Controllers/HumanVerification.php b/app/Http/Controllers/HumanVerification.php
index d236ecf29..4bf67f6a7 100644
--- a/app/Http/Controllers/HumanVerification.php
+++ b/app/Http/Controllers/HumanVerification.php
@@ -12,9 +12,12 @@ use Input;
 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)
     {
+        $redis = Redis::connection('REDIS_CACHE_HOST');
 
         if ($url != null) {
             $url = base64_decode(str_replace("<<SLASH>>", "/", $url));
@@ -24,7 +27,7 @@ class HumanVerification extends Controller
 
         if ($request->getMethod() == 'POST') {
 
-            $user = Redis::hgetall(HumanVerification::PREFIX . "." . $id);
+            $user = $redis->hgetall(HumanVerification::PREFIX . "." . $id);
             $user = ['uid' => $user["uid"],
                 'id' => $user["id"],
                 'unusedResultPages' => intval($user["unusedResultPages"]),
@@ -39,7 +42,10 @@ class HumanVerification extends Controller
 
             if (!$hasher->check($key, $lockedKey)) {
                 $captcha = Captcha::create("default", true);
-                Redis::hset(HumanVerification::PREFIX . "." . $id, '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')
                     ->with('id', $id)
                     ->with('url', $url)
@@ -49,7 +55,13 @@ 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
-                    Redis::hmset(HumanVerification::PREFIX . "." . $id, ['locked' => "0", 'lockedKey' => ""]);
+                    # 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);
                 } else {
                     return redirect('/');
@@ -57,7 +69,10 @@ class HumanVerification extends Controller
             }
         }
         $captcha = Captcha::create("default", true);
-        Redis::hset(HumanVerification::PREFIX . "." . $id, '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')
             ->with('id', $id)
             ->with('url', $url)
@@ -92,10 +107,11 @@ class HumanVerification extends Controller
 
     private static function removeUser($request, $uid)
     {
+        $redis = Redis::conection('REDIS_CACHE_HOST');
         $id = hash("sha512", $request->ip());
 
-        $userList = Redis::smembers(HumanVerification::PREFIX . "." . $id);
-        $pipe = Redis::pipeline();
+        $userList = $redis->smembers(HumanVerification::PREFIX . "." . $id);
+        $pipe = $redis->pipeline();
         foreach ($userList as $userid) {
             $pipe->hgetall(HumanVerification::PREFIX . "." . $userid);
         }
@@ -131,7 +147,7 @@ class HumanVerification extends Controller
             return;
         }
 
-        $pipeline = Redis::pipeline();
+        $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
@@ -145,6 +161,8 @@ class HumanVerification extends Controller
             $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();
     }
 
diff --git a/app/Http/Middleware/HumanVerification.php b/app/Http/Middleware/HumanVerification.php
index 216136562..bb1247aff 100644
--- a/app/Http/Middleware/HumanVerification.php
+++ b/app/Http/Middleware/HumanVerification.php
@@ -4,6 +4,7 @@ namespace App\Http\Middleware;
 
 use Captcha;
 use Closure;
+use Cookie;
 use Illuminate\Http\Response;
 use Illuminate\Support\Facades\Redis;
 use URL;
@@ -23,6 +24,7 @@ class HumanVerification
         $user = null;
         $update = true;
         $prefix = "humanverification";
+        $redis = Redis::connection('REDIS_CACHE_HOST');
         try {
             $id = hash("sha512", $request->ip());
             $uid = hash("sha512", $request->ip() . $_SERVER["AGENT"]);
@@ -34,14 +36,14 @@ class HumanVerification
              * If someone that uses a bot finds this out we
              * 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;
                 return $next($request);
             }
 
             # Get all Users of this IP
-            $userList = Redis::smembers($prefix . "." . $id);
-            $pipe = Redis::pipeline();
+            $userList = $redis->smembers($prefix . "." . $id);
+            $pipe = $redis->pipeline();
 
             foreach ($userList as $userid) {
                 $pipe->hgetall($prefix . "." . $userid);
@@ -54,8 +56,10 @@ class HumanVerification
             # Lock out everyone in a Bot network
             # Find out how many requests this IP has made
             $sum = 0;
-            foreach ($usersData as $userTmp) {
+            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"],
@@ -71,7 +75,7 @@ class HumanVerification
                 } else {
                     $users[] = $userNew;
                 }
-                if ($userNew["whitelist"]) {
+                if (!$userNew["whitelist"]) {
                     $sum += intval($userTmp["unusedResultPages"]);
                 }
 
@@ -150,22 +154,23 @@ class HumanVerification
             if ($update) {
 
                 // Update the user in the database
-                $pipeline = Redis::pipeline();
+                $pipeline = $redis->pipeline();
 
                 $pipeline->hmset($prefix . "." . $user['uid'], $user);
                 $pipeline->sadd($prefix . "." . $user["id"], $user["uid"]);
 
-                $expireDate = now();
-                $expireDateLong = date_add($expireDate, date_interval_create_from_date_string('2 weeks'))->timestamp;
-                $expireDateShort = date_add($expireDate, date_interval_create_from_date_string('2 weeks'))->timestamp;
+                // Expire in two weeks
+                $expireLong = 60 * 60 * 24 * 14;
+                // Expire in 72h
+                $expireShort = 60 * 60 * 72;
 
                 if ($user["whitelist"]) {
-                    $pipeline->expireat($prefix . "." . $user['uid'], $expireDateLong);
+                    $pipeline->expire($prefix . "." . $user['uid'], $expireLong);
                 } else {
-                    $pipeline->expireat($prefix . "." . $user['uid'], $expireDateShort);
+                    $pipeline->expire($prefix . "." . $user['uid'], $expireShort);
                 }
 
-                $pipeline->expireat($prefix . "." . $user["id"], $expireDateLong);
+                $pipeline->expire($prefix . "." . $user["id"], $expireLong);
 
                 $pipeline->execute();
             }
-- 
GitLab