Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 72
0.00% covered (danger)
0.00%
0 / 11
CRAP
0.00% covered (danger)
0.00%
0 / 1
SessionHuman
0.00% covered (danger)
0.00%
0 / 72
0.00% covered (danger)
0.00%
0 / 11
1560
0.00% covered (danger)
0.00%
0 / 1
 writeVerifySession
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
6
 writeBotSession
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 redirectOnSuspicion
0.00% covered (danger)
0.00%
0 / 31
0.00% covered (danger)
0.00%
0 / 1
156
 isOverAged
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
12
 isUnderAged
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
12
 addStep
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
12
 hasStep
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
12
 hasStepMaxReload
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
20
 isVerified
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
12
 isOrigin
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
12
 writeRedirectCaptcha
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
1<?php
2
3namespace BO\Slim\Middleware\Session;
4
5use Psr\Http\Message\ServerRequestInterface;
6
7/**
8 * Check if human
9 */
10class SessionHuman extends SessionContainer
11{
12    const int MAX_RELOAD = 10;
13
14    const int MAX_TIME = 1800;
15
16    const int MIN_TIME = 3;
17
18    /** @psalm-api */
19    public function writeVerifySession(ServerRequestInterface $request, string $origin = ''): void
20    {
21        $clientIp = $request->getAttribute('ip_address');
22        $this->set('client', 1, 'human');
23        $this->set('ts', time(), 'human');
24        if (! $this->isOrigin('captcha')) {
25            $this->set('origin', $origin, 'human');
26        }
27        $this->set('remoteAddress', $clientIp, 'human');
28    }
29
30    /** @psalm-api */
31    public function writeBotSession(string $origin = ''): void
32    {
33        $this->set('client', 0, 'human');
34        $this->set('ts', 0, 'human');
35        $this->set('origin', $origin, 'human');
36    }
37
38    /**
39     * @SuppressWarnings(Complexity)
40     * @param string[] $requiredSteps
41     * @psalm-api
42     */
43    public function redirectOnSuspicion(
44        ServerRequestInterface $request,
45        array $requiredSteps = [],
46        string|false $referer = false
47    ): bool {
48        $path = $request->getUri()->getPath();
49        $sessionId = session_id();
50        $sessionId = $sessionId === false ? '' : $sessionId;
51        $refererLabel = is_string($referer) ? $referer : '';
52        if (! $this->isOrigin('captcha')) {
53            foreach ($requiredSteps as $stepName) {
54                if (!$this->hasStep($stepName)) {
55                    \App::$log->notice(
56                        "[Human " . $sessionId . "] Missing step $stepName on " . $path . " (referer: " . $refererLabel . ")"
57                    );
58                    $this->writeRedirectCaptcha($path, $stepName);
59                    return true;
60                }
61                if ($this->hasStepMaxReload($stepName)) {
62                    \App::$log->notice(
63                        "[Human " . $sessionId . "] Exceeded max reload for step $stepName on " . $path
64                    );
65                    $this->writeRedirectCaptcha($path, ($referer !== false) ? $referer : end($requiredSteps));
66                    return true;
67                }
68            }
69            $clientIpAddress = $request->getAttribute('ip_address');
70            if (!$this->has('remoteAddress', 'human') || $clientIpAddress != $this->get('remoteAddress', 'human')) {
71                \App::$log->error("[Human " . $sessionId . "] Missing remote address " . $clientIpAddress);
72                $this->writeRedirectCaptcha($path, $referer);
73                return true;
74            }
75        }
76        if (!$this->isVerified()) {
77            \App::$log->error(
78                "[Human " . $sessionId . "] Missing session on " . $path . " (referer: " . $refererLabel . ")"
79            );
80            $this->writeRedirectCaptcha($path, $referer);
81            return true;
82        }
83        $this->writeRedirectCaptcha($path, ($referer !== false) ? $referer : end($requiredSteps));
84        return false;
85    }
86
87    /** @psalm-api */
88    public function isOverAged(): bool
89    {
90        if (!$this->has('ts', 'human') || time() > ($this->get('ts', 'human') + self::MAX_TIME)) {
91            return true;
92        }
93        return false;
94    }
95
96    /** @psalm-api */
97    public function isUnderAged(): bool
98    {
99        if (!$this->has('ts', 'human') || time() < ($this->get('ts', 'human') + self::MIN_TIME)) {
100            return true;
101        }
102        return false;
103    }
104
105    /** @psalm-api */
106    public function addStep(string $stepName): void
107    {
108        if (!$this->has('step', 'human')) {
109            $this->set('step', array(), 'human');
110        }
111        if (!array_key_exists($stepName, $this->get('step', 'human'))) {
112            $stepCount = 1;
113        } else {
114            $stepCount = $this->get('step', 'human')[$stepName] + 1;
115        }
116        $step = $this->get('step', 'human');
117        $step[$stepName] = $stepCount;
118        $this->setGroup(array('human' => array('step' => $step)));
119    }
120
121    public function hasStep(string $stepName): bool
122    {
123        if ($this->has('step', 'human') && array_key_exists($stepName, $this->get('step', 'human'))) {
124            return true;
125        }
126        return false;
127    }
128
129    public function hasStepMaxReload(string $stepName): bool
130    {
131        if (
132            $this->has('step', 'human') &&
133            array_key_exists($stepName, $this->get('step', 'human')) &&
134            $this->get('step', 'human')[$stepName] > self::MAX_RELOAD
135        ) {
136            return true;
137        }
138        return false;
139    }
140
141    public function isVerified(): bool
142    {
143        if ($this->has('client', 'human') && $this->get('client', 'human')) {
144            return true;
145        }
146        return false;
147    }
148
149    protected function isOrigin(string $originName): bool
150    {
151        if ($this->has('origin', 'human') && $originName == $this->get('origin', 'human')) {
152            return true;
153        }
154        return false;
155    }
156
157    protected function writeRedirectCaptcha(string $path, string|false $referer = false): void
158    {
159        if (false === $referer) {
160            $referer = basename($path);
161        }
162        $referer = array('human' => array('referer' => $referer));
163        $this->setGroup($referer);
164    }
165}