Passed
Push — master ( 3e0ae5...66dbef )
by Melech
01:47 queued 20s
created

Authenticator::authenticate()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 18
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 9
nc 3
nop 1
dl 0
loc 18
rs 9.9666
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the Valkyrja Framework package.
7
 *
8
 * (c) Melech Mizrachi <[email protected]>
9
 *
10
 * For the full copyright and license information, please view the LICENSE
11
 * file that was distributed with this source code.
12
 */
13
14
namespace Valkyrja\Auth\Authenticator\Abstract;
15
16
use Override;
0 ignored issues
show
Bug introduced by
The type Override was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
17
use Valkyrja\Auth\Authenticator\Contract\Authenticator as Contract;
18
use Valkyrja\Auth\Data\Attempt\Contract\AuthenticationAttempt;
19
use Valkyrja\Auth\Data\AuthenticatedUsers as AuthenticatedUsersData;
20
use Valkyrja\Auth\Data\Contract\AuthenticatedUsers;
21
use Valkyrja\Auth\Data\Retrieval\RetrievalById;
22
use Valkyrja\Auth\Entity\Contract\User;
23
use Valkyrja\Auth\Hasher\Contract\PasswordHasher;
24
use Valkyrja\Auth\Store\Contract\Store;
25
use Valkyrja\Auth\Throwable\Exception\InvalidAuthenticationException;
26
27
/**
28
 * Abstract Class Authenticator.
29
 *
30
 * @author Melech Mizrachi
31
 *
32
 * @template U of User
33
 *
34
 * @implements Contract<U>
35
 */
36
abstract class Authenticator implements Contract
37
{
38
    /** @var User|null */
39
    protected User|null $current = null;
40
    /** @var User|null */
41
    protected User|null $impersonated = null;
42
43
    /**
44
     * @param Store<U>        $store  The store
45
     * @param class-string<U> $entity The user entity
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string<U> at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string<U>.
Loading history...
46
     */
47
    public function __construct(
48
        protected Store $store,
49
        protected PasswordHasher $hasher,
50
        protected string $entity,
51
        protected AuthenticatedUsers $authenticatedUsers = new AuthenticatedUsersData(),
52
    ) {
53
    }
54
55
    /**
56
     * @inheritDoc
57
     */
58
    #[Override]
59
    public function isAuthenticated(): bool
60
    {
61
        return $this->authenticatedUsers->hasCurrent()
62
            || $this->authenticatedUsers->isImpersonating();
63
    }
64
65
    /**
66
     * Get the current authenticated user if one exists.
67
     *
68
     * @return User|null
69
     */
70
    #[Override]
71
    public function getAuthenticated(): User|null
72
    {
73
        $id = $this->authenticatedUsers->getCurrent();
74
75
        if ($id === null) {
76
            return null;
77
        }
78
79
        return $this->current ??= $this->store->retrieve(
80
            retrieval: new RetrievalById($id),
81
            user: $this->entity
82
        );
83
    }
84
85
    /**
86
     * Get the current impersonated user if one exists.
87
     *
88
     * @return User|null
89
     */
90
    #[Override]
91
    public function getImpersonated(): User|null
92
    {
93
        $id = $this->authenticatedUsers->getImpersonated();
94
95
        if ($id === null) {
96
            return null;
97
        }
98
99
        return $this->impersonated ??= $this->store->retrieve(
100
            retrieval: new RetrievalById($id),
101
            user: $this->entity
102
        );
103
    }
104
105
    /**
106
     * @inheritDoc
107
     */
108
    #[Override]
109
    public function getAuthenticatedUsers(): AuthenticatedUsers
110
    {
111
        return $this->authenticatedUsers;
112
    }
113
114
    /**
115
     * @inheritDoc
116
     */
117
    #[Override]
118
    public function setAuthenticatedUsers(AuthenticatedUsers $authenticatedUsers): static
119
    {
120
        $this->authenticatedUsers = $authenticatedUsers;
121
122
        return $this;
123
    }
124
125
    /**
126
     * @inheritDoc
127
     */
128
    #[Override]
129
    public function authenticate(AuthenticationAttempt $attempt): User
130
    {
131
        $user = $this->store->retrieve($attempt->getRetrieval(), $this->entity);
132
133
        if ($user === null) {
134
            throw new InvalidAuthenticationException('User not found');
135
        }
136
137
        if (! $this->hasher->confirmPassword($attempt->getPassword(), $user->getPasswordValue())) {
138
            throw new InvalidAuthenticationException('Incorrect password');
139
        }
140
141
        $this->authenticatedUsers->setCurrent($user->getIdValue());
142
143
        $this->current = $user;
144
145
        return $user;
146
    }
147
148
    /**
149
     * @inheritDoc
150
     */
151
    #[Override]
152
    public function unauthenticate(int|string $id): static
153
    {
154
        $this->authenticatedUsers->remove($id);
155
156
        if ($this->current?->getIdValue() === $id) {
157
            $this->current = null;
158
        }
159
160
        if ($this->impersonated?->getIdValue() === $id) {
161
            $this->impersonated = null;
162
        }
163
164
        return $this;
165
    }
166
}
167