Completed
Push — master ( dcbb39...8a36bf )
by Artem
02:39
created

AbstractOAuth2Authenticator::start()   B

Complexity

Conditions 3
Paths 3

Size

Total Lines 24
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 3

Importance

Changes 0
Metric Value
dl 0
loc 24
ccs 13
cts 13
cp 1
rs 8.9713
c 0
b 0
f 0
cc 3
eloc 13
nc 3
nop 2
crap 3
1
<?php
2
3
//----------------------------------------------------------------------
4
//
5
//  Copyright (C) 2017 Artem Rodygin
6
//
7
//  You should have received a copy of the MIT License along with
8
//  this file. If not, see <http://opensource.org/licenses/MIT>.
9
//
10
//----------------------------------------------------------------------
11
12
namespace Pignus\Authenticator;
13
14
use League\OAuth2\Client\Provider\AbstractProvider;
15
use League\OAuth2\Client\Provider\ResourceOwnerInterface;
16
use Symfony\Bundle\SecurityBundle\Security\FirewallMap;
17
use Symfony\Component\HttpFoundation\RedirectResponse;
18
use Symfony\Component\HttpFoundation\Request;
19
use Symfony\Component\HttpFoundation\Response;
20
use Symfony\Component\HttpFoundation\Session\SessionInterface;
21
use Symfony\Component\Routing\RouterInterface;
22
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
23
use Symfony\Component\Security\Core\Exception\AuthenticationException;
24
use Symfony\Component\Security\Core\Security;
25
use Symfony\Component\Security\Core\User\UserInterface;
26
use Symfony\Component\Security\Core\User\UserProviderInterface;
27
use Symfony\Component\Security\Guard\AbstractGuardAuthenticator;
28
29
/**
30
 * Authenticates user against OAuth2 server.
31
 */
32
abstract class AbstractOAuth2Authenticator extends AbstractGuardAuthenticator
33
{
34
    protected $router;
35
    protected $session;
36
    protected $firewalls;
37
38
    /**
39
     * Dependency Injection constructor.
40
     *
41
     * @param RouterInterface  $router
42
     * @param SessionInterface $session
43
     * @param FirewallMap      $firewalls
44
     */
45 15
    public function __construct(
46
        RouterInterface  $router,
47
        SessionInterface $session,
48
        FirewallMap      $firewalls)
49
    {
50 15
        $this->router    = $router;
51 15
        $this->session   = $session;
52 15
        $this->firewalls = $firewalls;
53 15
    }
54
55
    /**
56
     * {@inheritdoc}
57
     */
58 3
    public function start(Request $request, AuthenticationException $authException = null)
59
    {
60
        // Do not redirect the user if it's an AJAX request.
61 3
        if ($request->isXmlHttpRequest()) {
62 1
            return new Response('Authentication required.', Response::HTTP_UNAUTHORIZED);
63
        }
64
65 2
        $exception = $this->session->get(Security::AUTHENTICATION_ERROR);
66 2
        $this->session->remove(Security::AUTHENTICATION_ERROR);
67
68 2
        if ($exception !== null) {
69 1
            return new Response($exception->getMessage(), Response::HTTP_UNAUTHORIZED);
70
        }
71
72 1
        $firewall = $this->firewalls->getFirewallConfig($request)->getName();
73 1
        $statevar = $firewall . '@' . static::class;
74
75 1
        $provider = $this->getProvider($this->router, $firewall);
76 1
        $authUrl  = $provider->getAuthorizationUrl();
77
78 1
        $this->session->set($statevar, $provider->getState());
79
80 1
        return new RedirectResponse($authUrl);
81
    }
82
83
    /**
84
     * {@inheritdoc}
85
     */
86 5
    public function getCredentials(Request $request)
87
    {
88 5
        $code  = $request->query->get('code');
89 5
        $state = $request->query->get('state');
90
91 5
        if (!$code && !$state) {
92 1
            return null;
93
        }
94
95 4
        if (!$code || !$state) {
96 2
            throw new AuthenticationException('Bad credentials.');
97
        }
98
99 2
        $firewall = $this->firewalls->getFirewallConfig($request)->getName();
100 2
        $statevar = $firewall . '@' . static::class;
101
102 2
        if ($state !== $this->session->get($statevar)) {
103 1
            $this->session->remove($statevar);
104
105 1
            return null;
106
        }
107
108
        return [
109 1
            'firewall' => $firewall,
110 1
            'code'     => $code,
111
        ];
112
    }
113
114
    /**
115
     * {@inheritdoc}
116
     */
117 2
    public function getUser($credentials, UserProviderInterface $userProvider)
118
    {
119
        try {
120 2
            $provider = $this->getProvider($this->router, $credentials['firewall']);
121
122 2
            $token = $provider->getAccessToken('authorization_code', [
123 2
                'code' => $credentials['code'],
124
            ]);
125
126 2
            $owner = $provider->getResourceOwner($token);
127 2
            $user  = $this->getUserFromResourceOwner($owner);
128
129 2
            if ($user === null) {
130 1
                throw new AuthenticationException('Bad credentials.');
131
            }
132
133 1
            return $user;
134
        }
135 1
        catch (\Exception $e) {
136 1
            throw new AuthenticationException($e->getMessage());
137
        }
138
    }
139
140
    /**
141
     * {@inheritdoc}
142
     */
143 1
    public function checkCredentials($credentials, UserInterface $user)
144
    {
145 1
        return true;
146
    }
147
148
    /**
149
     * {@inheritdoc}
150
     */
151 2
    public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
152
    {
153 2
        $this->session->remove(Security::AUTHENTICATION_ERROR);
154
155
        // An URL the user was trying to reach before authentication.
156 2
        $targetPath = sprintf('_security.%s.target_path', $providerKey);
157 2
        $targetUrl  = $this->session->get($targetPath, '/');
158
159
        // Do not redirect the user if it's an AJAX request.
160 2
        return $request->isXmlHttpRequest()
161
            ? null
162 2
            : new RedirectResponse($targetUrl);
163
    }
164
165
    /**
166
     * {@inheritdoc}
167
     */
168 1
    public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
169
    {
170 1
        $this->session->set(Security::AUTHENTICATION_ERROR, $exception);
171
172 1
        return null;
173
    }
174
175
    /**
176
     * {@inheritdoc}
177
     */
178 1
    public function supportsRememberMe()
179
    {
180 1
        return false;
181
    }
182
183
    /**
184
     * Returns OAuth2 provider for the specified firewall.
185
     *
186
     * @param RouterInterface $router
187
     * @param string          $firewall
188
     *
189
     * @return AbstractProvider
190
     */
191
    abstract protected function getProvider(RouterInterface $router, string $firewall): AbstractProvider;
192
193
    /**
194
     * Returns user entity based on specified OAuth2 account.
195
     *
196
     * @param ResourceOwnerInterface $owner
197
     *
198
     * @return UserInterface|null
199
     */
200
    abstract protected function getUserFromResourceOwner(ResourceOwnerInterface $owner);
201
}
202