HumanVerification.php 6.94 KB
Newer Older
Dominik Hebeler's avatar
Dominik Hebeler committed
1
2
3
4
<?php

namespace App\Http\Middleware;

5
use Cache;
Dominik Hebeler's avatar
Dominik Hebeler committed
6
use Closure;
7
use Cookie;
8
use Log;
Dominik Hebeler's avatar
Dominik Hebeler committed
9
use URL;
Dominik Hebeler's avatar
Dominik Hebeler committed
10
11
12
13
14
15
16
17
18
19

class HumanVerification
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
20
    public function handle($request, Closure $next)
Dominik Hebeler's avatar
Dominik Hebeler committed
21
    {
22
23
24
25
        if ($request->filled("loadMore") && Cache::has($request->input("loadMore"))) {
            return $next($request);
        }

26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
        // Check for a valid Skip Token
        if ($request->filled("token")) {
            $prefix = \App\Http\Controllers\HumanVerification::TOKEN_PREFIX;
            $token = $prefix . $request->input("token");

            if (Cache::has($token)) {
                $value = Cache::get($token);

                if (!empty($value) && intval($value) > 0) {
                    Cache::decrement($token);
                    return $next($request);
                } else {
                    // Token is not valid. Remove it
                    Cache::forget($token);
                    return redirect()->to(url()->current() . '?' . http_build_query($request->except(["token", "headerPrinted", "jskey"])));
                }
            } else {
                return redirect()->to(url()->current() . '?' . http_build_query($request->except(["token", "headerPrinted", "jskey"])));
            }
        }

47
48
        // The specific user
        $user = null;
49
        $update = true;
50
        $prefix = "humanverification";
Dominik Hebeler's avatar
Dominik Hebeler committed
51
        try {
Dominik Hebeler's avatar
Dominik Hebeler committed
52
53
54
            $ip = $request->ip();
            $id = "";
            $uid = "";
55
56
57
58

            $spamID = \App\Http\Controllers\HumanVerification::couldBeSpammer($ip);
            if (!empty($spamID)) {
                $id = hash("sha1", $spamID);
59
                $uid = hash("sha1", $spamID . $ip . $_SERVER["AGENT"] . "uid");
Dominik Hebeler's avatar
Dominik Hebeler committed
60
            } else {
Dominik Hebeler's avatar
Dominik Hebeler committed
61
62
                $id = hash("sha1", $ip);
                $uid = hash("sha1", $ip . $_SERVER["AGENT"] . "uid");
Dominik Hebeler's avatar
Dominik Hebeler committed
63
            }
Dominik Hebeler's avatar
Dominik Hebeler committed
64
65
66
            unset($_SERVER["AGENT"]);

            /**
67
             * If the user sends a valid key or an appversion
Dominik Hebeler's avatar
Dominik Hebeler committed
68
69
70
71
             * We will not verificate the user.
             * If someone that uses a bot finds this out we
             * might have to change it at some point.
             */
72
73

            //use parameter for middleware to skip this when using associator
74
            if (!config("metager.metager.botprotection.enabled") || app('App\Models\Key')->getStatus()) {
75
76
                $update = false;
                return $next($request);
Dominik Hebeler's avatar
Dominik Hebeler committed
77
            }
78

79
            # Get all Users of this IP
80
            $users = Cache::get($prefix . "." . $id, []);
81
            $users = $this->removeOldUsers($prefix, $users);
82
83

            $user = [];
84
85
            if (empty($users[$uid])) {
                $user = [
86
87
88
89
90
91
                    'uid' => $uid,
                    'id' => $id,
                    'unusedResultPages' => 0,
                    'whitelist' => false,
                    'locked' => false,
                    "lockedKey" => "",
92
                    "expiration" => now()->addWeeks(2),
93
                ];
94
95
96
97
98
99
100
101
            } 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;
102
            foreach ($users as $uidTmp => $userTmp) {
103
104
                if (!$userTmp["whitelist"]) {
                    $sum += $userTmp["unusedResultPages"];
105
                    if ($userTmp["uid"] !== $uid) {
106
107
108
                        $alone = false;
                    }
                }
Dominik Hebeler's avatar
Dominik Hebeler committed
109
            }
110

Dominik Hebeler's avatar
Dominik Hebeler committed
111
112
113
114
115
116
117
118
119
120
121
122
            # A lot of automated requests are from websites that redirect users to our result page.
            # We will detect those requests and put a captcha
            $referer = URL::previous();
            # Just the URL-Parameter
            $refererLock = false;
            if (stripos($referer, "?") !== false) {
                $referer = substr($referer, stripos($referer, "?") + 1);
                $referer = urldecode($referer);
                if (preg_match("/http[s]{0,1}:\/\/metager\.de\/meta\/meta.ger3\?.*?eingabe=([\w\d]+\.){1,2}[\w\d]+/si", $referer) === 1) {
                    $refererLock = true;
                }
            }
123

124
125
            if ((!$alone && $sum >= 50 && !$user["whitelist"]) || $refererLock) {
                $user["locked"] = true;
Dominik Hebeler's avatar
Dominik Hebeler committed
126
            }
127

Dominik Hebeler's avatar
Dominik Hebeler committed
128
            # If the user is locked we will force a Captcha validation
129
            if ($user["locked"]) {
130
                \App\Http\Controllers\HumanVerification::logCaptcha($request);
Dominik Hebeler's avatar
Dominik Hebeler committed
131
                return redirect()->route('captcha', ["id" => $id, "uid" => $uid, "url" => url()->full()]);
Dominik Hebeler's avatar
Dominik Hebeler committed
132
            }
133

134
            $user["unusedResultPages"]++;
Dominik Hebeler's avatar
Dominik Hebeler committed
135

136
            if ($alone || $user["whitelist"]) {
Dominik Hebeler's avatar
Dominik Hebeler committed
137
138
139
140
                # This IP doesn't need verification yet
                # The user currently isn't locked

                # We have different security gates:
Dominik Hebeler's avatar
Dominik Hebeler committed
141
                #   50 and then every 25 => Captcha validated Result Pages
Dominik Hebeler's avatar
Dominik Hebeler committed
142
                # If the user shows activity on our result page the counter will be deleted
143
                if ($user["unusedResultPages"] === 50 || ($user["unusedResultPages"] > 50 && $user["unusedResultPages"] % 25 === 0)) {
144
                    $user["locked"] = true;
Dominik Hebeler's avatar
Dominik Hebeler committed
145
146
                }
            }
147
            \App\PrometheusExporter::HumanVerificationSuccessfull();
148
149
        } catch (\Exception $e) {
            Log::error($e->getMessage());
150
            \App\PrometheusExporter::HumanVerificationError();
151
        } finally {
152
            if ($update && $user != null) {
153
                if ($user["whitelist"]) {
154
                    $user["expiration"] = now()->addWeeks(2);
155
                } else {
156
                    $user["expiration"] = now()->addHours(72);
157
                }
158
159
160
161
162
                try {
                    $this->setUser($prefix, $user);
                } catch (\Exception $e) {
                    Log::error($e->getMessage());
                }
163
            }
Dominik Hebeler's avatar
Dominik Hebeler committed
164
        }
165

166
        $request->request->add(['verification_id' => $user["uid"], 'verification_count' => $user["unusedResultPages"]]);
Dominik Hebeler's avatar
Dominik Hebeler committed
167
168
        return $next($request);
    }
169
170
171
172
173

    public function setUser($prefix, $user)
    {
        $userList = Cache::get($prefix . "." . $user["id"], []);
        $userList[$user["uid"]] = $user;
174
        Cache::put($prefix . "." . $user["id"], $userList, 2 * 7 * 24 * 60 * 60);
175
176
    }

177
    public function removeOldUsers($prefix, $userList)
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
    {
        $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) {
194
            Cache::put($prefix . "." . $user["id"], $newUserlist, 2 * 7 * 24 * 60 * 60);
195
196
197
198
        }

        return $newUserlist;
    }
Dominik Hebeler's avatar
Dominik Hebeler committed
199
}