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

namespace App\Http\Middleware;

5
use Cache;
6
use Captcha;
Dominik Hebeler's avatar
Dominik Hebeler committed
7
use Closure;
8
use Cookie;
9
use Illuminate\Http\Response;
Dominik Hebeler's avatar
Dominik Hebeler committed
10
use URL;
Dominik Hebeler's avatar
Dominik Hebeler committed
11
12
13
14
15
16
17
18
19
20
21
22

class HumanVerification
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
23
24
        // The specific user
        $user = null;
25
        $update = true;
26
        $prefix = "humanverification";
Dominik Hebeler's avatar
Dominik Hebeler committed
27
        try {
Dominik Hebeler's avatar
Dominik Hebeler committed
28
29
30
            $ip = $request->ip();
            $id = "";
            $uid = "";
Dominik Hebeler's avatar
Bugfix    
Dominik Hebeler committed
31
            if (\App\Http\Controllers\HumanVerification::couldBeSpammer($ip)) {
Dominik Hebeler's avatar
Dominik Hebeler committed
32
33
34
35
36
37
                $id = hash("sha512", "999.999.999.999");
                $uid = hash("sha512", "999.999.999.999" . $ip . $_SERVER["AGENT"] . "uid");
            } else {
                $id = hash("sha512", $ip);
                $uid = hash("sha512", $ip . $_SERVER["AGENT"] . "uid");
            }
Dominik Hebeler's avatar
Dominik Hebeler committed
38
39
40
41
42
43
44
45
            unset($_SERVER["AGENT"]);

            /**
             * If the user sends a Password or a key
             * We will not verificate the user.
             * If someone that uses a bot finds this out we
             * might have to change it at some point.
             */
46
            if ($request->filled('password') || $request->filled('key') || Cookie::get('key') !== null || $request->filled('appversion') || !env('BOT_PROTECTION', false)) {
47
                $update = false;
Dominik Hebeler's avatar
Dominik Hebeler committed
48
49
                return $next($request);
            }
50

51
            # Get all Users of this IP
52
            $users = Cache::get($prefix . "." . $id, []);
53
            $users = $this->removeOldUsers($prefix, $users);
54
55

            $user = [];
56
57
            if (empty($users[$uid])) {
                $user = [
58
59
60
61
62
63
                    'uid' => $uid,
                    'id' => $id,
                    'unusedResultPages' => 0,
                    'whitelist' => false,
                    'locked' => false,
                    "lockedKey" => "",
64
                    "expiration" => now()->addWeeks(2),
65
                ];
66
67
68
            } else {
                $user = $users[$uid];
            }
69

70
71
72
73
74
            # 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;
75
            foreach ($users as $uidTmp => $userTmp) {
76
77
                if (!$userTmp["whitelist"]) {
                    $sum += $userTmp["unusedResultPages"];
78
                    if ($userTmp["uid"] !== $uid) {
79
80
81
                        $alone = false;
                    }
                }
Dominik Hebeler's avatar
Dominik Hebeler committed
82
            }
83
            
Dominik Hebeler's avatar
Dominik Hebeler committed
84
85
86
87
88
89
90
91
92
93
94
            # 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;
                }
Dominik Hebeler's avatar
Dominik Hebeler committed
95

Dominik Hebeler's avatar
Dominik Hebeler committed
96
            }
97

98
99
            if ((!$alone && $sum >= 50 && !$user["whitelist"]) || $refererLock) {
                $user["locked"] = true;
Dominik Hebeler's avatar
Dominik Hebeler committed
100
            }
101
            
Dominik Hebeler's avatar
Dominik Hebeler committed
102
            # If the user is locked we will force a Captcha validation
103
            if ($user["locked"]) {
Dominik Hebeler's avatar
Dominik Hebeler committed
104
                $captcha = Captcha::create("default", true);
105
                $user["lockedKey"] = $captcha["key"];
106
                \App\PrometheusExporter::CaptchaShown();
Dominik Hebeler's avatar
Dominik Hebeler committed
107
108
109
110
                return
                new Response(
                    view('humanverification.captcha')
                        ->with('title', "Bestätigung erforderlich")
111
112
                        ->with('uid', $uid)
                        ->with('id', $id)
Dominik Hebeler's avatar
Dominik Hebeler committed
113
114
115
116
                        ->with('url', url()->full())
                        ->with('image', $captcha["img"])
                );
            }
117

118
            $user["unusedResultPages"]++;
Dominik Hebeler's avatar
Dominik Hebeler committed
119

120
            if ($alone || $user["whitelist"]) {
Dominik Hebeler's avatar
Dominik Hebeler committed
121
122
123
124
                # 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
125
                #   50 and then every 25 => Captcha validated Result Pages
Dominik Hebeler's avatar
Dominik Hebeler committed
126
                # If the user shows activity on our result page the counter will be deleted
127
                if ($user["unusedResultPages"] === 50 || ($user["unusedResultPages"] > 50 && $user["unusedResultPages"] % 25 === 0)) {
128
                    $user["locked"] = true;
Dominik Hebeler's avatar
Dominik Hebeler committed
129
130
131
                }

            }
132
        } finally {
133
            if ($update) {
134
                if ($user["whitelist"]) {
135
                    $user["expiration"] = now()->addWeeks(2);
136
                } else {
137
                    $user["expiration"] = now()->addHours(72);
138
                }
139
                $this->setUser($prefix, $user);
140
            }
Dominik Hebeler's avatar
Dominik Hebeler committed
141
        }
142

143
        $request->request->add(['verification_id' => $user["uid"], 'verification_count' => $user["unusedResultPages"]]);
Dominik Hebeler's avatar
Dominik Hebeler committed
144
        return $next($request);
Dominik Hebeler's avatar
Dominik Hebeler committed
145

Dominik Hebeler's avatar
Dominik Hebeler committed
146
    }
147
148
149
150
151
152

    public function setUser($prefix, $user)
    {
        // Lock must be acquired within 2 seconds
        $userList = Cache::get($prefix . "." . $user["id"], []);
        $userList[$user["uid"]] = $user;
153
        Cache::put($prefix . "." . $user["id"], $userList, 2 * 7 * 24 * 60 * 60);
154
155
    }

156
    public function removeOldUsers($prefix, $userList)
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
    {
        $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) {
173
            Cache::put($prefix . "." . $user["id"], $newUserlist, 2 * 7 * 24 * 60 * 60);
174
175
176
177
        }

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