Passed
Push — master ( 81b6b5...5b35ab )
by Tim
03:44
created

TicketValidatorTrait::validate()   F

Complexity

Conditions 22
Paths 136

Size

Total Lines 156
Code Lines 86

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 86
c 1
b 0
f 0
dl 0
loc 156
rs 3.8666
cc 22
nc 136
nop 7

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
namespace SimpleSAML\Module\casserver\Controller\Traits;
6
7
use SimpleSAML\CAS\Constants as C;
8
use SimpleSAML\Logger;
9
use SimpleSAML\Module\casserver\Http\XmlResponse;
10
use Symfony\Component\HttpFoundation\Request;
11
use Symfony\Component\HttpFoundation\Response;
12
13
trait TicketValidatorTrait
14
{
15
    /**
16
     * @param   Request      $request
17
     * @param   string       $method
18
     * @param   bool         $renew
19
     * @param   string|null  $target
20
     * @param   string|null  $ticket
21
     * @param   string|null  $service
22
     * @param   string|null  $pgtUrl
23
     *
24
     * @return XmlResponse
25
     */
26
    public function validate(
27
        Request $request,
28
        string $method,
29
        bool $renew = false,
30
        ?string $target = null,
31
        ?string $ticket = null,
32
        ?string $service = null,
33
        ?string $pgtUrl = null,
34
    ): XmlResponse {
35
        $forceAuthn = $renew;
36
        $serviceUrl = $service ?? $target ?? null;
37
38
        // Check if any of the required query parameters are missing
39
        if ($serviceUrl === null || $ticket === null) {
40
            $messagePostfix = $serviceUrl === null ? 'service' : 'ticket';
41
            $message        = "casserver: Missing {$messagePostfix} parameter: [{$messagePostfix}]";
42
            Logger::debug($message);
43
44
            return new XmlResponse(
45
                (string)$this->cas20Protocol->getValidateFailureResponse(C::ERR_INVALID_SERVICE, $message),
46
                Response::HTTP_BAD_REQUEST,
47
            );
48
        }
49
50
        try {
51
            // Get the service ticket
52
            // `getTicket` uses the unserializable method and Objects may throw "Throwables" in their
53
            // un-serialization handlers.
54
            $serviceTicket = $this->ticketStore->getTicket($ticket);
55
        } catch (\Exception $e) {
56
            $messagePostfix = '';
57
            if (!empty($e->getMessage())) {
58
                $messagePostfix = ': ' . var_export($e->getMessage(), true);
59
            }
60
            $message = 'casserver:serviceValidate: internal server error' . $messagePostfix;
61
            Logger::error(__METHOD__ . '::' . $message);
62
63
            return new XmlResponse(
64
                (string)$this->cas20Protocol->getValidateFailureResponse(C::ERR_INTERNAL_ERROR, $message),
65
                Response::HTTP_INTERNAL_SERVER_ERROR,
66
            );
67
        }
68
69
        $failed  = false;
70
        $message = '';
71
        // Below, we do not have a ticket or the ticket does not meet the very basic criteria that allow
72
        // any further handling
73
        if (empty($serviceTicket)) {
74
            // No ticket
75
            $message = 'Ticket ' . var_export($ticket, true) . ' not recognized';
76
            $failed  = true;
77
        } elseif ($method === 'proxyValidate' && !$this->ticketFactory->isProxyTicket($serviceTicket)) {
78
            // proxyValidate but not a proxy ticket
79
            $message = 'Ticket ' . var_export($ticket, true) . ' is not a proxy ticket.';
80
            $failed  = true;
81
        } elseif ($method === 'serviceValidate' && !$this->ticketFactory->isServiceTicket($serviceTicket)) {
82
            // serviceValidate but not a service ticket
83
            $message = 'Ticket ' . var_export($ticket, true) . ' is not a service ticket.';
84
            $failed  = true;
85
        }
86
87
        if ($failed) {
88
            $finalMessage = 'casserver:validate: ' . $message;
89
            Logger::error(__METHOD__ . '::' . $finalMessage);
90
91
            return new XmlResponse(
92
                (string)$this->cas20Protocol->getValidateFailureResponse(C::ERR_INVALID_SERVICE, $message),
93
                Response::HTTP_BAD_REQUEST,
94
            );
95
        }
96
97
        // Delete the ticket
98
        $this->ticketStore->deleteTicket($ticket);
99
100
        // Check if the ticket
101
        // - has expired
102
        // - does not pass sanitization
103
        // - forceAutnn criteria are not met
104
        if ($this->ticketFactory->isExpired($serviceTicket)) {
105
            // the ticket has expired
106
            $message = 'Ticket ' . var_export($ticket, true) . ' has expired';
107
            $failed  = true;
108
        } elseif ($this->sanitize($serviceTicket['service']) !== $this->sanitize($serviceUrl)) {
0 ignored issues
show
Bug introduced by
It seems like sanitize() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

108
        } elseif ($this->/** @scrutinizer ignore-call */ sanitize($serviceTicket['service']) !== $this->sanitize($serviceUrl)) {
Loading history...
109
            // The service url we passed to the query parameters does not match the one in the ticket.
110
            $message = 'Mismatching service parameters: expected ' .
111
                var_export($serviceTicket['service'], true) .
112
                ' but was: ' . var_export($serviceUrl, true);
113
            $failed  = true;
114
        } elseif ($forceAuthn && !$serviceTicket['forceAuthn']) {
115
            // If `forceAuthn` is required but not set in the ticket
116
            $message = 'Ticket was issued from single sign on session';
117
            $failed  = true;
118
        }
119
120
        if ($failed) {
121
            $finalMessage = 'casserver:validate: ' . $message;
122
            Logger::error(__METHOD__ . '::' . $finalMessage);
123
124
            return new XmlResponse(
125
                (string)$this->cas20Protocol->getValidateFailureResponse(C::ERR_INVALID_SERVICE, $message),
126
                Response::HTTP_BAD_REQUEST,
127
            );
128
        }
129
130
        $attributes = $serviceTicket['attributes'];
131
        $this->cas20Protocol->setAttributes($attributes);
132
133
        if (isset($pgtUrl)) {
134
            $sessionTicket = $this->ticketStore->getTicket($serviceTicket['sessionId']);
135
            if (
136
                $sessionTicket !== null
137
                && $this->ticketFactory->isSessionTicket($sessionTicket)
138
                && !$this->ticketFactory->isExpired($sessionTicket)
139
            ) {
140
                $proxyGrantingTicket = $this->ticketFactory->createProxyGrantingTicket(
141
                    [
142
                        'userName' => $serviceTicket['userName'],
143
                        'attributes' => $attributes,
144
                        'forceAuthn' => false,
145
                        'proxies' => array_merge(
146
                            [$serviceUrl],
147
                            $serviceTicket['proxies'],
148
                        ),
149
                        'sessionId' => $serviceTicket['sessionId'],
150
                    ],
151
                );
152
                try {
153
                    // Here we assume that the fetch will throw on any error.
154
                    // The generation of the proxy-granting-ticket or the corresponding proxy granting ticket IOU may
155
                    // fail due to the proxy callback url failing to meet the minimum security requirements such as
156
                    // failure to establish trust between peers or unresponsiveness of the endpoint, etc.
157
                    // In case of failure, no proxy-granting ticket will be issued and the CAS service response
158
                    // as described in Section 2.5.2 MUST NOT contain a <proxyGrantingTicket> block.
159
                    // At this point, the issuance of a proxy-granting ticket is halted and service ticket
160
                    // validation will fail.
161
                    $data = $this->httpUtils->fetch(
162
                        $pgtUrl . '?pgtIou=' . $proxyGrantingTicket['iou'] . '&pgtId=' . $proxyGrantingTicket['id'],
163
                    );
164
                    Logger::debug(__METHOD__ . '::data: ' . var_export($data, true));
165
                    $this->cas20Protocol->setProxyGrantingTicketIOU($proxyGrantingTicket['iou']);
166
                    $this->ticketStore->addTicket($proxyGrantingTicket);
167
                } catch (\Exception $e) {
168
                    return new XmlResponse(
169
                        (string)$this->cas20Protocol->getValidateFailureResponse(
170
                            C::ERR_INVALID_SERVICE,
171
                            'Proxy callback url is failing.',
172
                        ),
173
                        Response::HTTP_BAD_REQUEST,
174
                    );
175
                }
176
            }
177
        }
178
179
        return new XmlResponse(
180
            (string)$this->cas20Protocol->getValidateSuccessResponse($serviceTicket['userName']),
181
            Response::HTTP_OK,
182
        );
183
    }
184
}
185