Issues (27)

src/Token/PdoTokenProvider.php (1 issue)

1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of Biurad opensource projects.
7
 *
8
 * PHP version 7.4 and above required
9
 *
10
 * @author    Divine Niiquaye Ibok <[email protected]>
11
 * @copyright 2019 Biurad Group (https://biurad.com/)
12
 * @license   https://opensource.org/licenses/BSD-3-Clause License
13
 *
14
 * For the full copyright and license information, please view the LICENSE
15
 * file that was distributed with this source code.
16
 *
17
 */
18
19
namespace Biurad\Security\Token;
20
21
use Symfony\Component\Security\Core\Authentication\RememberMe\PersistentToken;
22
use Symfony\Component\Security\Core\Authentication\RememberMe\PersistentTokenInterface;
23
use Symfony\Component\Security\Core\Authentication\RememberMe\TokenProviderInterface;
24
use Symfony\Component\Security\Core\Authentication\RememberMe\TokenVerifierInterface;
25
use Symfony\Component\Security\Core\Exception\TokenNotFoundException;
26
27
/**
28
 * Token provider for persistent login tokens.
29
 *
30
 * @author Divine Niiquaye Ibok <[email protected]>
31
 */
32
class PdoTokenProvider implements TokenProviderInterface, TokenVerifierInterface
33
{
34
    private \PDO $connection;
35
36
    /**
37
     * @param string|\PDO $connection A PDO instance or a DSN string
38
     */
39
    public function __construct($connection)
40
    {
41
        if (!$connection instanceof \PDO) {
42
            $connection = $this->createPdoFromDsm($connection);
43
        }
44
45
        // If table does not exist, create it
46
        $connection->exec('CREATE TABLE IF NOT EXISTS rememberme_token (
47
            series VARCHAR(88)      UNIQUE PRIMARY KEY NOT NULL,
48
            value  VARCHAR(88)      NOT NULL,
49
            last_used DATETIME      NOT NULL,
50
            class   VARCHAR(100)    NOT NULL,
51
            identifier VARCHAR(200) NOT NULL
52
        )');
53
        $this->connection = $connection;
54
    }
55
56
    /**
57
     * {@inheritdoc}
58
     */
59
    public function loadTokenBySeries(string $series): PersistentTokenInterface
60
    {
61
        $stmt = $this->connection->prepare('SELECT class, identifier, value, last_used FROM rememberme_token WHERE series = ?');
62
        $stmt->execute([$series]);
63
64
        if ($stmt->rowCount() > 0) {
65
            $row = $stmt->fetch(\PDO::FETCH_ASSOC);
66
67
            return new PersistentToken($row['class'], $row['identifier'], $series, $row['value'], new \DateTime($row['last_used']));
68
        }
69
70
        throw new TokenNotFoundException('No token found.');
71
    }
72
73
    /**
74
     * {@inheritdoc}
75
     */
76
    public function deleteTokenBySeries(string $series): void
77
    {
78
        $stmt = $this->connection->prepare('DELETE FROM rememberme_token WHERE series = ?');
79
        $stmt->execute([$series]);
80
    }
81
82
    /**
83
     * {@inheritdoc}
84
     */
85
    public function updateToken(string $series, string $tokenValue, \DateTime $lastUsed): void
86
    {
87
        $stmt = $this->connection->prepare('UPDATE rememberme_token SET value = ?, last_used = ? WHERE series = ?');
88
        $updated = $stmt->execute([$tokenValue, $lastUsed->format('Y-m-d H:i:s'), $series]);
89
90
        if (!$updated) {
91
            throw new TokenNotFoundException('No token found.');
92
        }
93
    }
94
95
    /**
96
     * {@inheritdoc}
97
     */
98
    public function createNewToken(PersistentTokenInterface $token): void
99
    {
100
        $stmt = $this->connection->prepare('INSERT INTO rememberme_token (series, value, last_used, class, identifier) VALUES (?, ?, ?, ?, ?)');
101
        $stmt->execute([$token->getSeries(), $token->getTokenValue(), $token->getLastUsed()->format('Y-m-d H:i:s'), $token->getClass(), $token->getUserIdentifier()]);
102
    }
103
104
    /**
105
     * {@inheritdoc}
106
     */
107
    public function verifyToken(PersistentTokenInterface $token, string $tokenValue): bool
108
    {
109
        if (\hash_equals($token->getTokenValue(), $tokenValue)) {
110
            return true;
111
        }
112
113
        try {
114
            $pToken = $this->loadTokenBySeries($token->getSeries());
115
        } catch (TokenNotFoundException $e) {
116
            return false;
117
        }
118
119
        if ($pToken->getLastUsed()->getTimestamp() + 60 < \time()) {
120
            return false;
121
        }
122
123
        return \hash_equals($pToken->getTokenValue(), $tokenValue);
124
    }
125
126
    /**
127
     * {@inheritdoc}
128
     *
129
     * @throws \Exception
130
     */
131
    public function updateExistingToken(PersistentTokenInterface $token, string $tokenValue, \DateTimeInterface $lastUsed): void
132
    {
133
        $this->connection->beginTransaction();
134
135
        try {
136
            $this->deleteTokenBySeries($token->getSeries());
137
            $this->createNewToken(new PersistentToken($token->getClass(), $token->getUserIdentifier(), $token->getSeries(), $token->getTokenValue(), $lastUsed));
138
            $this->connection->commit();
139
        } catch (\Exception $e) {
140
            $this->connection->rollBack();
141
142
            throw $e;
143
        }
144
    }
145
146
    protected function createPdoFromDsm(string $connection): \PDO
147
    {
148
        if (!\is_array($connectionData = \parse_url($connection))) {
0 ignored issues
show
The condition is_array($connectionData = parse_url($connection)) is always true.
Loading history...
149
            throw new \InvalidArgumentException(\sprintf('Invalid connection string "%s".', $connection));
150
        }
151
152
        $dsn = \sprintf('%s:host=%s;dbname=%s', $connectionData['scheme'], $connectionData['host'], $connectionData['path']);
153
154
        if (isset($connectionData['port'])) {
155
            $dsn .= \sprintf(';port=%d', (int) $connectionData['port']);
156
        }
157
158
        if ('null' === $password = ($connectionData['pass'] ?? null)) {
159
            $password = null;
160
        }
161
162
        return new \PDO($dsn, $connectionData['user'] ?? null, $password);
163
    }
164
}
165