Passed
Pull Request — master (#280)
by Tim
02:30
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 DOMElement;
8
use SimpleSAML\Assert\Assert;
9
use SimpleSAML\SAML2\Constants;
10
use SimpleSAML\SAML2\Exception\ProtocolViolationException;
11
use SimpleSAML\SAML2\Utils\XPath;
12
use SimpleSAML\SAML2\XML\saml\Assertion;
13
use SimpleSAML\SAML2\XML\saml\EncryptedAssertion;
14
use SimpleSAML\SAML2\XML\saml\Issuer;
15
use SimpleSAML\XML\Exception\InvalidDOMElementException;
16
use SimpleSAML\XML\Exception\MissingElementException;
17
use SimpleSAML\XML\Exception\TooManyElementsException;
18
use SimpleSAML\XML\Utils as XMLUtils;
19
use SimpleSAML\XMLSecurity\Utils\Security;
20
use SimpleSAML\XMLSecurity\XML\ds\Signature;
21
22
use function array_pop;
23
24
/**
25
 * Class for SAML 2 Response messages.
26
 *
27
 * @package simplesamlphp/saml2
28
 */
29
class Response extends AbstractStatusResponse
30
{
31
    /**
32
     * The assertions in this response.
33
     *
34
     * @var (\SimpleSAML\SAML2\XML\saml\Assertion|\SimpleSAML\SAML2\XML\saml\EncryptedAssertion)[]
35
     */
36
    protected array $assertions = [];
37
38
39
    /**
40
     * Constructor for SAML 2 response messages.
41
     *
42
     * @param \SimpleSAML\SAML2\XML\samlp\Status $status
43
     * @param \SimpleSAML\SAML2\XML\saml\Issuer $issuer
44
     * @param string $id
45
     * @param int $issueInstant
46
     * @param string $inResponseTo
47
     * @param string|null $destination
48
     * @param string|null $consent
49
     * @param \SimpleSAML\SAML2\XML\samlp\Extensions $extensions
50
     * @param (\SimpleSAML\SAML2\XML\saml\Assertion|\SimpleSAML\SAML2\XML\saml\EncryptedAssertion)[] $assertions
51
     */
52
    public function __construct(
53
        Status $status,
54
        ?Issuer $issuer = null,
55
        ?string $id = null,
56
        ?int $issueInstant = null,
57
        ?string $inResponseTo = null,
58
        ?string $destination = null,
59
        ?string $consent = null,
60
        ?Extensions $extensions = null,
61
        array $assertions = []
62
    ) {
63
        parent::__construct(
64
            $status,
65
            $issuer,
66
            $id,
67
            $issueInstant,
68
            $inResponseTo,
69
            $destination,
70
            $consent,
71
            $extensions
72
        );
73
74
        $this->setAssertions($assertions);
75
    }
76
77
78
    /**
79
     * Retrieve the assertions in this response.
80
     *
81
     * @return \SimpleSAML\SAML2\XML\saml\Assertion[]|\SimpleSAML\SAML2\XML\saml\EncryptedAssertion[]
82
     */
83
    public function getAssertions(): array
84
    {
85
        return $this->assertions;
86
    }
87
88
89
    /**
90
     * Set the assertions that should be included in this response.
91
     *
92
     * @param (\SimpleSAML\SAML2\XML\saml\Assertion|\SimpleSAML\SAML2\XML\saml\EncryptedAssertion)[] $assertions The assertions.
93
     */
94
    protected function setAssertions(array $assertions): void
95
    {
96
        Assert::allIsInstanceOfAny($assertions, [Assertion::class, EncryptedAssertion::class]);
97
98
        $this->assertions = $assertions;
99
    }
100
101
102
    /**
103
     * Convert XML into a Response element.
104
     *
105
     * @param \DOMElement $xml The input message.
106
     * @return self
107
     *
108
     * @throws \SimpleSAML\XML\Exception\InvalidDOMElementException if the qualified name of the supplied element is wrong
109
     * @throws \SimpleSAML\XML\Exception\MissingAttributeException if the supplied element is missing one of the mandatory attributes
110
     * @throws \SimpleSAML\XML\Exception\MissingElementException if one of the mandatory child-elements is missing
111
     */
112
    public static function fromXML(DOMElement $xml): object
113
    {
114
        Assert::same($xml->localName, 'Response', InvalidDOMElementException::class);
115
        Assert::same($xml->namespaceURI, Response::NS, InvalidDOMElementException::class);
116
        Assert::same('2.0', self::getAttribute($xml, 'Version'));
117
118
        $signature = Signature::getChildrenOfClass($xml);
119
        Assert::maxCount($signature, 1, 'Only one ds:Signature element is allowed.', TooManyElementsException::class);
120
121
        $id = self::getAttribute($xml, 'ID');
122
        $inResponseTo = self::getAttribute($xml, 'InResponseTo', null);
123
        $destination = self::getAttribute($xml, 'Destination', null);
124
        $consent = self::getAttribute($xml, 'Consent', null);
125
126
        /** @psalm-suppress PossiblyNullArgument */
127
        $issueInstant = self::getAttribute($xml, 'IssueInstant');
128
        Assert::validDateTimeZulu($issueInstant, ProtocolViolationException::class);
129
        $issueInstant = XMLUtils::xsDateTimeToTimestamp($issueInstant);
130
131
        $issuer = Issuer::getChildrenOfClass($xml);
132
        Assert::countBetween($issuer, 0, 1);
133
134
        $status = Status::getChildrenOfClass($xml);
135
        Assert::minCount($status, 1, MissingElementException::class);
136
        Assert::maxCount($status, 1, TooManyElementsException::class);
137
138
        $extensions = Extensions::getChildrenOfClass($xml);
139
        Assert::maxCount($extensions, 1, 'Only one saml:Extensions element is allowed.', TooManyElementsException::class);
140
141
        $assertions = [];
142
        foreach ($xml->childNodes as $node) {
143
            if ($node->namespaceURI !== Constants::NS_SAML) {
144
                continue;
145
            } elseif (!($node instanceof DOMElement)) {
146
                continue;
147
            }
148
149
            if ($node->localName === 'Assertion') {
150
                $assertions[] = Assertion::fromXML($node);
151
            } elseif ($node->localName === 'EncryptedAssertion') {
152
                $assertions[] = EncryptedAssertion::fromXML($node);
153
            }
154
        }
155
156
        $response = new self(
157
            array_pop($status),
158
            empty($issuer) ? null : array_pop($issuer),
159
            $id,
160
            $issueInstant,
161
            $inResponseTo,
162
            $destination,
163
            $consent,
164
            empty($extensions) ? null : array_pop($extensions),
165
            $assertions
166
        );
167
168
        if (!empty($signature)) {
169
            $response->setSignature($signature[0]);
170
            $response->messageContainedSignatureUponConstruction = true;
171
        }
172
173
        $response->setXML($xml);
174
        return $response;
175
    }
176
177
178
    /**
179
     * Convert the response message to an XML element.
180
     *
181
     * @return \DOMElement This response.
182
     */
183
    protected function toUnsignedXML(?DOMElement $parent = null): DOMElement
184
    {
185
        $e = parent::toUnsignedXML($parent);
186
187
        foreach ($this->assertions as $assertion) {
188
            $assertion->toXML($e);
189
        }
190
191
        return $e;
192
    }
193
}
194