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

LogoutRequest::toUnsignedXML()   A

Complexity

Conditions 4
Paths 8

Size

Total Lines 21
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

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

250
        $this->identifier->/** @scrutinizer ignore-call */ 
251
                           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...
251
252
        foreach ($this->sessionIndexes as $sessionIndex) {
253
            $sessionIndex->toXML($e);
254
        }
255
256
        return $e;
257
    }
258
}
259