Issues (186)

CredentialProviders/TotpCredentialProvider.php (1 issue)

Severity
1
<?php
2
/******************************************************************************
3
 * Wikipedia Account Creation Assistance tool                                 *
4
 * ACC Development Team. Please see team.json for a list of contributors.     *
5
 *                                                                            *
6
 * This is free and unencumbered software released into the public domain.    *
7
 * Please see LICENSE.md for the full licencing statement.                    *
8
 ******************************************************************************/
9
10
namespace Waca\Security\CredentialProviders;
11
12
use DateTimeImmutable;
13
use OTPHP\Factory;
14
use OTPHP\TOTP;
15
use Waca\DataObjects\User;
16
use Waca\Exceptions\ApplicationLogicException;
17
use Waca\PdoDatabase;
18
use Waca\Security\EncryptionHelper;
19
use Waca\SiteConfiguration;
20
21
class TotpCredentialProvider extends CredentialProviderBase
22
{
23
    /** @var EncryptionHelper */
24
    private $encryptionHelper;
25
26
    /**
27
     * TotpCredentialProvider constructor.
28
     *
29
     * @param PdoDatabase       $database
30
     * @param SiteConfiguration $configuration
31
     */
32
    public function __construct(PdoDatabase $database, SiteConfiguration $configuration)
33
    {
34
        parent::__construct($database, $configuration, 'totp');
35
        $this->encryptionHelper = new EncryptionHelper($configuration);
36
    }
37
38
    /**
39
     * Validates a user-provided credential
40
     *
41
     * @param User   $user The user to test the authentication against
42
     * @param string $data The raw credential data to be validated
43
     *
44
     * @return bool
45
     * @throws ApplicationLogicException
46
     */
47
    public function authenticate(User $user, $data)
48
    {
49
        if (is_array($data)) {
0 ignored issues
show
The condition is_array($data) is always false.
Loading history...
50
            return false;
51
        }
52
53
        $storedData = $this->getCredentialData($user->getId());
54
55
        if ($storedData === null) {
56
            throw new ApplicationLogicException('Credential data not found');
57
        }
58
59
        $provisioningUrl = $this->encryptionHelper->decryptData($storedData->getData());
60
        $totp = Factory::loadFromProvisioningUri($provisioningUrl);
61
62
        return $totp->verify($data, null, 2);
63
    }
64
65
    public function verifyEnable(User $user, $data)
66
    {
67
        $storedData = $this->getCredentialData($user->getId(), true);
68
69
        if ($storedData === null) {
70
            throw new ApplicationLogicException('Credential data not found');
71
        }
72
73
        $provisioningUrl = $this->encryptionHelper->decryptData($storedData->getData());
74
        $totp = Factory::loadFromProvisioningUri($provisioningUrl);
75
76
        $result = $totp->verify($data, null, 2);
77
78
        if ($result && $storedData->getTimeout() > new DateTimeImmutable()) {
79
            $storedData->setDisabled(0);
80
            $storedData->setPriority(5);
81
            $storedData->setTimeout(null);
82
            $storedData->save();
83
        }
84
85
        return $result;
86
    }
87
88
    /**
89
     * @param User   $user   The user the credential belongs to
90
     * @param int    $factor The factor this credential provides
91
     * @param string $data   Unused here, due to there being no user-provided data. We provide the user with the secret.
92
     */
93
    public function setCredential(User $user, $factor, $data)
94
    {
95
        $issuer = 'ACC - ' . $this->getConfiguration()->getIrcNotificationsInstance();
96
        $totp = TOTP::create();
97
        $totp->setLabel($user->getUsername());
98
        $totp->setIssuer($issuer);
99
100
        $storedData = $this->getCredentialData($user->getId(), null);
101
102
        if ($storedData !== null) {
103
            $storedData->delete();
104
        }
105
106
        $storedData = $this->createNewCredential($user);
107
108
        $storedData->setData($this->encryptionHelper->encryptData($totp->getProvisioningUri()));
109
        $storedData->setFactor($factor);
110
        $storedData->setTimeout(new DateTimeImmutable('+ 1 hour'));
111
        $storedData->setDisabled(1);
112
        $storedData->setVersion(1);
113
114
        $storedData->save();
115
    }
116
117
    public function getProvisioningUrl(User $user)
118
    {
119
        $storedData = $this->getCredentialData($user->getId(), true);
120
121
        if ($storedData->getTimeout() < new DateTimeImmutable()) {
122
            $storedData->delete();
123
            $storedData = null;
124
        }
125
126
        if ($storedData === null) {
127
            throw new ApplicationLogicException('Credential data not found');
128
        }
129
130
        return $this->encryptionHelper->decryptData($storedData->getData());
131
    }
132
133
    public function isPartiallyEnrolled(User $user)
134
    {
135
        $storedData = $this->getCredentialData($user->getId(), true);
136
137
        if ($storedData->getTimeout() < new DateTimeImmutable()) {
138
            $storedData->delete();
139
140
            return false;
141
        }
142
143
        if ($storedData === null) {
144
            return false;
145
        }
146
147
        return true;
148
    }
149
150
    public function getSecret(User $user)
151
    {
152
        $totp = Factory::loadFromProvisioningUri($this->getProvisioningUrl($user));
153
154
        return $totp->getSecret();
155
    }
156
}
157