Test Failed
Push — dev ( ccede5...b27119 )
by Herberto
13:46
created

getUserEntityByUserCredentials()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 19
rs 9.6333
c 0
b 0
f 0
cc 3
nc 3
nop 4
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the Explicit Architecture POC,
7
 * which is created on top of the Symfony Demo application.
8
 *
9
 * (c) Herberto Graça <[email protected]>
10
 *
11
 * For the full copyright and license information, please view the LICENSE
12
 * file that was distributed with this source code.
13
 */
14
15
namespace Acme\App\Infrastructure\Auth\Authentication;
16
17
use Acme\App\Core\Component\User\Application\Repository\UserRepositoryInterface;
18
use Acme\App\Core\Component\User\Domain\User\User;
19
use Acme\App\Core\Port\Auth\AuthenticationException;
20
use Acme\App\Core\Port\Auth\AuthenticationServiceInterface;
21
use Acme\App\Core\Port\Auth\NoUserAuthenticatedException;
22
use Acme\App\Core\SharedKernel\Component\User\Domain\User\UserId;
23
use League\OAuth2\Server\Repositories\UserRepositoryInterface as OauthUserRepositoryInterface;
24
use Psr\Http\Message\ServerRequestInterface;
25
use Symfony\Bridge\PsrHttpMessage\HttpFoundationFactoryInterface;
26
use Symfony\Component\HttpFoundation\Session\SessionInterface;
27
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
28
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
29
use Symfony\Component\Security\Core\Exception\AuthenticationException as SymfonyAuthenticationException;
30
use Symfony\Component\Security\Core\Security;
31
use Symfony\Component\Security\Csrf\CsrfToken;
32
use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
33
34
final class AuthenticationService implements AuthenticationServiceInterface, OauthUserRepositoryInterface
35
{
36
    /**
37
     * @var CsrfTokenManagerInterface
38
     */
39
    private $csrfTokenManager;
40
41
    /**
42
     * @var TokenStorageInterface
43
     */
44
    private $tokenStorage;
45
46
    /**
47
     * @var HttpFoundationFactoryInterface
48
     */
49
    private $symfonyRequestFactory;
50
51
    /**
52
     * @var SessionInterface
53
     */
54
    private $session;
55
56
    /**
57
     * @var UserRepositoryInterface
58
     */
59
    private $userRepository;
60
61
    /**
62
     * @var UserPasswordEncoderInterface
63
     */
64
    private $userPasswordEncoder;
65
66
    public function __construct(
67
        CsrfTokenManagerInterface $csrfTokenManager,
68
        TokenStorageInterface $tokenStorage,
69
        HttpFoundationFactoryInterface $symfonyRequestFactory,
70
        SessionInterface $session,
71
        UserRepositoryInterface $userRepository,
72
        UserPasswordEncoderInterface $userPasswordEncoder
73
    ) {
74
        $this->csrfTokenManager = $csrfTokenManager;
75
        $this->tokenStorage = $tokenStorage;
76
        $this->symfonyRequestFactory = $symfonyRequestFactory;
77
        $this->session = $session;
78
        $this->userRepository = $userRepository;
79
        $this->userPasswordEncoder = $userPasswordEncoder;
80
    }
81
82
    public function isCsrfTokenValid(string $id, string $token): bool
83
    {
84
        return $this->csrfTokenManager->isTokenValid(new CsrfToken($id, $token));
85
    }
86
87
    public function getLoggedInUserId(): UserId
88
    {
89
        return $this->getSecurityUser()->getUserId();
90
    }
91
92
    public function getLoggedInUser(): User
93
    {
94
        return $this->userRepository->findOneById($this->getLoggedInUserId());
95
    }
96
97
    public function getLastAuthenticationError(
98
        ServerRequestInterface $request,
99
        bool $clearSession = true
100
    ): ?AuthenticationException {
101
        $request = $this->symfonyRequestFactory->createRequest($request);
102
        $session = $this->session;
103
        /** @var SymfonyAuthenticationException|null $symfonyAuthenticationException */
104
        $symfonyAuthenticationException = null;
105
106
        if ($request->attributes->has(Security::AUTHENTICATION_ERROR)) {
107
            $symfonyAuthenticationException = $request->attributes->get(Security::AUTHENTICATION_ERROR);
108
        } elseif ($session !== null && $session->has(Security::AUTHENTICATION_ERROR)) {
109
            $symfonyAuthenticationException = $session->get(Security::AUTHENTICATION_ERROR);
110
111
            if ($clearSession) {
112
                $session->remove(Security::AUTHENTICATION_ERROR);
113
            }
114
        }
115
116
        return $symfonyAuthenticationException
117
            ? new AuthenticationException(
118
                $symfonyAuthenticationException->getMessage(),
119
                $symfonyAuthenticationException->getCode(),
120
                $symfonyAuthenticationException,
121
                $symfonyAuthenticationException->getMessageKey(),
122
                $symfonyAuthenticationException->getMessageData()
123
            )
124
            : null;
125
    }
126
127
    public function getLastAuthenticationUsername(ServerRequestInterface $request): string
128
    {
129
        $request = $this->symfonyRequestFactory->createRequest($request);
130
        if ($request->attributes->has(Security::LAST_USERNAME)) {
131
            return $request->attributes->get(Security::LAST_USERNAME, '');
132
        }
133
134
        return $this->session === null ? '' : $this->session->get(Security::LAST_USERNAME, '');
135
    }
136
137
    /**
138
     * @param string $username
139
     * @param string $password
140
     * @param string $grantType
141
     */
142
    public function getUserEntityByUserCredentials(
143
        $username,
144
        $password,
145
        $grantType,
146
        ClientEntityInterface $clientEntity
147
    ): ?UserEntityInterface {
148
        $user = $this->userRepository->findOneByEmail($username);
149
        if ($user === null) {
150
            return null;
151
        }
152
153
        $securityUser = SecurityUser::fromUser($user);
154
155
        if (!$this->userPasswordEncoder->isPasswordValid($securityUser, $password)) {
156
            return null;
157
        }
158
159
        return $securityUser;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $securityUser; (self) is incompatible with the return type declared by the interface League\OAuth2\Server\Rep...EntityByUserCredentials of type League\OAuth2\Server\Entities\UserEntityInterface.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
160
    }
161
162
    private function getSecurityUser(): SecurityUser
163
    {
164
        $token = $this->tokenStorage->getToken();
165
166
        if ($token === null) {
167
            throw new NoUserAuthenticatedException();
168
        }
169
170
        $securityUser = $token->getUser();
171
        if (!$securityUser instanceof SecurityUser) {
172
            throw new NoUserAuthenticatedException();
173
        }
174
175
        return $securityUser;
176
    }
177
}
178