Completed
Push — master ( 56f862...ea15ab )
by Dominik
03:39
created

FormAuthentication::getCriteriaAsSting()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 9
rs 9.6666
c 0
b 0
f 0
cc 2
eloc 5
nc 2
nop 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Chubbyphp\Security\Authentication;
6
7
use Chubbyphp\Model\RepositoryInterface;
8
use Chubbyphp\Security\Authentication\Exception\AuthenticationExceptionInterface;
9
use Chubbyphp\Security\Authentication\Exception\InvalidPasswordException;
10
use Chubbyphp\Security\Authentication\Exception\MissingRequirementException;
11
use Chubbyphp\Security\Authentication\Exception\UserNotFoundException;
12
use Chubbyphp\Session\SessionInterface;
13
use Psr\Http\Message\ServerRequestInterface as Request;
14
use Psr\Log\LoggerInterface;
15
use Psr\Log\NullLogger;
16
17
final class FormAuthentication implements AuthenticationInterface
18
{
19
    /**
20
     * @var PasswordManagerInterface
21
     */
22
    private $passwordManager;
23
24
    /**
25
     * @var SessionInterface
26
     */
27
    private $session;
28
29
    const USER_KEY = 'u';
30
31
    /**
32
     * @var RepositoryInterface
33
     */
34
    private $userRepository;
35
36
    /**
37
     * @var LoggerInterface
38
     */
39
    private $logger;
40
41
    /**
42
     * @param PasswordManagerInterface $passwordManager
43
     * @param SessionInterface         $session
44
     * @param RepositoryInterface      $userRepository
45
     */
46
    public function __construct(
47
        PasswordManagerInterface $passwordManager,
48
        SessionInterface $session,
49
        RepositoryInterface $userRepository,
50
        LoggerInterface $logger = null
51
    ) {
52
        $this->passwordManager = $passwordManager;
53
        $this->session = $session;
54
        $this->userRepository = $userRepository;
55
        $this->logger = $logger ?? new NullLogger();
56
    }
57
58
    /**
59
     * @param Request $request
60
     *
61
     * @throws AuthenticationExceptionInterface
62
     */
63
    public function login(Request $request)
64
    {
65
        $data = $request->getParsedBody();
66
        $this->checkingRequirements($data);
67
68
        /** @var UserPasswordInterface $user */
69 View Code Duplication
        if (null === $user = $this->userRepository->findOneBy(['username' => $data['username']])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
70
            $this->logger->warning(
71
                'security.authentication.form: user not found with criteria {criteria}',
72
                ['criteria' => $this->getCriteriaAsSting(['username' => $data['username']])]
73
            );
74
75
            throw UserNotFoundException::create(['username' => $data['username']]);
76
        }
77
78 View Code Duplication
        if (!$this->passwordManager->verify($data['password'], $user->getPassword())) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
79
            $this->logger->warning(
80
                'security.authentication.form: invalid password for user with criteria {criteria}',
81
                ['criteria' => $this->getCriteriaAsSting(['username' => $data['username']])]
82
            );
83
84
            throw InvalidPasswordException::create(['username' => $data['username']]);
85
        }
86
87
        $this->logger->info(
88
            'security.authentication.form: login successful for user with id {id}', ['id' => $user->getId()]
89
        );
90
91
        $this->session->set($request, self::USER_KEY, $user->getId());
92
    }
93
94
    /**
95
     * @param null|array|object $data
96
     */
97
    private function checkingRequirements($data)
98
    {
99
        $fields = [];
100
        if (!isset($data['username'])) {
101
            $fields[] = 'username';
102
        }
103
104
        if (!isset($data['password'])) {
105
            $fields[] = 'password';
106
        }
107
108
        if ([] === $fields) {
109
            return;
110
        }
111
112
        $this->logger->warning(
113
            'security.authentication.form: missing required fields {fields}', ['fields' => implode(', ', $fields)]
114
        );
115
116
        throw MissingRequirementException::create($fields);
117
    }
118
119
    /**
120
     * @param Request $request
121
     */
122
    public function logout(Request $request)
123
    {
124
        if (!$this->checkForUserIdWithinSession($request)) {
125
            return;
126
        }
127
128
        $id = $this->getUserIdFromSession($request);
129
130
        $this->logger->info(
131
            'security.authentication.form: logout user with id {id}', ['id' => $id]
132
        );
133
134
        $this->session->remove($request, self::USER_KEY);
135
    }
136
137
    /**
138
     * @param Request $request
139
     *
140
     * @return bool
141
     */
142
    public function isAuthenticated(Request $request): bool
143
    {
144
        return null !== $this->getAuthenticatedUser($request);
145
    }
146
147
    /**
148
     * @param Request $request
149
     *
150
     * @return UserPasswordInterface|null
151
     */
152
    public function getAuthenticatedUser(Request $request)
153
    {
154
        if (!$this->checkForUserIdWithinSession($request)) {
155
            $this->logger->info('security.authentication.form: not authenticated');
156
157
            return null;
158
        }
159
160
        $id = $this->getUserIdFromSession($request);
161
162
        $user = $this->userRepository->find($id);
163
164
        if (null === $user) {
165
            $this->logger->warning('security.authentication.form: user with id {id} is not resolvable', ['id' => $id]);
166
            $this->session->remove($request, self::USER_KEY);
167
168
            return null;
169
        }
170
171
        $this->logger->info('security.authentication.form: authenticated user with id {id}', ['id' => $id]);
172
173
        return $user;
174
    }
175
176
    /**
177
     * @param Request $request
178
     *
179
     * @return bool
180
     */
181
    private function checkForUserIdWithinSession(Request $request): bool
182
    {
183
        return $this->session->has($request, self::USER_KEY);
184
    }
185
186
    /**
187
     * @param Request $request
188
     *
189
     * @return string
190
     */
191
    private function getUserIdFromSession(Request $request): string
192
    {
193
        return $this->session->get($request, self::USER_KEY);
194
    }
195
196
    /**
197
     * @param array $criteria
198
     *
199
     * @return string
200
     */
201
    private function getCriteriaAsSting(array $criteria): string
202
    {
203
        $criteriaString = '';
204
        foreach ($criteria as $key => $value) {
205
            $criteriaString .= $key.': '.$value.', ';
206
        }
207
208
        return substr($criteriaString, 0, -2);
209
    }
210
}
211