Passed
Push — master ( 64d78c...9221fc )
by Tim
02:38
created

Response::toUnsignedXML()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 4
nc 2
nop 1
dl 0
loc 9
rs 10
c 1
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace SimpleSAML\SAML2\XML\samlp;
6
7
use DateTimeImmutable;
8
use DOMElement;
9
use SimpleSAML\Assert\Assert;
10
use SimpleSAML\SAML2\Assert\Assert as SAMLAssert;
11
use SimpleSAML\SAML2\Constants as C;
12
use SimpleSAML\SAML2\Exception\Protocol\RequestVersionTooHighException;
13
use SimpleSAML\SAML2\Exception\Protocol\RequestVersionTooLowException;
14
use SimpleSAML\SAML2\Exception\ProtocolViolationException;
15
use SimpleSAML\SAML2\XML\saml\Assertion;
16
use SimpleSAML\SAML2\XML\saml\EncryptedAssertion;
17
use SimpleSAML\SAML2\XML\saml\Issuer;
18
use SimpleSAML\XML\Exception\InvalidDOMElementException;
19
use SimpleSAML\XML\Exception\MissingElementException;
20
use SimpleSAML\XML\Exception\TooManyElementsException;
21
use SimpleSAML\XMLSecurity\XML\ds\Signature;
22
23
use function array_merge;
24
use function array_pop;
25
26
/**
27
 * Class for SAML 2 Response messages.
28
 *
29
 * @package simplesamlphp/saml2
30
 */
31
class Response extends AbstractStatusResponse
32
{
33
    /**
34
     * Constructor for SAML 2 response messages.
35
     *
36
     * @param \SimpleSAML\SAML2\XML\samlp\Status $status
37
     * @param \DateTimeImmutable $issueInstant
38
     * @param \SimpleSAML\SAML2\XML\saml\Issuer|null $issuer
39
     * @param string|null $id
40
     * @param string $version
41
     * @param string $inResponseTo
42
     * @param string|null $destination
43
     * @param string|null $consent
44
     * @param \SimpleSAML\SAML2\XML\samlp\Extensions $extensions
45
     * @param (\SimpleSAML\SAML2\XML\saml\Assertion|\SimpleSAML\SAML2\XML\saml\EncryptedAssertion)[] $assertions
46
     */
47
    final public function __construct(
48
        Status $status,
49
        DateTimeImmutable $issueInstant,
50
        ?Issuer $issuer = null,
51
        ?string $id = null,
52
        string $version = '2.0',
53
        ?string $inResponseTo = null,
54
        ?string $destination = null,
55
        ?string $consent = null,
56
        ?Extensions $extensions = null,
57
        protected array $assertions = [],
58
    ) {
59
        Assert::maxCount($assertions, C::UNBOUNDED_LIMIT);
60
        Assert::allIsInstanceOfAny($assertions, [Assertion::class, EncryptedAssertion::class]);
61
62
        parent::__construct(
63
            $status,
64
            $issueInstant,
65
            $issuer,
66
            $id,
67
            $version,
68
            $inResponseTo,
69
            $destination,
70
            $consent,
71
            $extensions,
72
        );
73
    }
74
75
76
    /**
77
     * Retrieve the assertions in this response.
78
     *
79
     * @return \SimpleSAML\SAML2\XML\saml\Assertion[]|\SimpleSAML\SAML2\XML\saml\EncryptedAssertion[]
80
     */
81
    public function getAssertions(): array
82
    {
83
        return $this->assertions;
84
    }
85
86
87
    /**
88
     * Convert XML into a Response element.
89
     *
90
     * @param \DOMElement $xml The input message.
91
     * @return static
92
     *
93
     * @throws \SimpleSAML\XML\Exception\InvalidDOMElementException
94
     *   if the qualified name of the supplied element is wrong
95
     * @throws \SimpleSAML\XML\Exception\MissingAttributeException
96
     *   if the supplied element is missing one of the mandatory attributes
97
     * @throws \SimpleSAML\XML\Exception\MissingElementException
98
     *   if one of the mandatory child-elements is missing
99
     */
100
    public static function fromXML(DOMElement $xml): static
101
    {
102
        Assert::same($xml->localName, 'Response', InvalidDOMElementException::class);
103
        Assert::same($xml->namespaceURI, Response::NS, InvalidDOMElementException::class);
104
105
        $version = self::getAttribute($xml, 'Version');
106
        Assert::true(version_compare('2.0', $version, '<='), RequestVersionTooLowException::class);
107
        Assert::true(version_compare('2.0', $version, '>='), RequestVersionTooHighException::class);
108
109
        $signature = Signature::getChildrenOfClass($xml);
110
        Assert::maxCount($signature, 1, 'Only one ds:Signature element is allowed.', TooManyElementsException::class);
111
112
        $id = self::getAttribute($xml, 'ID');
113
        Assert::validNCName($id); // Covers the empty string
114
115
        $inResponseTo = self::getOptionalAttribute($xml, 'InResponseTo', null);
116
        $destination = self::getOptionalAttribute($xml, 'Destination', null);
117
        $consent = self::getOptionalAttribute($xml, 'Consent', null);
118
119
        $issueInstant = self::getAttribute($xml, 'IssueInstant');
120
        // Strip sub-seconds - See paragraph 1.3.3 of SAML core specifications
121
        $issueInstant = preg_replace('/([.][0-9]+Z)$/', 'Z', $issueInstant, 1);
122
123
        SAMLAssert::validDateTime($issueInstant, ProtocolViolationException::class);
124
        $issueInstant = new DateTimeImmutable($issueInstant);
125
126
        $issuer = Issuer::getChildrenOfClass($xml);
127
        Assert::countBetween($issuer, 0, 1);
128
129
        $status = Status::getChildrenOfClass($xml);
130
        Assert::minCount($status, 1, MissingElementException::class);
131
        Assert::maxCount($status, 1, TooManyElementsException::class);
132
133
        $extensions = Extensions::getChildrenOfClass($xml);
134
        Assert::maxCount(
135
            $extensions,
136
            1,
137
            'Only one saml:Extensions element is allowed.',
138
            TooManyElementsException::class,
139
        );
140
141
        $response = new static(
142
            array_pop($status),
143
            $issueInstant,
144
            empty($issuer) ? null : array_pop($issuer),
145
            $id,
146
            $version,
147
            $inResponseTo,
148
            $destination,
149
            $consent,
150
            empty($extensions) ? null : array_pop($extensions),
151
            array_merge(Assertion::getChildrenOfClass($xml), EncryptedAssertion::getChildrenOfClass($xml)),
152
        );
153
154
        if (!empty($signature)) {
155
            $response->setSignature($signature[0]);
156
            $response->messageContainedSignatureUponConstruction = true;
157
            $response->setXML($xml);
158
        }
159
160
        return $response;
161
    }
162
163
164
    /**
165
     * Convert the response message to an XML element.
166
     *
167
     * @return \DOMElement This response.
168
     */
169
    protected function toUnsignedXML(?DOMElement $parent = null): DOMElement
170
    {
171
        $e = parent::toUnsignedXML($parent);
172
173
        foreach ($this->getAssertions() as $assertion) {
174
            $assertion->toXML($e);
175
        }
176
177
        return $e;
178
    }
179
}
180