Passed
Push — master ( f2694b...92ebee )
by Pol
12:01
created

CasGuardAuthenticator   A

Complexity

Total Complexity 18

Size/Duplication

Total Lines 166
Duplicated Lines 0 %

Test Coverage

Coverage 86.67%

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 57
dl 0
loc 166
ccs 52
cts 60
cp 0.8667
rs 10
c 2
b 0
f 0
wmc 18

10 Methods

Rating   Name   Duplication   Size   Complexity  
A start() 0 20 3
A onAuthenticationFailure() 0 18 2
A getCredentials() 0 11 2
A __construct() 0 6 1
A checkCredentials() 0 15 3
A onAuthenticationSuccess() 0 7 1
A supports() 0 6 1
A toPsr() 0 7 1
A getUser() 0 13 3
A supportsRememberMe() 0 3 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace EcPhp\CasBundle\Security;
6
7
use EcPhp\CasBundle\Security\Core\User\CasUserProviderInterface;
8
use EcPhp\CasLib\CasInterface;
9
use EcPhp\CasLib\Introspection\Contract\ServiceValidate;
10
use EcPhp\CasLib\Utils\Uri;
11
use InvalidArgumentException;
12
use Psr\Http\Message\ResponseInterface;
13
use Psr\Http\Message\ServerRequestInterface;
14
use Symfony\Bridge\PsrHttpMessage\HttpMessageFactoryInterface;
15
use Symfony\Component\HttpFoundation\JsonResponse;
16
use Symfony\Component\HttpFoundation\RedirectResponse;
17
use Symfony\Component\HttpFoundation\Request;
18
use Symfony\Component\HttpFoundation\Response;
19
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
20
use Symfony\Component\Security\Core\Exception\AuthenticationException;
21
use Symfony\Component\Security\Core\User\UserInterface;
22
use Symfony\Component\Security\Core\User\UserProviderInterface;
23
use Symfony\Component\Security\Guard\AbstractGuardAuthenticator;
24
25
final class CasGuardAuthenticator extends AbstractGuardAuthenticator
26
{
27
    private CasInterface $cas;
28
29
    private HttpMessageFactoryInterface $httpMessageFactory;
30
31
    public function __construct(
32
        CasInterface $cas,
33
        HttpMessageFactoryInterface $httpMessageFactory
34
    ) {
35
        $this->cas = $cas;
36
        $this->httpMessageFactory = $httpMessageFactory;
37
    }
38
39
    /**
40 11
     * {@inheritdoc}
41
     */
42
    public function checkCredentials($credentials, UserInterface $user): bool
43
    {
44 11
        try {
45 11
            $introspect = $this->cas->detect($credentials);
46 11
        } catch (InvalidArgumentException $exception) {
47
            throw new AuthenticationException($exception->getMessage());
48
        }
49
50
        if (false === ($introspect instanceof ServiceValidate)) {
51 1
            throw new AuthenticationException(
52
                'Failure in the returned response'
53
            );
54 1
        }
55 1
56 1
        return true;
57
    }
58
59 1
    /**
60 1
     * {@inheritdoc}
61 1
     */
62
    public function getCredentials(Request $request): ?ResponseInterface
63
    {
64
        $response = $this
65 1
            ->cas->withServerRequest($this->toPsr($request))
66
            ->requestTicketValidation();
67
68
        if (null === $response) {
69
            throw new AuthenticationException('Unable to authenticate the user with such service ticket.');
70
        }
71
72
        return $response;
73
    }
74
75
    /**
76
     * {@inheritdoc}
77
     */
78
    public function getUser($credentials, UserProviderInterface $userProvider): ?UserInterface
79
    {
80
        if (false === ($userProvider instanceof CasUserProviderInterface)) {
81
            throw new AuthenticationException('Unable to load the user through the given User Provider.');
82
        }
83
84
        try {
85
            $user = $userProvider->loadUserByResponse($credentials);
86
        } catch (AuthenticationException $exception) {
87 1
            throw $exception;
88
        }
89 1
90 1
        return $user;
91
    }
92
93
    /**
94 1
     * {@inheritdoc}
95 1
     */
96 1
    public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response
97
    {
98
        $uri = $this->toPsr($request)->getUri();
99 1
100
        if (true === Uri::hasParams($uri, 'ticket')) {
101
            // Remove the ticket parameter.
102
            $uri = Uri::removeParams(
103
                $uri,
104
                'ticket'
105 1
            );
106
107 1
            // Add the renew parameter to force login again.
108
            $uri = Uri::withParam($uri, 'renew', 'true');
109 1
110
            return new RedirectResponse((string) $uri);
111 1
        }
112 1
113 1
        return null;
114
    }
115
116
    /**
117 1
     * {@inheritdoc}
118
     */
119 1
    public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $providerKey): Response
120
    {
121
        return new RedirectResponse(
122 1
            (string) Uri::removeParams(
123
                $this->toPsr($request)->getUri(),
124
                'ticket',
125
                'renew'
126
            )
127
        );
128 1
    }
129
130 1
    /**
131 1
     * {@inheritdoc}
132 1
     */
133 1
    public function start(Request $request, ?AuthenticationException $authException = null): Response
134 1
    {
135
        if (true === $request->isXmlHttpRequest()) {
136
            return new JsonResponse(
137
                ['message' => 'Authentication required'],
138
                Response::HTTP_UNAUTHORIZED
139
            );
140
        }
141
142 1
        $response = $this
143
            ->cas
144
            ->login();
145 1
146 1
        if (null === $response) {
147
            throw new AuthenticationException('Unable to trigger the login procedure');
148 1
        }
149
150
        return new RedirectResponse(
151
            $response
152 1
                ->getHeaderLine('location')
153
        );
154 1
    }
155
156
    /**
157
     * {@inheritdoc}
158
     */
159
    public function supports(Request $request): bool
160
    {
161 2
        return $this
162
            ->cas
163 2
            ->withServerRequest($this->toPsr($request))
164 1
            ->supportAuthentication();
165 1
    }
166 1
167
    /**
168
     * {@inheritdoc}
169
     */
170
    public function supportsRememberMe(): bool
171 1
    {
172 1
        return false;
173
    }
174 1
175
    /**
176
     * Convert a Symfony request into a PSR Request.
177
     *
178 1
     * @param \Symfony\Component\HttpFoundation\Request $request
179
     *   The Symfony request.
180 1
     *
181
     * @return \Psr\Http\Message\ServerRequestInterface
182
     *   The PSR request.
183
     */
184
    private function toPsr(Request $request): ServerRequestInterface
185
    {
186
        // As we cannot decorate the Symfony Request object, we convert it into
187 2
        // a PSR Request so we can override the PSR HTTP Message factory if
188
        // needed.
189
        // See the reasons at https://github.com/ecphp/cas-lib/issues/5
190 2
        return $this->httpMessageFactory->createRequest($request);
191 2
    }
192
}
193