CasAuthenticator::authenticate()   A
last analyzed

Complexity

Conditions 4
Paths 4

Size

Total Lines 29
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 4.1054

Importance

Changes 0
Metric Value
cc 4
eloc 18
nc 4
nop 1
dl 0
loc 29
ccs 13
cts 16
cp 0.8125
crap 4.1054
rs 9.6666
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\CasLib\CasInterface;
16
use EcPhp\CasLib\Introspection\Contract\ServiceValidate;
17
use EcPhp\CasLib\Utils\Uri;
18
use InvalidArgumentException;
19
use Psr\Http\Message\ServerRequestInterface;
20
use Symfony\Bridge\PsrHttpMessage\HttpMessageFactoryInterface;
21
use Symfony\Component\HttpFoundation\RedirectResponse;
22
use Symfony\Component\HttpFoundation\Request;
23
use Symfony\Component\HttpFoundation\Response;
24
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
25
use Symfony\Component\Security\Core\Exception\AuthenticationException;
26
use Symfony\Component\Security\Core\User\UserInterface;
27
use Symfony\Component\Security\Http\Authenticator\AbstractAuthenticator;
28
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
29
use Symfony\Component\Security\Http\Authenticator\Passport\Passport;
30
use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport;
31
32
final class CasAuthenticator extends AbstractAuthenticator
33
{
34
    private CasInterface $cas;
35
36
    private HttpMessageFactoryInterface $httpMessageFactory;
37
38 7
    public function __construct(
39
        CasInterface $cas,
40
        HttpMessageFactoryInterface $httpMessageFactory
41
    ) {
42 7
        $this->cas = $cas;
43 7
        $this->httpMessageFactory = $httpMessageFactory;
44
    }
45
46 2
    public function authenticate(Request $request): Passport
47
    {
48
        $response = $this
49 2
            ->cas
50 2
            ->withServerRequest($this->toPsr($request))
51 2
            ->requestTicketValidation();
52
53 2
        if (null === $response) {
54 2
            throw new AuthenticationException('Unable to authenticate the user with such service ticket.');
55
        }
56
57
        try {
58 2
            $introspect = $this->cas->detect($response);
59
        } catch (InvalidArgumentException $exception) {
60
            throw new AuthenticationException($exception->getMessage(), 0, $exception);
61
        }
62
63 2
        if (false === ($introspect instanceof ServiceValidate)) {
64
            throw new AuthenticationException(
65
                'Failure in the returned response'
66
            );
67
        }
68
69 2
        $payload = $introspect->getCredentials();
70
71 2
        return new SelfValidatingPassport(
72 2
            new UserBadge(
73 2
                $payload['user'],
74 2
                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

74
                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...
75
            )
76
        );
77
    }
78
79 1
    public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response
80
    {
81 1
        $uri = $this->toPsr($request)->getUri();
82
83 1
        if (false === Uri::hasParams($uri, 'ticket')) {
84 1
            return null;
85
        }
86
87
        // Remove the ticket parameter.
88 1
        $uri = Uri::removeParams(
89
            $uri,
90
            'ticket'
91
        );
92
93 1
        return new RedirectResponse((string) Uri::withParam($uri, 'renew', 'true'));
94
    }
95
96 1
    public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): Response
97
    {
98 1
        return new RedirectResponse(
99 1
            (string) Uri::removeParams(
100 1
                $this->toPsr($request)->getUri(),
101
                'ticket',
102
                'renew'
103
            )
104
        );
105
    }
106
107 2
    public function supports(Request $request): bool
108
    {
109
        return $this
110 2
            ->cas
111 2
            ->withServerRequest($this->toPsr($request))
112 2
            ->supportAuthentication();
113
    }
114
115
    /**
116
     * Convert a Symfony request into a PSR Request.
117
     *
118
     * @param \Symfony\Component\HttpFoundation\Request $request
119
     *   The Symfony request.
120
     *
121
     * @return \Psr\Http\Message\ServerRequestInterface
122
     *   The PSR request.
123
     */
124 6
    private function toPsr(Request $request): ServerRequestInterface
125
    {
126
        // As we cannot decorate the Symfony Request object, we convert it into
127
        // a PSR Request so we can override the PSR HTTP Message factory if
128
        // needed.
129
        // See the reasons at https://github.com/ecphp/cas-lib/issues/5
130 6
        return $this->httpMessageFactory->createRequest($request);
131
    }
132
}
133