Failed Conditions
Push — master ( 9593ed...05e56c )
by Arnold
02:48 queued 11s
created

TokenConfirmation::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 3
c 1
b 0
f 0
dl 0
loc 6
ccs 4
cts 4
cp 1
rs 10
cc 1
nc 1
nop 2
crap 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Jasny\Auth\Confirmation;
6
7
use Jasny\Auth\Storage\TokenStorageInterface;
8
use Jasny\Auth\StorageInterface as Storage;
9
use Jasny\Auth\UserInterface as User;
10
use Jasny\Immutable;
11
use Psr\Log\LoggerInterface as Logger;
12
use Psr\Log\NullLogger;
13
14
/**
15
 * Generate random confirmation tokens.
16
 * The random token needs to be stored to the database.
17
 */
18
class TokenConfirmation implements ConfirmationInterface
19
{
20
    use Immutable\With;
21
22
    protected int $numberOfBytes;
23
24
    /** @phpstan-param \Closure&callable(string):string */
25
    protected \Closure $encode;
0 ignored issues
show
introduced by
PHPDoc tag @phpstan-param has invalid value (\Closure&callable(string):string): Unexpected token "*/", expected variable at offset 52
Loading history...
26
27
    protected TokenStorageInterface $storage;
28
    protected Logger $logger;
29
30
    protected string $subject;
31
32
    /**
33
     * Class constructor.
34
     * 
35
     * @param int           $numberOfBytes  Number of bytes of the random string.
36
     * @param callable|null $encode         Method to encode random string.
37
     */
38 8
    public function __construct(int $numberOfBytes = 16, ?callable $encode = null)
39
    {
40 8
        $this->numberOfBytes = $numberOfBytes;
41 8
        $this->encode = \Closure::fromCallable($encode ?? [__CLASS__, 'encode']);
42
43 8
        $this->logger = new NullLogger();
44 8
    }
45
46
    /**
47
     * @inheritDoc
48
     */
49 8
    public function withStorage(Storage $storage): self
0 ignored issues
show
introduced by
Return type (Jasny\Auth\Confirmation\TokenConfirmation) of method Jasny\Auth\Confirmation\TokenConfirmation::withStorage() should be covariant with return type (static(Jasny\Auth\Confirmation\ConfirmationInterface)) of method Jasny\Auth\Confirmation\ConfirmationInterface::withStorage()
Loading history...
50
    {
51 8
        if (!$storage instanceof TokenStorageInterface) {
52 1
            throw new \InvalidArgumentException("Storage object needs to implement " . TokenStorageInterface::class);
53
        }
54
55 8
        return $this->withProperty('storage', $storage);
56
    }
57
58
    /**
59
     * @inheritDoc
60
     */
61 8
    public function withLogger(Logger $logger)
62
    {
63 8
        return $this->withProperty('logger', $logger);
64
    }
65
66
    /**
67
     * @inheritDoc
68
     */
69 8
    public function withSubject(string $subject)
70
    {
71 8
        return $this->withProperty('subject', $subject);
72
    }
73
74
    /**
75
     * @inheritDoc
76
     */
77 2
    public function getToken(User $user, \DateTimeInterface $expire): string
78
    {
79 2
        if (!isset($this->storage)) {
80 1
            throw new \BadMethodCallException("Storage is not set");
81
        }
82
83 1
        $rawToken = random_bytes($this->numberOfBytes);
84 1
        $token = ($this->encode)($rawToken);
85
86 1
        $this->storage->saveToken($this->subject, $token, $user, $expire);
87
88 1
        return $token;
89
    }
90
91
    /**
92
     * @inheritDoc
93
     */
94 5
    public function from(string $token): User
95
    {
96 5
        if (!isset($this->storage)) {
97 1
            throw new \BadMethodCallException("Storage is not set");
98
        }
99
100 4
        $info = $this->storage->fetchToken($this->subject, $token);
101
102 4
        if ($info === null) {
103 1
            $this->logger->debug('Unknown confirmation token', ['token' => $token]);
104 1
            throw new InvalidTokenException("Token has been revoked");
105
        }
106
107 3
        ['uid' => $uid, 'expire' => $expire] = $info;
108
109 3
        if ($expire <= new \DateTime()) {
110 1
            $this->logger->debug('Expired confirmation token', ['token' => $token, 'uid' => $uid]);
111 1
            throw new InvalidTokenException("Token is expired");
112
        }
113
114 2
        $user = $this->storage->fetchUserById($uid);
115
116 2
        if ($user === null) {
117 1
            $this->logger->debug('Invalid confirmation token: user not available', ['token' => $token, 'uid' => $uid]);
118 1
            throw new InvalidTokenException("Token has been revoked");
119
        }
120
121 1
        return $user;
122
    }
123
124
    /**
125
     * Encode the raw token to an alphanumeric string.
126
     */
127 1
    protected static function encode(string $rawToken): string
128
    {
129 1
        return base_convert(bin2hex($rawToken), 16, 36);
130
    }
131
}
132