Passed
Push — master ( 007c76...c983ad )
by Dominik
05:09
created

FormAuthentication::logout()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 16
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 16
ccs 9
cts 9
cp 1
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 8
nc 2
nop 1
crap 2
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 13
    public function __construct(
47
        PasswordManagerInterface $passwordManager,
48
        SessionInterface $session,
49
        RepositoryInterface $userRepository,
50
        LoggerInterface $logger = null
51
    ) {
52 13
        $this->passwordManager = $passwordManager;
53 13
        $this->session = $session;
54 13
        $this->userRepository = $userRepository;
55 13
        $this->logger = $logger ?? new NullLogger();
56 13
    }
57
58
    /**
59
     * @param Request $request
60
     *
61
     * @throws AuthenticationExceptionInterface
62
     */
63 6
    public function login(Request $request)
64
    {
65 6
        $data = $request->getParsedBody();
66 6
        $this->checkingRequirements($data);
67
68
        /** @var UserPasswordInterface $user */
69 3 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 1
            $this->logger->warning(
71 1
                'security.authentication.form: user not found with criteria {criteria}',
72 1
                ['criteria' => $this->getCriteriaAsSting(['username' => $data['username']])]
73
            );
74
75 1
            throw UserNotFoundException::create(['username' => $data['username']]);
76
        }
77
78 2 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...
Bug introduced by
The method getPassword() does not seem to exist on object<Chubbyphp\Model\ModelInterface>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

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