Lines 11.45% 11 / 96
Methods 11.53% 3 / 26
Classes 0.00% 0 / 1
Name Lines Methods CRAP
 writeVerifySession 0.00% 0 / 6 0.00% 0 / 1 6
 writeBotSession 0.00% 0 / 3 0.00% 0 / 1 2
 redirectOnSuspicion 0.00% 0 / 31 0.00% 0 / 1 156
 isOverAged 0.00% 0 / 3 0.00% 0 / 1 12
 isUnderAged 0.00% 0 / 3 0.00% 0 / 1 12
 addStep 0.00% 0 / 8 0.00% 0 / 1 12
 hasStep 0.00% 0 / 3 0.00% 0 / 1 12
 hasStepMaxReload 0.00% 0 / 5 0.00% 0 / 1 20
 isVerified 0.00% 0 / 3 0.00% 0 / 1 12
 isOrigin 0.00% 0 / 3 0.00% 0 / 1 12
 writeRedirectCaptcha 0.00% 0 / 4 0.00% 0 / 1 6
 [BO\Slim\Middleware\Session\SessionContainer] fromContainer 100.00% 3 / 3 100.00% 1 / 1 1
 [BO\Slim\Middleware\Session\SessionContainer] setGroup 0.00% 0 / 1 0.00% 0 / 1 2
 [BO\Slim\Middleware\Session\SessionContainer] writeData 0.00% 0 / 1 0.00% 0 / 1 2
 [BO\Slim\Middleware\Session\SessionContainer] set 0.00% 0 / 1 0.00% 0 / 1 2
 [BO\Slim\Middleware\Session\SessionContainer] get 100.00% 1 / 1 100.00% 1 / 1 1
 [BO\Slim\Middleware\Session\SessionContainer] getEntity 0.00% 0 / 1 0.00% 0 / 1 2
 [BO\Slim\Middleware\Session\SessionContainer] remove 0.00% 0 / 1 0.00% 0 / 1 2
 [BO\Slim\Middleware\Session\SessionContainer] clear 0.00% 0 / 1 0.00% 0 / 1 2
 [BO\Slim\Middleware\Session\SessionContainer] restart 0.00% 0 / 1 0.00% 0 / 1 2
 [BO\Slim\Middleware\Session\SessionContainer] clearGroup 0.00% 0 / 1 0.00% 0 / 1 2
 [BO\Slim\Middleware\Session\SessionContainer] has 0.00% 0 / 1 0.00% 0 / 1 2
 [BO\Slim\Middleware\Session\SessionContainer] isEmpty 0.00% 0 / 1 0.00% 0 / 1 2
 [BO\Slim\Middleware\Session\SessionContainer] jsonSerialize 0.00% 0 / 1 0.00% 0 / 1 2
 [BO\Slim\Middleware\Session\SessionContainer] getSession 100.00% 3 / 3 100.00% 1 / 1 2
 [BO\Slim\Middleware\Session\SessionContainer] loadSession 66.66% 4 / 6 0.00% 0 / 1 3.33
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}

Inherited from BO\Slim\Middleware\Session\SessionContainer

12    public static function fromContainer(callable $sessionLoader): self
13    {
14        $instance = new self();
15        $instance->sessionLoader = $sessionLoader;
16        return $instance;
17    }
23    public function setGroup(array $group, bool $clear = false): void
24    {
25        $this->getSession()->setGroup($group, $clear);
26    }
29    public function writeData(): void
30    {
31        $this->getSession()->writeData();
32    }
38    public function set(int|string $key, mixed $value, int|string|null $groupIndex = null): void
39    {
40        $this->getSession()->set($key, $value, $groupIndex);
41    }
44    public function get(int|string $key, int|string|null $groupIndex = null, mixed $default = null): mixed
45    {
46        return $this->getSession()->get($key, $groupIndex, $default);
47    }
50    public function getEntity(): mixed
51    {
52        return $this->getSession()->getEntity();
53    }
59    public function remove(int|string $key, int|string|null $groupIndex = null): void
60    {
61        $this->getSession()->remove($key, $groupIndex);
62    }
68    public function clear(): void
69    {
70        $this->getSession()->clear();
71    }
74    public function restart(): void
75    {
76        $this->getSession()->restart();
77    }
83    public function clearGroup(int|string|null $groupIndex = null): void
84    {
85        $this->getSession()->clearGroup($groupIndex);
86    }
89    public function has(int|string $key, int|string|null $groupIndex = null): ?bool
90    {
91        return $this->getSession()->has($key, $groupIndex);
92    }
95    public function isEmpty(): bool
96    {
97        return $this->getSession()->isEmpty();
98    }
101    public function jsonSerialize(): mixed
102    {
103        return $this->getSession()->jsonSerialize();
104    }
106    private function getSession(): SessionData
107    {
108        if (!$this->sessionContainer) {
109            $this->sessionContainer = $this->loadSession();
110        }
111        return $this->sessionContainer;
112    }
114    private function loadSession(): SessionData
115    {
116        if (!is_callable($this->sessionLoader)) {
117            throw new \RuntimeException('Session loader is not set');
118        }
119        $session = ($this->sessionLoader)();
120        if (!$session instanceof SessionData) {
121            throw new \RuntimeException('Session loader must return SessionData');
122        }
123        return $session;
124    }