ArtifactResponse::getMessage()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
rs 10
c 0
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\SAML2\Assert\Assert;
10
use SimpleSAML\SAML2\Exception\Protocol\RequestVersionTooHighException;
11
use SimpleSAML\SAML2\Exception\Protocol\RequestVersionTooLowException;
12
use SimpleSAML\SAML2\Exception\ProtocolViolationException;
13
use SimpleSAML\SAML2\Utils\XPath;
14
use SimpleSAML\SAML2\XML\saml\Issuer;
15
use SimpleSAML\XML\Exception\InvalidDOMElementException;
16
use SimpleSAML\XML\Exception\TooManyElementsException;
17
use SimpleSAML\XML\SchemaValidatableElementInterface;
18
use SimpleSAML\XML\SchemaValidatableElementTrait;
19
use SimpleSAML\XMLSecurity\XML\ds\Signature;
20
21
use function array_pop;
22
use function preg_replace;
23
use function version_compare;
24
25
/**
26
 * The \SimpleSAML\SAML2\XML\samlp\ArtifactResponse,
27
 *  is the response to the \SimpleSAML\SAML2\XML\samlp\ArtifactResolve.
28
 *
29
 * @package simplesamlphp/saml2
30
 */
31
class ArtifactResponse extends AbstractStatusResponse implements SchemaValidatableElementInterface
32
{
33
    use SchemaValidatableElementTrait;
0 ignored issues
show
Bug introduced by
The trait SimpleSAML\XML\SchemaValidatableElementTrait requires the property $line which is not provided by SimpleSAML\SAML2\XML\samlp\ArtifactResponse.
Loading history...
34
35
    /**
36
     * Constructor for SAML 2 ArtifactResponse.
37
     *
38
     * @param \SimpleSAML\SAML2\XML\samlp\Status $status
39
     * @param \DateTimeImmutable $issueInstant
40
     * @param \SimpleSAML\SAML2\XML\saml\Issuer|null $issuer
41
     * @param string|null $id
42
     * @param string $version
43
     * @param string|null $inResponseTo
44
     * @param string|null $destination
45
     * @param string|null $consent
46
     * @param \SimpleSAML\SAML2\XML\samlp\Extensions|null $extensions
47
     * @param \SimpleSAML\SAML2\XML\samlp\AbstractMessage|null $message
48
     */
49
    final public function __construct(
50
        Status $status,
51
        DateTimeImmutable $issueInstant,
52
        ?Issuer $issuer = null,
53
        ?string $id = null,
54
        string $version = '2.0',
55
        ?string $inResponseTo = null,
56
        ?string $destination = null,
57
        ?string $consent = null,
58
        ?Extensions $extensions = null,
59
        protected ?AbstractMessage $message = null,
60
    ) {
61
        parent::__construct(
62
            $status,
63
            $issueInstant,
64
            $issuer,
65
            $id,
66
            $version,
67
            $inResponseTo,
68
            $destination,
69
            $consent,
70
            $extensions,
71
        );
72
    }
73
74
75
    /**
76
     * Collect the value of the any-property
77
     *
78
     * @return \SimpleSAML\SAML2\XML\samlp\AbstractMessage|null
79
     */
80
    public function getMessage(): ?AbstractMessage
81
    {
82
        return $this->message;
83
    }
84
85
86
    /**
87
     * Convert XML into an ArtifactResponse
88
     *
89
     * @param \DOMElement $xml
90
     * @return static
91
     *
92
     * @throws \SimpleSAML\XML\Exception\InvalidDOMElementException
93
     *   if the qualified name of the supplied element is wrong
94
     * @throws \SimpleSAML\XML\Exception\MissingAttributeException
95
     *   if the supplied element is missing one of the mandatory attributes
96
     */
97
    public static function fromXML(DOMElement $xml): static
98
    {
99
        Assert::same($xml->localName, 'ArtifactResponse', InvalidDOMElementException::class);
100
        Assert::same($xml->namespaceURI, ArtifactResponse::NS, InvalidDOMElementException::class);
101
102
        $version = self::getAttribute($xml, 'Version');
103
        Assert::true(version_compare('2.0', $version, '<='), RequestVersionTooLowException::class);
104
        Assert::true(version_compare('2.0', $version, '>='), RequestVersionTooHighException::class);
105
106
        $id = self::getAttribute($xml, 'ID');
107
        Assert::validNCName($id); // Covers the empty string
108
109
        $inResponseTo = self::getOptionalAttribute($xml, 'InResponseTo', null);
110
        $destination = self::getOptionalAttribute($xml, 'Destination', null);
111
        $consent = self::getOptionalAttribute($xml, 'Consent', null);
112
113
        $issueInstant = self::getAttribute($xml, 'IssueInstant');
114
        // Strip sub-seconds - See paragraph 1.3.3 of SAML core specifications
115
        $issueInstant = preg_replace('/([.][0-9]+Z)$/', 'Z', $issueInstant, 1);
116
117
        Assert::validDateTime($issueInstant, ProtocolViolationException::class);
118
        $issueInstant = new DateTimeImmutable($issueInstant);
119
120
        $issuer = Issuer::getChildrenOfClass($xml);
121
        Assert::countBetween($issuer, 0, 1);
122
123
        // find message; it should come last, after the Status-element
124
        $status = XPath::xpQuery($xml, './saml_protocol:Status', XPath::getXPath($xml));
125
        $status = $status[0];
126
        $message = null;
127
128
        /** @psalm-suppress RedundantCondition */
129
        for ($child = $status->nextSibling; $child !== null; $child = $child->nextSibling) {
130
            if ($child instanceof DOMElement) {
131
                $message = MessageFactory::fromXML($child);
132
                break;
133
            }
134
            /* Ignore comments and text nodes. */
135
        }
136
137
        $status = Status::getChildrenOfClass($xml);
138
        Assert::count($status, 1);
139
140
        $extensions = Extensions::getChildrenOfClass($xml);
141
        Assert::maxCount(
142
            $extensions,
143
            1,
144
            'Only one saml:Extensions element is allowed.',
145
            TooManyElementsException::class,
146
        );
147
148
        $signature = Signature::getChildrenOfClass($xml);
149
        Assert::maxCount(
150
            $signature,
151
            1,
152
            'Only one ds:Signature element is allowed.',
153
            TooManyElementsException::class,
154
        );
155
156
        $response = new static(
157
            array_pop($status),
158
            $issueInstant,
159
            empty($issuer) ? null : array_pop($issuer),
160
            $id,
161
            $version,
162
            $inResponseTo,
163
            $destination,
164
            $consent,
165
            empty($extensions) ? null : array_pop($extensions),
166
            $message,
167
        );
168
169
        if (!empty($signature)) {
170
            $response->setSignature($signature[0]);
171
            $response->setXML($xml);
172
        }
173
174
        return $response;
175
    }
176
177
178
    /**
179
     * Convert this message to an unsigned XML document.
180
     * This method does not sign the resulting XML document.
181
     *
182
     * @return \DOMElement The root element of the DOM tree
183
     */
184
    protected function toUnsignedXML(?DOMElement $parent = null): DOMElement
185
    {
186
        $e = parent::toUnsignedXML($parent);
187
188
        $this->getMessage()?->toXML($e);
189
190
        return $e;
191
    }
192
}
193