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

Cas30Controller::samlValidate()   B

Complexity

Conditions 8
Paths 7

Size

Total Lines 57
Code Lines 29

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 29
c 2
b 0
f 0
dl 0
loc 57
rs 8.2114
cc 8
nc 7
nop 2

How to fix   Long Method   

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;
6
7
use DOMXPath;
8
use SAML2\DOMDocumentFactory;
9
use SimpleSAML\Configuration;
10
use SimpleSAML\Logger;
11
use SimpleSAML\Module\casserver\Cas\Protocol\Cas20;
12
use SimpleSAML\Module\casserver\Cas\Protocol\SamlValidateResponder;
13
use SimpleSAML\Module\casserver\Cas\TicketValidator;
14
use SimpleSAML\Module\casserver\Controller\Traits\UrlTrait;
15
use SimpleSAML\Module\casserver\Http\XmlResponse;
16
use Symfony\Component\HttpFoundation\Request;
17
use Symfony\Component\HttpFoundation\Response;
18
use Symfony\Component\HttpKernel\Attribute\AsController;
19
use Symfony\Component\HttpKernel\Attribute\MapQueryParameter;
20
21
#[AsController]
22
class Cas30Controller
23
{
24
    use UrlTrait;
0 ignored issues
show
introduced by
The trait SimpleSAML\Module\casser...troller\Traits\UrlTrait requires some properties which are not provided by SimpleSAML\Module\casser...troller\Cas30Controller: $ticketFactory, $request, $ticketStore, $httpUtils, $query
Loading history...
25
26
    /** @var Logger */
27
    protected Logger $logger;
28
29
    /** @var Configuration */
30
    protected Configuration $casConfig;
31
32
    /** @var Cas20 */
33
    protected Cas20 $cas20Protocol;
34
35
    /** @var TicketValidator */
36
    protected TicketValidator $ticketValidator;
37
38
    /** @var SamlValidateResponder */
39
    protected SamlValidateResponder $validateResponder;
40
41
    /**
42
     * @param   Configuration         $sspConfig
43
     * @param   Configuration|null    $casConfig
44
     * @param   TicketValidator|null  $ticketValidator
45
     *
46
     * @throws \Exception
47
     */
48
    public function __construct(
49
        private readonly Configuration $sspConfig,
50
        Configuration $casConfig = null,
51
        TicketValidator $ticketValidator = null,
52
    ) {
53
        // We are using this work around in order to bypass Symfony's autowiring for cas configuration. Since
54
        // the configuration class is the same, it loads the ssp configuration twice. Still, we need the constructor
55
        // argument in order to facilitate testin.
56
        $this->casConfig = ($casConfig === null || $casConfig === $sspConfig)
57
            ? Configuration::getConfig('module_casserver.php') : $casConfig;
58
        $this->cas20Protocol = new Cas20($this->casConfig);
59
        $this->ticketValidator   = $ticketValidator ?? new TicketValidator($this->casConfig);
60
        $this->validateResponder = new SamlValidateResponder();
61
    }
62
63
64
    /**
65
     * POST /casserver/samlValidate?TARGET=
66
     * Host: cas.example.com
67
     * Content-Length: 491
68
     * Content-Type: text/xml
69
     *
70
     * @param   Request  $request
71
     * @param   string   $TARGET  URL encoded service identifier of the back-end service.
72
     *
73
     * @throws \RuntimeException
74
     * @return XmlResponse
75
     * @link https://apereo.github.io/cas/7.1.x/protocol/CAS-Protocol-Specification.html#42-samlvalidate-cas-30
76
     */
77
    public function samlValidate(
78
        Request $request,
79
        #[MapQueryParameter] string $TARGET,
80
    ): XmlResponse {
81
        // From SAML2\SOAP::receive()
82
        $postBody = $request->getContent();
83
        if (empty($postBody)) {
84
            throw new \RuntimeException('samlValidate expects a soap body.');
85
        }
86
87
        // SAML request values
88
        //
89
        // samlp:Request
90
        //  - RequestID [REQUIRED] - unique identifier for the request
91
        //  - IssueInstant [REQUIRED] - timestamp of the request
92
        // samlp:AssertionArtifact [REQUIRED] - the valid CAS Service
93
94
        $documentBody = DOMDocumentFactory::fromString($postBody);
95
        $xPath = new DOMXpath($documentBody);
96
        $xPath->registerNamespace('soap-env', 'http://schemas.xmlsoap.org/soap/envelope/');
97
        $samlRequestAttributes = $xPath->query('/soap-env:Envelope/soap-env:Body/*');
98
99
        // Check for the required saml attributes
100
        if (!$samlRequestAttributes->item(0)->hasAttribute('RequestID')) {
0 ignored issues
show
Bug introduced by
The method hasAttribute() does not exist on DOMNode. Did you maybe mean hasAttributes()? ( Ignorable by Annotation )

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

100
        if (!$samlRequestAttributes->item(0)->/** @scrutinizer ignore-call */ hasAttribute('RequestID')) {

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
101
            throw new \RuntimeException('Missing RequestID samlp:Request attribute.');
102
        } elseif (!$samlRequestAttributes->item(0)->hasAttribute('IssueInstant')) {
103
            throw new \RuntimeException('Missing IssueInstant samlp:Request attribute.');
104
        }
105
106
        $assertionArtifactNode = $samlRequestAttributes->item(0)->getElementsByTagName('AssertionArtifact');
107
        if (
108
            $assertionArtifactNode->count() === 0
109
            || empty($assertionArtifactNode->item(0)->nodeValue)
110
        ) {
111
            throw new \RuntimeException('Missing ticketId in AssertionArtifact');
112
        }
113
114
        $ticketId = $assertionArtifactNode->item(0)->nodeValue;
115
        Logger::debug('samlvalidate: Checking ticket ' . $ticketId);
116
117
        try {
118
            // validateAndDeleteTicket might throw a CasException. In order to avoid third party modules
119
            // dependencies, we will catch and rethrow the Exception.
120
            $ticket = $this->ticketValidator->validateAndDeleteTicket($ticketId, $TARGET);
121
        } catch (\Exception $e) {
122
            throw new \RuntimeException($e->getMessage());
123
        }
124
        if (!\is_array($ticket)) {
0 ignored issues
show
introduced by
The condition is_array($ticket) is always true.
Loading history...
125
            throw new \RuntimeException('Error loading ticket');
126
        }
127
128
        $response = $this->validateResponder->convertToSaml($ticket);
129
        $soap     = $this->validateResponder->wrapInSoap($response);
130
131
        return new XmlResponse(
132
            (string)$soap,
133
            Response::HTTP_OK,
134
        );
135
    }
136
}
137