Test Setup Failed
Push — oauth2 ( 63c070 )
by Herberto
11:43 queued 07:12
created

getUserEntityByUserCredentials()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 20
rs 9.6
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\Authentication\AuthenticationException;
20
use Acme\App\Core\Port\Auth\Authentication\AuthenticationServiceInterface;
21
use Acme\App\Core\Port\Auth\Authentication\NoUserAuthenticatedException;
22
use Acme\App\Core\Port\Persistence\Exception\EmptyQueryResultException;
23
use Acme\App\Core\SharedKernel\Component\User\Domain\User\UserId;
24
use League\OAuth2\Server\Entities\ClientEntityInterface;
25
use League\OAuth2\Server\Entities\UserEntityInterface;
26
use League\OAuth2\Server\Repositories\UserRepositoryInterface as OauthUserRepositoryInterface;
27
use Psr\Http\Message\ServerRequestInterface;
28
use Symfony\Bridge\PsrHttpMessage\HttpFoundationFactoryInterface;
29
use Symfony\Component\HttpFoundation\Session\SessionInterface;
30
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
31
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
32
use Symfony\Component\Security\Core\Exception\AuthenticationException as SymfonyAuthenticationException;
33
use Symfony\Component\Security\Core\Security;
34
use Symfony\Component\Security\Csrf\CsrfToken;
35
use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
36
37
final class AuthenticationService implements AuthenticationServiceInterface, OauthUserRepositoryInterface
38
{
39
    /**
40
     * @var CsrfTokenManagerInterface
41
     */
42
    private $csrfTokenManager;
43
44
    /**
45
     * @var TokenStorageInterface
46
     */
47
    private $tokenStorage;
48
49
    /**
50
     * @var HttpFoundationFactoryInterface
51
     */
52
    private $symfonyRequestFactory;
53
54
    /**
55
     * @var SessionInterface
56
     */
57
    private $session;
58
59
    /**
60
     * @var UserRepositoryInterface
61
     */
62
    private $userRepository;
63
64
    /**
65
     * @var UserPasswordEncoderInterface
66
     */
67
    private $userPasswordEncoder;
68
69
    public function __construct(
70
        CsrfTokenManagerInterface $csrfTokenManager,
71
        TokenStorageInterface $tokenStorage,
72
        HttpFoundationFactoryInterface $symfonyRequestFactory,
73
        SessionInterface $session,
74
        UserRepositoryInterface $userRepository,
75
        UserPasswordEncoderInterface $userPasswordEncoder
76
    ) {
77
        $this->csrfTokenManager = $csrfTokenManager;
78
        $this->tokenStorage = $tokenStorage;
79
        $this->symfonyRequestFactory = $symfonyRequestFactory;
80
        $this->session = $session;
81
        $this->userRepository = $userRepository;
82
        $this->userPasswordEncoder = $userPasswordEncoder;
83
    }
84
85
    public function isCsrfTokenValid(string $id, string $token): bool
86
    {
87
        return $this->csrfTokenManager->isTokenValid(new CsrfToken($id, $token));
88
    }
89
90
    public function getLoggedInUserId(): UserId
91
    {
92
        return $this->getSecurityUser()->getUserId();
93
    }
94
95
    public function getLoggedInUser(): User
96
    {
97
        return $this->userRepository->findOneById($this->getLoggedInUserId());
98
    }
99
100
    public function getLastAuthenticationError(
101
        ServerRequestInterface $request,
102
        bool $clearSession = true
103
    ): ?AuthenticationException {
104
        $request = $this->symfonyRequestFactory->createRequest($request);
105
        $session = $this->session;
106
        /** @var SymfonyAuthenticationException|null $symfonyAuthenticationException */
107
        $symfonyAuthenticationException = null;
108
109
        if ($request->attributes->has(Security::AUTHENTICATION_ERROR)) {
110
            $symfonyAuthenticationException = $request->attributes->get(Security::AUTHENTICATION_ERROR);
111
        } elseif ($session !== null && $session->has(Security::AUTHENTICATION_ERROR)) {
112
            $symfonyAuthenticationException = $session->get(Security::AUTHENTICATION_ERROR);
113
114
            if ($clearSession) {
115
                $session->remove(Security::AUTHENTICATION_ERROR);
116
            }
117
        }
118
119
        return $symfonyAuthenticationException
120
            ? new AuthenticationException(
121
                $symfonyAuthenticationException->getMessage(),
122
                $symfonyAuthenticationException->getCode(),
123
                $symfonyAuthenticationException,
124
                $symfonyAuthenticationException->getMessageKey(),
125
                $symfonyAuthenticationException->getMessageData()
126
            )
127
            : null;
128
    }
129
130
    public function getLastAuthenticationUsername(ServerRequestInterface $request): string
131
    {
132
        $request = $this->symfonyRequestFactory->createRequest($request);
133
        if ($request->attributes->has(Security::LAST_USERNAME)) {
134
            return $request->attributes->get(Security::LAST_USERNAME, '');
135
        }
136
137
        return $this->session === null ? '' : $this->session->get(Security::LAST_USERNAME, '');
138
    }
139
140
    /**
141
     * @param string $username
142
     * @param string $password
143
     * @param string $grantType
144
     */
145
    public function getUserEntityByUserCredentials(
146
        $username,
147
        $password,
148
        $grantType,
149
        ClientEntityInterface $clientEntity
150
    ): ?UserEntityInterface {
151
        try {
152
            $user = $this->userRepository->findOneByUsername($username);
153
        } catch (EmptyQueryResultException$e) {
154
            return null;
155
        }
156
157
        $securityUser = SecurityUser::fromUser($user);
158
159
        if (!$this->userPasswordEncoder->isPasswordValid($securityUser, $password)) {
160
            return null;
161
        }
162
163
        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...
164
    }
165
166
    private function getSecurityUser(): SecurityUser
167
    {
168
        $token = $this->tokenStorage->getToken();
169
170
        if ($token === null) {
171
            throw new NoUserAuthenticatedException();
172
        }
173
174
        $securityUser = $token->getUser();
175
        if (!$securityUser instanceof SecurityUser) {
176
            throw new NoUserAuthenticatedException();
177
        }
178
179
        return $securityUser;
180
    }
181
}
182