Passed
Pull Request — master (#45)
by
unknown
14:33
created

Cas10Controller::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 17
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 9
c 2
b 0
f 0
dl 0
loc 17
rs 9.9666
cc 1
nc 1
nop 2
1
<?php
2
3
declare(strict_types=1);
4
5
namespace SimpleSAML\Module\casserver\Controller;
6
7
use SimpleSAML\Configuration;
8
use SimpleSAML\Logger;
9
use SimpleSAML\Module\casserver\Cas\Factories\TicketFactory;
10
use SimpleSAML\Module\casserver\Cas\Protocol\Cas10;
11
use SimpleSAML\Module\casserver\Controller\Traits\UrlTrait;
12
use Symfony\Component\HttpFoundation\Request;
13
use Symfony\Component\HttpFoundation\Response;
14
use Symfony\Component\HttpKernel\Attribute\AsController;
15
16
#[AsController]
17
class Cas10Controller
18
{
19
    use UrlTrait;
1 ignored issue
show
introduced by
The trait SimpleSAML\Module\casser...troller\Traits\UrlTrait requires some properties which are not provided by SimpleSAML\Module\casser...troller\Cas10Controller: $query, $request
Loading history...
20
21
    /** @var Logger */
22
    protected Logger $logger;
23
24
    /** @var Configuration */
25
    protected Configuration $casConfig;
26
27
    /** @var Cas10 */
28
    protected Cas10 $cas10Protocol;
29
30
    /** @var TicketFactory */
31
    protected TicketFactory $ticketFactory;
32
33
    // this could be any configured ticket store
34
    protected mixed $ticketStore;
35
36
    /**
37
     * @param   Configuration|null  $casConfig
38
     * @param                       $ticketStore
39
     *
40
     * @throws \Exception
41
     */
42
    public function __construct(
43
        Configuration $casConfig = null,
44
        $ticketStore = null,
45
    ) {
46
        $this->casConfig = $casConfig ?? Configuration::getConfig('module_casserver.php');
47
        $this->cas10Protocol = new Cas10($this->casConfig);
48
        /* Instantiate ticket factory */
49
        $this->ticketFactory = new TicketFactory($this->casConfig);
50
        /* Instantiate ticket store */
51
        $ticketStoreConfig = $this->casConfig->getOptionalValue(
52
            'ticketstore',
53
            ['class' => 'casserver:FileSystemTicketStore'],
54
        );
55
        $ticketStoreClass = 'SimpleSAML\\Module\\casserver\\Cas\\Ticket\\'
56
            . explode(':', $ticketStoreConfig['class'])[1];
57
        /** @psalm-suppress InvalidStringClass */
58
        $this->ticketStore = $ticketStore ?? new $ticketStoreClass($this->casConfig);
59
    }
60
61
    /**
62
     * @param   Request      $request
63
     * @param   bool         $renew
64
     * @param   string|null  $ticket
65
     * @param   string|null  $service
66
     *
67
     * @return Response
68
     */
69
    public function validate(
70
        Request $request,
71
        #[MapQueryParameter] bool $renew = false,
72
        #[MapQueryParameter] ?string $ticket = null,
73
        #[MapQueryParameter] ?string $service = null,
74
    ): Response {
75
76
        $forceAuthn = $renew;
77
        // Check if any of the required query parameters are missing
78
        if ($service === null || $ticket === null) {
79
            $messagePostfix = $service === null ? 'service' : 'ticket';
80
            Logger::debug("casserver: Missing service parameter: [{$messagePostfix}]");
81
            return new Response(
82
                $this->cas10Protocol->getValidateFailureResponse(),
83
                Response::HTTP_BAD_REQUEST,
84
            );
85
        }
86
87
        try {
88
            // Get the service ticket
89
            // `getTicket` uses the unserializable method and Objects may throw Throwables in their
90
            // unserialization handlers.
91
            $serviceTicket = $this->ticketStore->getTicket($ticket);
92
            // Delete the ticket
93
            $this->ticketStore->deleteTicket($ticket);
94
        } catch (\Exception $e) {
95
            Logger::error('casserver:validate: internal server error. ' . var_export($e->getMessage(), true));
96
            return new Response(
97
                $this->cas10Protocol->getValidateFailureResponse(),
98
                Response::HTTP_INTERNAL_SERVER_ERROR,
99
            );
100
        }
101
102
        $failed = false;
103
        $message = '';
104
        // No ticket
105
        if ($serviceTicket === null) {
106
            $message = 'ticket: ' . var_export($ticket, true) . ' not recognized';
107
            $failed = true;
108
            // This is not a service ticket
109
        } elseif (!$this->ticketFactory->isServiceTicket($serviceTicket)) {
110
            $message = 'ticket: ' . var_export($ticket, true) . ' is not a service ticket';
111
            $failed = true;
112
            // the ticket has expired
113
        } elseif ($this->ticketFactory->isExpired($serviceTicket)) {
114
            $message = 'Ticket has ' . var_export($ticket, true) . ' expired';
115
            $failed = true;
116
        } elseif ($this->sanitize($serviceTicket['service']) !== $this->sanitize($service)) {
117
            // The service we pass to the query parameters does not match the one in the ticket.
118
            $message = 'Mismatching service parameters: expected ' .
119
                var_export($serviceTicket['service'], true) .
120
                ' but was: ' . var_export($service, true);
121
            $failed = true;
122
        } elseif ($forceAuthn) {
123
            // If the forceAuthn/renew is true
124
            $message = 'Ticket was issued from single sign on session';
125
            $failed = true;
126
        }
127
128
        if ($failed) {
129
            Logger::error('casserver:validate: ' . $message, true);
0 ignored issues
show
Unused Code introduced by
The call to SimpleSAML\Logger::error() has too many arguments starting with true. ( Ignorable by Annotation )

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

129
            Logger::/** @scrutinizer ignore-call */ 
130
                    error('casserver:validate: ' . $message, true);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
130
            return new Response(
131
                $this->cas10Protocol->getValidateFailureResponse(),
132
                Response::HTTP_BAD_REQUEST,
133
            );
134
        }
135
136
        // Get the username field
137
        $usernameField = $this->casConfig->getOptionalValue('attrname', 'eduPersonPrincipalName');
138
139
        // Fail if the username field is not present in the attribute list
140
        if (!\array_key_exists($usernameField, $serviceTicket['attributes'])) {
141
            Logger::error(
142
                'casserver:validate: internal server error. Missing user name attribute: '
143
                . var_export($usernameField, true),
144
            );
145
            return new Response(
146
                $this->cas10Protocol->getValidateFailureResponse(),
147
                Response::HTTP_BAD_REQUEST,
148
            );
149
        }
150
151
        // Successful validation
152
        return new Response(
153
            $this->cas10Protocol->getValidateSuccessResponse($serviceTicket['attributes'][$usernameField][0]),
154
            Response::HTTP_OK,
155
        );
156
    }
157
158
    /**
159
     * @return mixed
160
     */
161
    public function getTicketStore(): mixed
162
    {
163
        return $this->ticketStore;
164
    }
165
}
166