Completed
Push — master ( b97427...e235cc )
by Raffael
30:35 queued 26:08
created

Recaptcha::preAuthentication()   B

Complexity

Conditions 5
Paths 5

Size

Total Lines 40

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 30

Importance

Changes 0
Metric Value
dl 0
loc 40
ccs 0
cts 33
cp 0
rs 8.9688
c 0
b 0
f 0
cc 5
nc 5
nop 1
crap 30
1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * balloon
7
 *
8
 * @copyright   Copryright (c) 2012-2019 gyselroth GmbH (https://gyselroth.com)
9
 * @license     GPL-3.0 https://opensource.org/licenses/GPL-3.0
10
 */
11
12
namespace Balloon\App\Recaptcha\Hook;
13
14
use Balloon\App\Recaptcha\Exception;
15
use Balloon\Hook\AbstractHook;
16
use InvalidArgumentException;
17
use Micro\Auth\Adapter\None;
18
use Micro\Auth\Auth;
19
use Micro\Auth\Identity;
20
use MongoDB\Database;
21
use Psr\Log\LoggerInterface;
22
use ReCaptcha\ReCaptcha as CaptchaService;
23
24
class Recaptcha extends AbstractHook
25
{
26
    /**
27
     * Logger.
28
     *
29
     * @var LoggerInterface
30
     */
31
    protected $logger;
32
33
    /**
34
     * Recaptcha.
35
     *
36
     * @var CaptchaService
37
     */
38
    protected $recaptcha;
39
40
    /**
41
     * Database.
42
     *
43
     * @var Database
44
     */
45
    protected $db;
46
47
    /**
48
     * Validate captcha.
49
     *
50
     * @var int
51
     */
52
    protected $recaptcha_threshold = 30;
53
54
    /**
55
     * Constructor.
56
     */
57
    public function __construct(LoggerInterface $logger, CaptchaService $recaptcha, Database $db, array $config = [])
58
    {
59
        $this->logger = $logger;
60
        $this->recaptcha = $recaptcha;
61
        $this->db = $db;
62
        $this->setOptions($config);
63
    }
64
65
    /**
66
     * Set options.
67
     */
68
    public function setOptions(array $config = [])
69
    {
70
        foreach ($config as $option => $value) {
71
            switch ($option) {
72
                case 'recaptcha_threshold':
73
                    $this->{$option} = (int) $value;
74
75
                break;
76
                case 'hostname':
77
                    $this->recaptcha->setExpectedHostname($value);
78
79
                break;
80
                default:
81
                    throw new InvalidArgumentException('unknown option '.$option.' given');
82
            }
83
        }
84
    }
85
86
    /**
87
     * {@inheritdoc}
88
     */
89
    public function postAuthentication(Auth $auth, ?Identity $identity = null): void
90
    {
91
        $username = $_POST['username'] ?? $_SERVER['username'] ?? null;
92
93
        if ($identity === null && $username !== null) {
94
            $this->logger->info('detected unsuccessful authentication for ['.$username.'], increase failed_auth counter', [
95
                'category' => get_class($this),
96
            ]);
97
98
            $this->db->user->updateOne([
99
                'username' => $username,
100
            ], [
101
                '$inc' => [
102
                    'failed_auth' => 1,
103
                ],
104
            ]);
105
106
            return;
107
        }
108
109
        if ($identity instanceof Identity && !($identity->getAdapter() instanceof None)) {
0 ignored issues
show
Bug introduced by
The class Micro\Auth\Identity does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
Bug introduced by
The class Micro\Auth\Adapter\None does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
110
            $this->logger->info('detected successful authentication for ['.$identity->getIdentifier().']', [
111
                'category' => get_class($this),
112
            ]);
113
114
            $this->db->user->updateOne([
115
                'username' => $username,
116
            ], [
117
                '$set' => [
118
                    'failed_auth' => 0,
119
                ],
120
            ]);
121
122
            return;
123
        }
124
    }
125
126
    /**
127
     * {@inheritdoc}
128
     */
129
    public function preAuthentication(Auth $auth): void
130
    {
131
        $username = $_POST['username'] ?? $_SERVER['username'] ?? null;
132
133
        if ($username === null) {
134
            return;
135
        }
136
137
        $result = $this->db->user->findOne([
138
            'username' => $username,
139
            'failed_auth' => [
140
                '$gte' => $this->recaptcha_threshold,
141
            ],
142
        ]);
143
144
        if ($result !== null) {
145
            $this->logger->info('max failed auth requests for user ['.$username.'] exceeded recaptcha threshold ['.$this->recaptcha_threshold.']', [
146
                'category' => get_class($this),
147
            ]);
148
149
            if (!isset($_GET['g-recaptcha-response'])) {
150
                throw new Exception\InvalidRecaptchaToken('valid recaptcha token `g-recaptcha-response` is required');
151
            }
152
153
            $resp = $this->recaptcha->verify($_GET['g-recaptcha-response'], $_SERVER['REMOTE_ADDR']);
154
155
            if ($resp->isSuccess()) {
156
                $this->logger->info('recaptcha token validation for ['.$username.'] succeeded', [
157
                    'category' => get_class($this),
158
                ]);
159
            } else {
160
                $this->logger->info('recaptcha token validation for ['.$username.'] failed', [
161
                    'category' => get_class($this),
162
                    'errors' => $resp->getErrorCodes(),
163
                ]);
164
165
                throw new Exception\InvalidRecaptchaToken('Valid recaptcha token `g-recaptcha-response` is required');
166
            }
167
        }
168
    }
169
}
170