ScratchTokenCredentialProvider::__construct()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 2
dl 0
loc 4
rs 10
c 1
b 0
f 0
cc 1
nc 1
nop 2
1
<?php
2
/******************************************************************************
3
 * Wikipedia Account Creation Assistance tool                                 *
4
 *                                                                            *
5
 * All code in this file is released into the public domain by the ACC        *
6
 * Development Team. Please see team.json for a list of contributors.         *
7
 ******************************************************************************/
8
9
namespace Waca\Security\CredentialProviders;
10
11
use ParagonIE\ConstantTime\Base32;
12
use Waca\DataObjects\User;
13
use Waca\Exceptions\ApplicationLogicException;
14
use Waca\Exceptions\OptimisticLockFailedException;
15
use Waca\PdoDatabase;
16
use Waca\Security\EncryptionHelper;
17
use Waca\SessionAlert;
18
use Waca\SiteConfiguration;
19
use Waca\WebRequest;
20
21
class ScratchTokenCredentialProvider extends CredentialProviderBase
22
{
23
    /** @var EncryptionHelper */
24
    private $encryptionHelper;
25
    /** @var array the tokens generated in the last generation round. */
26
    private $generatedTokens;
27
28
    /**
29
     * ScratchTokenCredentialProvider constructor.
30
     *
31
     * @param PdoDatabase       $database
32
     * @param SiteConfiguration $configuration
33
     */
34
    public function __construct(PdoDatabase $database, SiteConfiguration $configuration)
35
    {
36
        parent::__construct($database, $configuration, 'scratch');
37
        $this->encryptionHelper = new EncryptionHelper($configuration);
38
    }
39
40
    /**
41
     * Validates a user-provided credential
42
     *
43
     * @param User   $user The user to test the authentication against
44
     * @param string $data The raw credential data to be validated
45
     *
46
     * @return bool
47
     * @throws ApplicationLogicException|OptimisticLockFailedException
48
     */
49
    public function authenticate(User $user, $data)
50
    {
51
        if (is_array($data)) {
0 ignored issues
show
introduced by
The condition is_array($data) is always false.
Loading history...
52
            return false;
53
        }
54
55
        $storedData = $this->getCredentialData($user->getId());
56
57
        if ($storedData === null) {
58
            throw new ApplicationLogicException('Credential data not found');
59
        }
60
61
        $scratchTokens = unserialize($this->encryptionHelper->decryptData($storedData->getData()));
62
63
        $usedToken = null;
64
        foreach ($scratchTokens as $scratchToken) {
65
            if (password_verify($data, $scratchToken)) {
66
                $usedToken = $scratchToken;
67
                SessionAlert::quick("Hey, it looks like you used a scratch token to log in. Would you like to change your multi-factor authentication configuration?", 'alert-warning');
68
                WebRequest::setPostLoginRedirect($this->getConfiguration()->getBaseUrl() . "/internal.php/multiFactor");
69
                break;
70
            }
71
        }
72
73
        if ($usedToken === null) {
74
            return false;
75
        }
76
77
        $scratchTokens = array_diff($scratchTokens, [$usedToken]);
78
79
        $storedData->setData($this->encryptionHelper->encryptData(serialize($scratchTokens)));
80
        $storedData->save();
81
82
        return true;
83
    }
84
85
    /**
86
     * @param User   $user   The user the credential belongs to
87
     * @param int    $factor The factor this credential provides
88
     * @param string $data   Unused.
89
     *
90
     * @throws OptimisticLockFailedException
91
     */
92
    public function setCredential(User $user, $factor, $data)
93
    {
94
        $plaintextScratch = array();
95
        $storedScratch = array();
96
        for ($i = 0; $i < 5; $i++) {
97
            $token = Base32::encodeUpper(openssl_random_pseudo_bytes(10));
98
            $plaintextScratch[] = $token;
99
100
            $storedScratch[] = password_hash(
101
                $token,
102
                PasswordCredentialProvider::PASSWORD_ALGO,
103
                array('cost' => PasswordCredentialProvider::PASSWORD_COST)
104
            );
105
        }
106
107
        $storedData = $this->getCredentialData($user->getId(), null);
108
109
        if ($storedData !== null) {
110
            $storedData->delete();
111
        }
112
113
        $storedData = $this->createNewCredential($user);
114
115
        $storedData->setData($this->encryptionHelper->encryptData(serialize($storedScratch)));
116
        $storedData->setFactor($factor);
117
        $storedData->setVersion(1);
118
        $storedData->setPriority(9);
119
120
        $storedData->save();
121
        $this->generatedTokens = $plaintextScratch;
122
    }
123
124
    /**
125
     * Gets the count of remaining valid tokens
126
     *
127
     * @param int $userId
128
     *
129
     * @return int
130
     */
131
    public function getRemaining($userId)
132
    {
133
        $storedData = $this->getCredentialData($userId);
134
135
        if ($storedData === null) {
136
            return 0;
137
        }
138
139
        $scratchTokens = unserialize($this->encryptionHelper->decryptData($storedData->getData()));
140
141
        return count($scratchTokens);
142
    }
143
144
    /**
145
     * @return array
146
     */
147
    public function getTokens()
148
    {
149
        if ($this->generatedTokens != null) {
150
            return $this->generatedTokens;
151
        }
152
153
        return array();
154
    }
155
}
156