LogoutRequest   A
last analyzed

Complexity

Total Complexity 11

Size/Duplication

Total Lines 196
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 11
eloc 68
dl 0
loc 196
rs 10
c 0
b 0
f 0

6 Methods

Rating   Name   Duplication   Size   Complexity  
A toUnsignedXML() 0 21 4
A getReason() 0 3 1
A fromXML() 0 73 3
A __construct() 0 19 1
A getNotOnOrAfter() 0 3 1
A getSessionIndexes() 0 3 1
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\Constants as C;
11
use SimpleSAML\SAML2\Exception\Protocol\RequestVersionTooHighException;
12
use SimpleSAML\SAML2\Exception\Protocol\RequestVersionTooLowException;
13
use SimpleSAML\SAML2\Exception\ProtocolViolationException;
14
use SimpleSAML\SAML2\XML\IdentifierTrait;
15
use SimpleSAML\SAML2\XML\saml\AbstractBaseID;
16
use SimpleSAML\SAML2\XML\saml\EncryptedID;
17
use SimpleSAML\SAML2\XML\saml\IdentifierInterface;
18
use SimpleSAML\SAML2\XML\saml\Issuer;
19
use SimpleSAML\SAML2\XML\saml\NameID;
20
use SimpleSAML\XML\Exception\InvalidDOMElementException;
21
use SimpleSAML\XML\Exception\MissingElementException;
22
use SimpleSAML\XML\Exception\TooManyElementsException;
23
use SimpleSAML\XML\SchemaValidatableElementInterface;
24
use SimpleSAML\XML\SchemaValidatableElementTrait;
25
use SimpleSAML\XMLSecurity\XML\ds\Signature;
26
27
use function array_pop;
28
29
/**
30
 * Class for SAML 2 logout request messages.
31
 *
32
 * @package simplesamlphp/saml2
33
 */
34
final class LogoutRequest extends AbstractRequest implements SchemaValidatableElementInterface
35
{
36
    use IdentifierTrait;
37
    use SchemaValidatableElementTrait;
38
39
40
    /**
41
     * Constructor for SAML 2 AttributeQuery.
42
     *
43
     * @param \SimpleSAML\SAML2\XML\saml\IdentifierInterface $identifier
44
     * @param \DateTimeImmutable $issueInstant
45
     * @param \DateTimeImmutable|null $notOnOrAfter
46
     * @param string|null $reason
47
     * @param \SimpleSAML\SAML2\XML\samlp\SessionIndex[] $sessionIndexes
48
     * @param \SimpleSAML\SAML2\XML\saml\Issuer|null $issuer
49
     * @param string|null $id
50
     * @param string $version
51
     * @param string|null $destination
52
     * @param string|null $consent
53
     * @param \SimpleSAML\SAML2\XML\samlp\Extensions $extensions
54
     * @throws \Exception
55
     */
56
    public function __construct(
57
        IdentifierInterface $identifier,
58
        DateTimeImmutable $issueInstant,
59
        protected ?DateTimeImmutable $notOnOrAfter = null,
60
        protected ?string $reason = null,
61
        protected array $sessionIndexes = [],
62
        ?Issuer $issuer = null,
63
        ?string $id = null,
64
        string $version = '2.0',
65
        ?string $destination = null,
66
        ?string $consent = null,
67
        ?Extensions $extensions = null,
68
    ) {
69
        Assert::maxCount($sessionIndexes, C::UNBOUNDED_LIMIT);
70
        Assert::allIsInstanceOf($sessionIndexes, SessionIndex::class);
71
72
        parent::__construct($issuer, $id, $version, $issueInstant, $destination, $consent, $extensions);
73
74
        $this->setIdentifier($identifier);
75
    }
76
77
78
    /**
79
     * Retrieve the expiration time of this request.
80
     *
81
     * @return \DateTimeImmutable|null The expiration time of this request.
82
     */
83
    public function getNotOnOrAfter(): ?DateTimeImmutable
84
    {
85
        return $this->notOnOrAfter;
86
    }
87
88
89
    /**
90
     * Retrieve the reason for this request.
91
     *
92
     * @return string|null The reason for this request.
93
     */
94
    public function getReason(): ?string
95
    {
96
        return $this->reason;
97
    }
98
99
100
    /**
101
     * Retrieve the SessionIndexes of the sessions that should be terminated.
102
     *
103
     * @return \SimpleSAML\SAML2\XML\samlp\SessionIndex[]
104
     *   The SessionIndexes, or an empty array if all sessions should be terminated.
105
     */
106
    public function getSessionIndexes(): array
107
    {
108
        return $this->sessionIndexes;
109
    }
110
111
112
    /**
113
     * Convert XML into a LogoutRequest
114
     *
115
     * @param \DOMElement $xml The XML element we should load
116
     * @return static
117
     *
118
     * @throws \SimpleSAML\XML\Exception\InvalidDOMElementException
119
     *   if the qualified name of the supplied element is wrong
120
     * @throws \SimpleSAML\XML\Exception\MissingAttributeException
121
     *   if the supplied element is missing one of the mandatory attributes
122
     * @throws \SimpleSAML\XML\Exception\MissingElementException
123
     *   if one of the mandatory child-elements is missing
124
     * @throws \SimpleSAML\XML\Exception\TooManyElementsException
125
     *   if too many child-elements of a type are specified
126
     */
127
    public static function fromXML(DOMElement $xml): static
128
    {
129
        Assert::same($xml->localName, 'LogoutRequest', InvalidDOMElementException::class);
130
        Assert::same($xml->namespaceURI, LogoutRequest::NS, InvalidDOMElementException::class);
131
132
        $version = self::getAttribute($xml, 'Version');
133
        Assert::true(version_compare('2.0', $version, '<='), RequestVersionTooLowException::class);
134
        Assert::true(version_compare('2.0', $version, '>='), RequestVersionTooHighException::class);
135
136
        $id = self::getAttribute($xml, 'ID');
137
        Assert::validNCName($id); // Covers the empty string
138
139
        $issueInstant = self::getAttribute($xml, 'IssueInstant');
140
        // Strip sub-seconds - See paragraph 1.3.3 of SAML core specifications
141
        $issueInstant = preg_replace('/([.][0-9]+Z)$/', 'Z', $issueInstant, 1);
142
143
        Assert::validDateTime($issueInstant, ProtocolViolationException::class);
144
        $issueInstant = new DateTimeImmutable($issueInstant);
145
146
        $notOnOrAfter = self::getOptionalAttribute($xml, 'NotOnOrAfter', null);
147
        if ($notOnOrAfter !== null) {
148
            // Strip sub-seconds - See paragraph 1.3.3 of SAML core specifications
149
            $notOnOrAfter = preg_replace('/([.][0-9]+Z)$/', 'Z', $notOnOrAfter, 1);
150
151
            Assert::validDateTime($notOnOrAfter, ProtocolViolationException::class);
152
            $notOnOrAfter = new DateTimeImmutable($notOnOrAfter);
153
        }
154
155
        $issuer = Issuer::getChildrenOfClass($xml);
156
        Assert::countBetween($issuer, 0, 1);
157
158
        $extensions = Extensions::getChildrenOfClass($xml);
159
        Assert::maxCount(
160
            $extensions,
161
            1,
162
            'Only one saml:Extensions element is allowed.',
163
            TooManyElementsException::class,
164
        );
165
166
        $identifier = self::getIdentifierFromXML($xml);
167
        Assert::notNull(
168
            $identifier,
169
            'Missing <saml:NameID>, <saml:BaseID> or <saml:EncryptedID> in <samlp:LogoutRequest>.',
170
            MissingElementException::class,
171
        );
172
        Assert::isInstanceOfAny($identifier, [AbstractBaseID::class, NameID::class, EncryptedID::class]);
173
174
        $signature = Signature::getChildrenOfClass($xml);
175
        Assert::maxCount($signature, 1, 'Only one ds:Signature element is allowed.');
176
177
        $sessionIndex = SessionIndex::getChildrenOfClass($xml);
178
179
        $request = new static(
180
            $identifier,
181
            $issueInstant,
182
            $notOnOrAfter,
183
            self::getOptionalAttribute($xml, 'Reason', null),
184
            $sessionIndex,
185
            array_pop($issuer),
186
            $id,
187
            $version,
188
            self::getOptionalAttribute($xml, 'Destination', null),
189
            self::getOptionalAttribute($xml, 'Consent', null),
190
            array_pop($extensions),
191
        );
192
193
        if (!empty($signature)) {
194
            $request->setSignature($signature[0]);
195
            $request->messageContainedSignatureUponConstruction = true;
196
            $request->setXML($xml);
197
        }
198
199
        return $request;
200
    }
201
202
203
    /**
204
     * Convert this message to an unsigned XML document.
205
     * This method does not sign the resulting XML document.
206
     *
207
     * @return \DOMElement The root element of the DOM tree
208
     */
209
    protected function toUnsignedXML(?DOMElement $parent = null): DOMElement
210
    {
211
        $e = parent::toUnsignedXML($parent);
212
213
        if ($this->getNotOnOrAfter() !== null) {
214
            $e->setAttribute('NotOnOrAfter', $this->getNotOnOrAfter()->format(C::DATETIME_FORMAT));
215
        }
216
217
        if ($this->getReason() !== null) {
218
            $e->setAttribute('Reason', $this->getReason());
219
        }
220
221
        /** @var \SimpleSAML\XML\SerializableElementInterface $identifier */
222
        $identifier = $this->getIdentifier();
223
        $identifier->toXML($e);
224
225
        foreach ($this->getSessionIndexes() as $sessionIndex) {
226
            $sessionIndex->toXML($e);
227
        }
228
229
        return $e;
230
    }
231
}
232