Passed
Pull Request — master (#59)
by Pol
18:26 queued 03:23
created

CasAuthenticator::supports()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 4
nc 1
nop 1
dl 0
loc 6
rs 10
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
    public function __construct(
39
        CasInterface $cas,
40
        HttpMessageFactoryInterface $httpMessageFactory
41
    ) {
42
        $this->cas = $cas;
43
        $this->httpMessageFactory = $httpMessageFactory;
44
    }
45
46
    public function authenticate(Request $request): Passport
47
    {
48
        $response = $this
49
            ->cas->withServerRequest($this->toPsr($request))
50
            ->requestTicketValidation();
51
52
        if (null === $response) {
53
            throw new AuthenticationException('Unable to authenticate the user with such service ticket.');
54
        }
55
56
        try {
57
            $introspect = $this->cas->detect($response);
58
        } catch (InvalidArgumentException $exception) {
59
            throw new AuthenticationException($exception->getMessage(), 0, $exception);
60
        }
61
62
        if (false === ($introspect instanceof ServiceValidate)) {
63
            throw new AuthenticationException(
64
                'Failure in the returned response'
65
            );
66
        }
67
68
        $payload = $introspect->getCredentials();
69
70
        return new SelfValidatingPassport(
71
            new UserBadge(
72
                $payload['user'],
73
                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

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