Passed
Pull Request — master (#280)
by Tim
02:26
created

LogoutRequest::toXML()   A

Complexity

Conditions 4
Paths 8

Size

Total Lines 21
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 4
eloc 9
nc 8
nop 1
dl 0
loc 21
rs 9.9666
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\Exception\Protocol\RequestVersionTooHighException;
10
use SimpleSAML\SAML2\Exception\Protocol\RequestVersionTooLowException;
11
use SimpleSAML\SAML2\Exception\ProtocolViolationException;
12
use SimpleSAML\SAML2\XML\IdentifierTrait;
13
use SimpleSAML\SAML2\XML\saml\IdentifierInterface;
14
use SimpleSAML\SAML2\XML\saml\BaseID;
15
use SimpleSAML\SAML2\XML\saml\EncryptedID;
16
use SimpleSAML\SAML2\XML\saml\NameID;
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\XML\Utils as XMLUtils;
22
use SimpleSAML\XMLSecurity\Key\PrivateKey;
23
use SimpleSAML\XMLSecurity\XML\ds\Signature;
24
25
use function array_pop;
26
use function gmdate;
27
28
/**
29
 * Class for SAML 2 logout request messages.
30
 *
31
 * @package simplesamlphp/saml2
32
 */
33
class LogoutRequest extends AbstractRequest
34
{
35
    use IdentifierTrait;
36
37
    /**
38
     * The expiration time of this request.
39
     *
40
     * @var int|null
41
     */
42
    protected ?int $notOnOrAfter = null;
43
44
    /**
45
     * The SessionIndexes of the sessions that should be terminated.
46
     *
47
     * @var \SimpleSAML\SAML2\XML\samlp\SessionIndex[]
48
     */
49
    protected array $sessionIndexes = [];
50
51
    /**
52
     * The optional reason for the logout, typically a URN
53
     * See \SimpleSAML\SAML2\Constants::LOGOUT_REASON_*
54
     * From the standard section 3.7.3: "other values MAY be agreed on between participants"
55
     *
56
     * @var string|null
57
     */
58
    protected ?string $reason = null;
59
60
61
    /**
62
     * Constructor for SAML 2 AttributeQuery.
63
     *
64
     * @param \SimpleSAML\SAML2\XML\saml\IdentifierInterface $identifier
65
     * @param int|null $notOnOrAfter
66
     * @param string|null $reason
67
     * @param \SimpleSAML\SAML2\XML\samlp\SessionIndex[] $sessionIndexes
68
     * @param \SimpleSAML\SAML2\XML\saml\Issuer|null $issuer
69
     * @param string|null $id
70
     * @param int|null $issueInstant
71
     * @param string|null $destination
72
     * @param string|null $consent
73
     * @param \SimpleSAML\SAML2\XML\samlp\Extensions $extensions
74
     * @throws \Exception
75
     */
76
    public function __construct(
77
        IdentifierInterface $identifier,
78
        ?int $notOnOrAfter = null,
79
        ?string $reason = null,
80
        array $sessionIndexes = [],
81
        ?Issuer $issuer = null,
82
        ?string $id = null,
83
        ?int $issueInstant = null,
84
        ?string $destination = null,
85
        ?string $consent = null,
86
        ?Extensions $extensions = null
87
    ) {
88
        parent::__construct($issuer, $id, $issueInstant, $destination, $consent, $extensions);
89
90
        $this->setIdentifier($identifier);
91
        $this->setNotOnOrAfter($notOnOrAfter);
92
        $this->setReason($reason);
93
        $this->setSessionIndexes($sessionIndexes);
94
    }
95
96
97
    /**
98
     * Retrieve the expiration time of this request.
99
     *
100
     * @return int|null The expiration time of this request.
101
     */
102
    public function getNotOnOrAfter(): ?int
103
    {
104
        return $this->notOnOrAfter;
105
    }
106
107
108
    /**
109
     * Set the expiration time of this request.
110
     *
111
     * @param int|null $notOnOrAfter The expiration time of this request.
112
     */
113
    public function setNotOnOrAfter(?int $notOnOrAfter = null): void
114
    {
115
        $this->notOnOrAfter = $notOnOrAfter;
116
    }
117
118
    /**
119
     * Retrieve the reason for this request.
120
     *
121
     * @return string|null The reason for this request.
122
     */
123
    public function getReason(): ?string
124
    {
125
        return $this->reason;
126
    }
127
128
129
    /**
130
     * Set the reason for this request.
131
     *
132
     * @param string|null $reason The optional reason for this request in URN format
133
     */
134
    public function setReason(?string $reason = null): void
135
    {
136
        $this->reason = $reason;
137
    }
138
139
140
    /**
141
     * Retrieve the SessionIndexes of the sessions that should be terminated.
142
     *
143
     * @return \SimpleSAML\SAML2\XML\samlp\SessionIndex[] The SessionIndexes, or an empty array if all sessions should be terminated.
144
     */
145
    public function getSessionIndexes(): array
146
    {
147
        return $this->sessionIndexes;
148
    }
149
150
151
    /**
152
     * Set the SessionIndexes of the sessions that should be terminated.
153
     *
154
     * @param \SimpleSAML\SAML2\XML\samlp\SessionIndex[] $sessionIndexes The SessionIndexes, or an empty array if all sessions should be terminated.
155
     */
156
    public function setSessionIndexes(array $sessionIndexes): void
157
    {
158
        Assert::allIsInstanceOf($sessionIndexes, SessionIndex::class);
159
        $this->sessionIndexes = $sessionIndexes;
160
    }
161
162
163
    /**
164
     * Convert XML into a LogoutRequest
165
     *
166
     * @param \DOMElement $xml The XML element we should load
167
     * @return \SimpleSAML\SAML2\XML\samlp\LogoutRequest
168
     *
169
     * @throws \SimpleSAML\XML\Exception\InvalidDOMElementException if the qualified name of the supplied element is wrong
170
     * @throws \SimpleSAML\XML\Exception\MissingAttributeException if the supplied element is missing one of the mandatory attributes
171
     * @throws \SimpleSAML\XML\Exception\MissingElementException if one of the mandatory child-elements is missing
172
     * @throws \SimpleSAML\XML\Exception\TooManyElementsException if too many child-elements of a type are specified
173
     */
174
    public static function fromXML(DOMElement $xml): object
175
    {
176
        Assert::same($xml->localName, 'LogoutRequest', InvalidDOMElementException::class);
177
        Assert::same($xml->namespaceURI, LogoutRequest::NS, InvalidDOMElementException::class);
178
179
        $version = self::getAttribute($xml, 'Version');
180
        Assert::true(version_compare('2.0', $version, '<='), RequestVersionTooLowException::class);
181
        Assert::true(version_compare('2.0', $version, '>='), RequestVersionTooHighException::class);
182
183
        $issueInstant = self::getAttribute($xml, 'IssueInstant');
184
        Assert::validDateTimeZulu($issueInstant, ProtocolViolationException::class);
185
        $issueInstant = XMLUtils::xsDateTimeToTimestamp($issueInstant);
186
187
        $notOnOrAfter = self::getAttribute($xml, 'NotOnOrAfter', null);
188
        if ($notOnOrAfter !== null) {
189
            Assert::validDateTimeZulu($notOnOrAfter, ProtocolViolationException::class);
190
            $notOnOrAfter = XMLUtils::xsDateTimeToTimestamp($notOnOrAfter);
191
        }
192
193
        $issuer = Issuer::getChildrenOfClass($xml);
194
        Assert::countBetween($issuer, 0, 1);
195
196
        $extensions = Extensions::getChildrenOfClass($xml);
197
        Assert::maxCount($extensions, 1, 'Only one saml:Extensions element is allowed.', TooManyElementsException::class);
198
199
        $identifier = self::getIdentifierFromXML($xml);
200
        Assert::notNull(
201
            $identifier,
202
            'Missing <saml:NameID>, <saml:BaseID> or <saml:EncryptedID> in <samlp:LogoutRequest>.',
203
            MissingElementException::class
204
        );
205
        Assert::isInstanceOfAny($identifier, [BaseID::class, NameID::class, EncryptedID::class]);
206
207
        $signature = Signature::getChildrenOfClass($xml);
208
        Assert::maxCount($signature, 1, 'Only one ds:Signature element is allowed.');
209
210
        $sessionIndex = SessionIndex::getChildrenOfClass($xml);
211
212
        $request = new self(
213
            $identifier,
214
            $notOnOrAfter,
215
            self::getAttribute($xml, 'Reason', null),
216
            $sessionIndex,
217
            array_pop($issuer),
218
            self::getAttribute($xml, 'ID'),
219
            $issueInstant,
220
            self::getAttribute($xml, 'Destination', null),
221
            self::getAttribute($xml, 'Consent', null),
222
            array_pop($extensions)
223
        );
224
225
        if (!empty($signature)) {
226
            $request->setSignature($signature[0]);
227
            $request->messageContainedSignatureUponConstruction = true;
228
        }
229
230
        $request->setXML($xml);
231
        return $request;
232
    }
233
234
235
    /**
236
     * Convert this message to an unsigned XML document.
237
     * This method does not sign the resulting XML document.
238
     *
239
     * @param \DOMElement|null $parent
240
     * @return \DOMElement The root element of the DOM tree
241
     */
242
    protected function toUnsignedXML(?DOMElement $parent = null): DOMElement
243
    {
244
        /** @psalm-var \DOMDocument $e->ownerDocument */
245
        $e = parent::toUnsignedXML($parent);
246
247
        if ($this->notOnOrAfter !== null) {
248
            $e->setAttribute('NotOnOrAfter', gmdate('Y-m-d\TH:i:s\Z', $this->notOnOrAfter));
249
        }
250
251
        if ($this->reason !== null) {
252
            $e->setAttribute('Reason', $this->reason);
253
        }
254
255
        /** @var \SimpleSAML\SAML2\XML\saml\IdentifierInterface $this->identifier */
256
        $this->identifier->toXML($e);
0 ignored issues
show
Bug introduced by
The method toXML() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

256
        $this->identifier->/** @scrutinizer ignore-call */ 
257
                           toXML($e);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
257
258
        foreach ($this->sessionIndexes as $sessionIndex) {
259
            $sessionIndex->toXML($e);
260
        }
261
262
        return $e;
263
    }
264
}
265