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

Cas30Controller::samlValidate()   B

Complexity

Conditions 8
Paths 7

Size

Total Lines 58
Code Lines 30

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 30
c 1
b 0
f 0
dl 0
loc 58
rs 8.1954
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 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 Symfony\Component\HttpFoundation\Request;
15
use Symfony\Component\HttpFoundation\Response;
16
use Symfony\Component\HttpKernel\Attribute\AsController;
17
use Symfony\Component\HttpKernel\Attribute\MapQueryParameter;
18
19
#[AsController]
20
class Cas30Controller
21
{
22
    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...
23
24
    /** @var Logger */
25
    protected Logger $logger;
26
27
    /** @var Configuration */
28
    protected Configuration $casConfig;
29
30
    /** @var Cas20 */
31
    protected Cas20 $cas20Protocol;
32
33
    /** @var TicketValidator */
34
    protected TicketValidator $ticketValidator;
35
36
    /** @var SamlValidateResponder */
37
    protected SamlValidateResponder $validateResponder;
38
39
    /**
40
     * @param   Configuration         $sspConfig
41
     * @param   Configuration|null    $casConfig
42
     * @param   TicketValidator|null  $ticketValidator
43
     *
44
     * @throws \Exception
45
     */
46
    public function __construct(
47
        private readonly Configuration $sspConfig,
48
        Configuration $casConfig = null,
49
        TicketValidator $ticketValidator = null,
50
    ) {
51
        // We are using this work around in order to bypass Symfony's autowiring for cas configuration. Since
52
        // the configuration class is the same, it loads the ssp configuration twice. Still, we need the constructor
53
        // argument in order to facilitate testin.
54
        $this->casConfig = ($casConfig === null || $casConfig === $sspConfig)
55
            ? Configuration::getConfig('module_casserver.php') : $casConfig;
56
        $this->cas20Protocol = new Cas20($this->casConfig);
57
        $this->ticketValidator   = $ticketValidator ?? new TicketValidator($this->casConfig);
58
        $this->validateResponder = new SamlValidateResponder();
59
    }
60
61
62
    /**
63
     * POST /casserver/samlValidate?TARGET=
64
     * Host: cas.example.com
65
     * Content-Length: 491
66
     * Content-Type: text/xml
67
     *
68
     * @param   Request  $request
69
     * @param   string   $TARGET  URL encoded service identifier of the back-end service.
70
     *
71
     * @throws \RuntimeException
72
     * @return XmlResponse
73
     * @link https://apereo.github.io/cas/7.1.x/protocol/CAS-Protocol-Specification.html#42-samlvalidate-cas-30
74
     */
75
    public function samlValidate(
76
        Request $request,
77
        #[MapQueryParameter] string $TARGET,
78
    ): XmlResponse {
79
        // From SAML2\SOAP::receive()
80
        $postBody = $request->getContent();
81
        if (empty($postBody)) {
82
            throw new \RuntimeException('samlValidate expects a soap body.');
83
        }
84
85
        // SAML request values
86
        //
87
        // samlp:Request
88
        //  - RequestID [REQUIRED] - unique identifier for the request
89
        //  - IssueInstant [REQUIRED] - timestamp of the request
90
        // samlp:AssertionArtifact [REQUIRED] - the valid CAS Service
91
92
        $ticketParser = xml_parser_create();
93
        xml_parser_set_option($ticketParser, XML_OPTION_CASE_FOLDING, 0);
94
        xml_parser_set_option($ticketParser, XML_OPTION_SKIP_WHITE, 1);
95
        xml_parse_into_struct($ticketParser, $postBody, $values, $tags);
96
        xml_parser_free($ticketParser);
97
98
        // Check for the required saml attributes
99
        $samlRequestAttributes = $values[ $tags['samlp:Request'][0] ]['attributes'];
100
        if (!isset($samlRequestAttributes['RequestID'])) {
101
            throw new \RuntimeException('Missing RequestID samlp:Request attribute.');
102
        } elseif (!isset($samlRequestAttributes['IssueInstant'])) {
103
            throw new \RuntimeException('Missing IssueInstant samlp:Request attribute.');
104
        }
105
106
        if (
107
            !isset($tags['samlp:AssertionArtifact'])
108
            || empty($values[$tags['samlp:AssertionArtifact'][0]]['value'])
109
        ) {
110
            throw new \RuntimeException('Missing ticketId in AssertionArtifact');
111
        }
112
113
        $ticketId = $values[$tags['samlp:AssertionArtifact'][0]]['value'];
114
        Logger::debug('samlvalidate: Checking ticket ' . $ticketId);
115
116
        try {
117
            // validateAndDeleteTicket might throw a CasException. In order to avoid third party modules
118
            // dependencies, we will catch and rethrow the Exception.
119
            $ticket = $this->ticketValidator->validateAndDeleteTicket($ticketId, $TARGET);
120
        } catch (\Exception $e) {
121
            throw new \RuntimeException($e->getMessage());
122
        }
123
        if (!\is_array($ticket)) {
0 ignored issues
show
introduced by
The condition is_array($ticket) is always true.
Loading history...
124
            throw new \RuntimeException('Error loading ticket');
125
        }
126
127
        $response = $this->validateResponder->convertToSaml($ticket);
128
        $soap     = $this->validateResponder->wrapInSoap($response);
129
130
        return new XmlResponse(
131
            (string)$soap,
132
            Response::HTTP_OK,
133
        );
134
    }
135
}
136