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

Cas30Controller   A

Complexity

Total Complexity 9

Size/Duplication

Total Lines 106
Duplicated Lines 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
wmc 9
eloc 36
c 3
b 0
f 0
dl 0
loc 106
rs 10

2 Methods

Rating   Name   Duplication   Size   Complexity  
B samlValidate() 0 50 6
A __construct() 0 13 3
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\Protocol\Cas20;
10
use SimpleSAML\Module\casserver\Cas\Protocol\SamlValidateResponder;
11
use SimpleSAML\Module\casserver\Cas\TicketValidator;
12
use SimpleSAML\Module\casserver\Controller\Traits\UrlTrait;
13
use SimpleSAML\Module\casserver\Http\XmlResponse;
14
use SimpleSAML\SAML11\XML\samlp\Request as SamlRequest;
15
use SimpleSAML\SOAP\XML\env_200106\Envelope;
16
use SimpleSAML\XML\DOMDocumentFactory;
17
use Symfony\Component\HttpFoundation\Request;
18
use Symfony\Component\HttpFoundation\Response;
19
use Symfony\Component\HttpKernel\Attribute\AsController;
20
use Symfony\Component\HttpKernel\Attribute\MapQueryParameter;
21
22
#[AsController]
23
class Cas30Controller
24
{
25
    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...
26
27
    /** @var Logger */
28
    protected Logger $logger;
29
30
    /** @var Configuration */
31
    protected Configuration $casConfig;
32
33
    /** @var Cas20 */
34
    protected Cas20 $cas20Protocol;
35
36
    /** @var TicketValidator */
37
    protected TicketValidator $ticketValidator;
38
39
    /** @var SamlValidateResponder */
40
    protected SamlValidateResponder $validateResponder;
41
42
    /**
43
     * @param   Configuration         $sspConfig
44
     * @param   Configuration|null    $casConfig
45
     * @param   TicketValidator|null  $ticketValidator
46
     *
47
     * @throws \Exception
48
     */
49
    public function __construct(
50
        private readonly Configuration $sspConfig,
51
        Configuration $casConfig = null,
52
        TicketValidator $ticketValidator = null,
53
    ) {
54
        // We are using this work around in order to bypass Symfony's autowiring for cas configuration. Since
55
        // the configuration class is the same, it loads the ssp configuration twice. Still, we need the constructor
56
        // argument in order to facilitate testin.
57
        $this->casConfig = ($casConfig === null || $casConfig === $sspConfig)
58
            ? Configuration::getConfig('module_casserver.php') : $casConfig;
59
        $this->cas20Protocol = new Cas20($this->casConfig);
60
        $this->ticketValidator   = $ticketValidator ?? new TicketValidator($this->casConfig);
61
        $this->validateResponder = new SamlValidateResponder();
62
    }
63
64
65
    /**
66
     * POST /casserver/samlValidate?TARGET=
67
     * Host: cas.example.com
68
     * Content-Length: 491
69
     * Content-Type: text/xml
70
     *
71
     * @param   Request  $request
72
     * @param   string   $TARGET  URL encoded service identifier of the back-end service.
73
     *
74
     * @throws \RuntimeException
75
     * @return XmlResponse
76
     * @link https://apereo.github.io/cas/7.1.x/protocol/CAS-Protocol-Specification.html#42-samlvalidate-cas-30
77
     */
78
    public function samlValidate(
79
        Request $request,
80
        #[MapQueryParameter] string $TARGET,
81
    ): XmlResponse {
82
        // From SAML2\SOAP::receive()
83
        $postBody = $request->getContent();
84
        if (empty($postBody)) {
85
            throw new \RuntimeException('samlValidate expects a soap body.');
86
        }
87
88
        // SAML request values
89
        //
90
        // samlp:Request
91
        //  - RequestID [REQUIRED] - unique identifier for the request
92
        //  - IssueInstant [REQUIRED] - timestamp of the request
93
        // samlp:AssertionArtifact [REQUIRED] - the valid CAS Service
94
95
        $documentBody = DOMDocumentFactory::fromString($postBody);
96
        $envelope = Envelope::fromXML($documentBody->documentElement);
97
        foreach ($envelope->getBody()->getElements() as $element) {
98
            // Request Element
99
            $samlpRequestParsed = SamlRequest::fromXML($element->getXML());
100
101
            // Assertion Artifact Element
102
            $assertionArtifactParsed = $samlpRequestParsed->getRequest()[0];
103
            if (empty($assertionArtifactParsed->getContent())) {
104
                throw new \RuntimeException('Missing ticketId in AssertionArtifact');
105
            }
106
        }
107
108
        $ticketId = $assertionArtifactParsed?->getContent() ?? '';
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $assertionArtifactParsed seems to be defined by a foreach iteration on line 97. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
109
        Logger::debug('samlvalidate: Checking ticket ' . $ticketId);
110
111
        try {
112
            // validateAndDeleteTicket might throw a CasException. In order to avoid third party modules
113
            // dependencies, we will catch and rethrow the Exception.
114
            $ticket = $this->ticketValidator->validateAndDeleteTicket($ticketId, $TARGET);
115
        } catch (\Exception $e) {
116
            throw new \RuntimeException($e->getMessage());
117
        }
118
        if (!\is_array($ticket)) {
0 ignored issues
show
introduced by
The condition is_array($ticket) is always true.
Loading history...
119
            throw new \RuntimeException('Error loading ticket');
120
        }
121
122
        $response = $this->validateResponder->convertToSaml($ticket);
123
        $soap     = $this->validateResponder->wrapInSoap($response);
124
125
        return new XmlResponse(
126
            (string)$soap,
127
            Response::HTTP_OK,
128
        );
129
    }
130
}
131