Passed
Pull Request — master (#226)
by Jaime Pérez
03:17
created

LogoutRequest::toXML()   A

Complexity

Conditions 4
Paths 8

Size

Total Lines 21
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

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

228
        $this->identifier->/** @scrutinizer ignore-call */ 
229
                           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...
229
230
        foreach ($this->sessionIndexes as $sessionIndex) {
231
            $e->appendChild(
232
                $e->ownerDocument->createElementNS(AbstractSamlpElement::NS, 'samlp:SessionIndex', $sessionIndex)
233
            );
234
        }
235
236
        return $this->signElement($e);
237
    }
238
}
239