Passed
Pull Request — master (#59)
by Pol
14:21
created

CasAuthenticator::getUser()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 18
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 10
nc 3
nop 2
dl 0
loc 18
rs 9.9332
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * For the full copyright and license information, please view
5
 * the LICENSE file that was distributed with this source code.
6
 *
7
 * @see https://github.com/ecphp
8
 */
9
10
declare(strict_types=1);
11
12
namespace EcPhp\CasBundle\Security;
13
14
use EcPhp\CasBundle\Security\Core\User\CasUser;
15
use EcPhp\CasBundle\Security\Core\User\CasUserProviderInterface;
16
use EcPhp\CasLib\Contract\CasInterface;
17
use EcPhp\CasLib\Contract\Response\Type\ServiceValidate;
18
use EcPhp\CasLib\Utils\Uri;
19
use Psr\Http\Message\ServerRequestInterface;
20
use Symfony\Bridge\PsrHttpMessage\HttpFoundationFactoryInterface;
21
use Symfony\Bridge\PsrHttpMessage\HttpMessageFactoryInterface;
22
use Symfony\Component\HttpFoundation\JsonResponse;
23
use Symfony\Component\HttpFoundation\RedirectResponse;
24
use Symfony\Component\HttpFoundation\Request;
25
use Symfony\Component\HttpFoundation\Response;
26
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
27
use Symfony\Component\Security\Core\Exception\AuthenticationException;
28
use Symfony\Component\Security\Core\User\UserInterface;
29
use Symfony\Component\Security\Core\User\UserProviderInterface;
30
use Symfony\Component\Security\Http\Authenticator\AbstractAuthenticator;
31
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
32
use Symfony\Component\Security\Http\Authenticator\Passport\Passport;
33
use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport;
34
use Throwable;
35
36
final class CasAuthenticator extends AbstractAuthenticator
37
{
38
    private CasInterface $cas;
39
40
    private HttpFoundationFactoryInterface $httpFoundationFactory;
41
42
    private HttpMessageFactoryInterface $httpMessageFactory;
43
44
    public function __construct(
45
        CasInterface $cas,
46
        HttpMessageFactoryInterface $httpMessageFactory,
47
        HttpFoundationFactoryInterface $httpFoundationFactory
48
    ) {
49
        $this->cas = $cas;
50
        $this->httpMessageFactory = $httpMessageFactory;
51
        $this->httpFoundationFactory = $httpFoundationFactory;
52
    }
53
54
    public function authenticate(Request $request): Passport
55
    {
56
        try {
57
            $response = $this
58
                ->cas
59
                ->requestTicketValidation(
60
                    $this->httpMessageFactory->createRequest($request)
61
                );
62
        } catch (Throwable $exception) {
63
            throw new AuthenticationException(
64
                sprintf('Unable to authenticate the user with such service ticket: %s', $exception->getMessage()),
65
                0,
66
                $exception
67
            );
68
        }
69
70
        if (false === ($response instanceof ServiceValidate)) {
71
            throw new AuthenticationException(
72
                'Failure in the returned response'
73
            );
74
        }
75
76
        /** @var ServiceValidate $response */
77
        $payload = $response->getCredentials();
78
79
        return new SelfValidatingPassport(
80
            new UserBadge(
81
                $payload['user'],
82
                static fn (string $identifier): UserInterface => new CasUser($payload)
0 ignored issues
show
Unused Code introduced by
The parameter $identifier is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

82
                static fn (/** @scrutinizer ignore-unused */ string $identifier): UserInterface => new CasUser($payload)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
83
            )
84
        );
85
    }
86
87
    public function getUser($credentials, UserProviderInterface $userProvider): ?UserInterface
88
    {
89
        if (false === ($userProvider instanceof CasUserProviderInterface)) {
90
            throw new AuthenticationException('Unable to load the user through the given User Provider.');
91
        }
92
93
        if (false === ($credentials instanceof ServiceValidate)) {
94
            throw new AuthenticationException(
95
                'Failure in the returned response'
96
            );
97
        }
98
99
        $payload = $credentials->getCredentials();
100
101
        return new SelfValidatingPassport(
0 ignored issues
show
Bug Best Practice introduced by
The expression return new Symfony\Compo...on(...) { /* ... */ })) returns the type Symfony\Component\Securi...\SelfValidatingPassport which is incompatible with the type-hinted return Symfony\Component\Securi...User\UserInterface|null.
Loading history...
102
            new UserBadge(
103
                $payload['user'],
104
                static fn (string $identifier): UserInterface => new CasUser($payload)
0 ignored issues
show
Unused Code introduced by
The parameter $identifier is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

104
                static fn (/** @scrutinizer ignore-unused */ string $identifier): UserInterface => new CasUser($payload)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
105
            )
106
        );
107
    }
108
109
    public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response
110
    {
111
        $uri = $this->toPsr($request)->getUri();
112
113
        if (false === Uri::hasParams($uri, 'ticket')) {
114
            return null;
115
        }
116
117
        // Remove the ticket parameter.
118
        $uri = Uri::removeParams(
119
            $uri,
120
            'ticket'
121
        );
122
123
        return new RedirectResponse((string) Uri::withParam($uri, 'renew', 'true'));
124
    }
125
126
    public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): Response
127
    {
128
        return new RedirectResponse(
129
            (string) Uri::removeParams(
130
                $this->toPsr($request)->getUri(),
131
                'ticket',
132
                'renew'
133
            )
134
        );
135
    }
136
137
    public function start(Request $request, ?AuthenticationException $authException = null): Response
0 ignored issues
show
Unused Code introduced by
The parameter $authException is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

137
    public function start(Request $request, /** @scrutinizer ignore-unused */ ?AuthenticationException $authException = null): Response

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
138
    {
139
        if (true === $request->isXmlHttpRequest()) {
140
            return new JsonResponse(
141
                ['message' => 'Authentication required'],
142
                Response::HTTP_UNAUTHORIZED
143
            );
144
        }
145
146
        try {
147
            $response = $this
148
                ->cas
149
                ->login($this->toPsr($request));
150
        } catch (Throwable $exception) {
151
            throw new AuthenticationException(
152
                'Unable to trigger the login procedure',
153
                0,
154
                $exception
155
            );
156
        }
157
158
        return $this->httpFoundationFactory->createResponse($response);
159
    }
160
161
    public function supports(Request $request): bool
162
    {
163
        return $this
164
            ->cas
165
            ->supportAuthentication(
166
                $this->toPsr($request)
167
            );
168
    }
169
170
    /**
171
     * Convert a Symfony request into a PSR Request.
172
     *
173
     * @param \Symfony\Component\HttpFoundation\Request $request
174
     *   The Symfony request.
175
     *
176
     * @return \Psr\Http\Message\ServerRequestInterface
177
     *   The PSR request.
178
     */
179
    private function toPsr(Request $request): ServerRequestInterface
180
    {
181
        // As we cannot decorate the Symfony Request object, we convert it into
182
        // a PSR Request so we can override the PSR HTTP Message factory if
183
        // needed.
184
        // See the reasons at https://github.com/ecphp/cas-lib/issues/5
185
        return $this->httpMessageFactory->createRequest($request);
186
    }
187
}
188