Passed
Pull Request — master (#59)
by Pol
15:05
created

CasAuthenticator   A

Complexity

Total Complexity 10

Size/Duplication

Total Lines 101
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 40
dl 0
loc 101
rs 10
c 0
b 0
f 0
wmc 10

6 Methods

Rating   Name   Duplication   Size   Complexity  
A onAuthenticationSuccess() 0 7 1
A __construct() 0 6 1
A authenticate() 0 28 4
A supports() 0 6 1
A onAuthenticationFailure() 0 18 2
A toPsr() 0 7 1
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\SelfValidatingPassport;
30
31
final class CasAuthenticator extends AbstractAuthenticator
32
{
33
    private CasInterface $cas;
34
35
    private HttpMessageFactoryInterface $httpMessageFactory;
36
37
    public function __construct(
38
        CasInterface $cas,
39
        HttpMessageFactoryInterface $httpMessageFactory
40
    ) {
41
        $this->cas = $cas;
42
        $this->httpMessageFactory = $httpMessageFactory;
43
    }
44
45
    public function authenticate(Request $request)
46
    {
47
        $response = $this
48
            ->cas->withServerRequest($this->toPsr($request))
49
            ->requestTicketValidation();
50
51
        if (null === $response) {
52
            throw new AuthenticationException('Unable to authenticate the user with such service ticket.');
53
        }
54
55
        try {
56
            $introspect = $this->cas->detect($response);
57
        } catch (InvalidArgumentException $exception) {
58
            throw new AuthenticationException($exception->getMessage(), 0, $exception);
59
        }
60
61
        if (false === ($introspect instanceof ServiceValidate)) {
62
            throw new AuthenticationException(
63
                'Failure in the returned response'
64
            );
65
        }
66
67
        $payload = $introspect->getCredentials();
68
69
        return new SelfValidatingPassport(
70
            new UserBadge(
71
                $payload['user'],
72
                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

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