Passed
Push — master ( 40ad58...014371 )
by Tim
01:47
created

Cas20::isValidXmlName()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 2
eloc 5
c 2
b 0
f 0
nc 2
nop 1
dl 0
loc 7
rs 10
1
<?php
2
3
/*
4
 *    simpleSAMLphp-casserver is a CAS 1.0 and 2.0 compliant CAS server in the form of a simpleSAMLphp module
5
 *
6
 *    Copyright (C) 2013  Bjorn R. Jensen
7
 *
8
 *    This library is free software; you can redistribute it and/or
9
 *    modify it under the terms of the GNU Lesser General Public
10
 *    License as published by the Free Software Foundation; either
11
 *    version 2.1 of the License, or (at your option) any later version.
12
 *
13
 *    This library is distributed in the hope that it will be useful,
14
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
15
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16
 *    Lesser General Public License for more details.
17
 *
18
 *    You should have received a copy of the GNU Lesser General Public
19
 *    License along with this library; if not, write to the Free Software
20
 *    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
21
 *
22
 */
23
24
declare(strict_types=1);
25
26
namespace SimpleSAML\Module\casserver\Cas\Protocol;
27
28
use DOMDocument;
29
use DOMElement;
30
use DOMException;
31
use SimpleSAML\Configuration;
32
use SimpleSAML\Logger;
33
use SimpleSAML\XML\DOMDocumentFactory;
34
35
class Cas20
36
{
37
    /** @var bool $sendAttributes */
38
    private bool $sendAttributes;
39
40
    /** @var bool $base64EncodeAttributes */
41
    private bool $base64EncodeAttributes;
42
43
    /** @var string|null $base64IndicatorAttribute */
44
    private ?string $base64IndicatorAttribute;
45
46
    /** @var array $attributes */
47
    private array $attributes = [];
48
49
    /** @var string|null $proxyGrantingTicketIOU */
50
    private ?string $proxyGrantingTicketIOU = null;
51
52
53
    /**
54
     * @param \SimpleSAML\Configuration $config
55
     */
56
    public function __construct(Configuration $config)
57
    {
58
        $this->sendAttributes = $config->getValue('attributes', false);
59
        $this->base64EncodeAttributes = $config->getValue('base64attributes', false);
60
        $this->base64IndicatorAttribute = $config->getValue('base64_attributes_indicator_attribute', null);
61
    }
62
63
64
    /**
65
     * @param array $attributes
66
     */
67
    public function setAttributes(array $attributes): void
68
    {
69
        $this->attributes = $attributes;
70
    }
71
72
73
    /**
74
     * @return array
75
     */
76
    public function getAttributes(): array
77
    {
78
        return $this->attributes;
79
    }
80
81
82
    /**
83
     * @param string $proxyGrantingTicketIOU
84
     */
85
    public function setProxyGrantingTicketIOU(string $proxyGrantingTicketIOU): void
86
    {
87
        $this->proxyGrantingTicketIOU = $proxyGrantingTicketIOU;
88
    }
89
90
91
    /**
92
     * @return string|null
93
     */
94
    public function getProxyGrantingTicketIOU(): ?string
95
    {
96
        return $this->proxyGrantingTicketIOU;
97
    }
98
99
100
    /**
101
     * @param string $username
102
     * @return string
103
     */
104
    public function getValidateSuccessResponse(string $username): string
105
    {
106
        $xmlDocument = DOMDocumentFactory::create();
107
        $xmlDocument->formatOutput = true;
108
109
        $root = $xmlDocument->createElement("cas:serviceResponse");
110
        $root->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:cas', 'http://www.yale.edu/tp/cas');
111
112
        $usernameNode = $xmlDocument->createTextNode($username);
113
        $casUser = $xmlDocument->createElement('cas:user');
114
        $casUser->appendChild($usernameNode);
115
116
        $casSuccess = $xmlDocument->createElement('cas:authenticationSuccess');
117
        $casSuccess->appendChild($casUser);
118
119
        if (is_string($this->proxyGrantingTicketIOU)) {
120
            $iouNode = $xmlDocument->createTextNode($this->proxyGrantingTicketIOU);
121
            $iouElement = $xmlDocument->createElement("cas:proxyGrantingTicket");
122
            $iouElement->appendChild($iouNode);
123
            $casSuccess->appendChild($iouElement);
124
        }
125
126
        if ($this->sendAttributes && count($this->attributes) > 0) {
127
            $casAttributes = $xmlDocument->createElement('cas:attributes');
128
129
            foreach ($this->attributes as $name => $values) {
130
                // Fix the most common cause of invalid XML elements
131
                $_name = str_replace(':', '_', $name);
132
                if ($this->isValidXmlName($_name) === true) {
133
                    foreach ($values as $value) {
134
                        $casAttributes->appendChild(
135
                            $this->generateCas20Attribute($xmlDocument, $_name, $value)
136
                        );
137
                    }
138
                } else {
139
                    Logger::warning("Dom exception creating attribute '$_name'. Continuing without atrribute'");
140
                }
141
            }
142
143
            if (!is_null($this->base64IndicatorAttribute)) {
144
                $casAttributes->appendChild(
145
                    $this->generateCas20Attribute(
146
                        $xmlDocument,
147
                        $this->base64IndicatorAttribute,
148
                        $this->base64EncodeAttributes ? "true" : "false"
149
                    )
150
                );
151
            }
152
153
            $casSuccess->appendChild($casAttributes);
154
        }
155
156
        $root->appendChild($casSuccess);
157
        $xmlDocument->appendChild($root);
158
159
        return $xmlDocument->saveXML();
160
    }
161
162
163
    /**
164
     * @param string $errorCode
165
     * @param string $explanation
166
     * @return string
167
     */
168
    public function getValidateFailureResponse(string $errorCode, string $explanation): string
169
    {
170
        $xmlDocument = DOMDocumentFactory::create();
171
172
        $root = $xmlDocument->createElement("cas:serviceResponse");
173
        $root->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:cas', 'http://www.yale.edu/tp/cas');
174
175
        $casFailureCode = $xmlDocument->createAttribute('code');
176
        $casFailureCode->value = $errorCode;
177
178
        $casFailureNode = $xmlDocument->createTextNode($explanation);
179
        $casFailure = $xmlDocument->createElement('cas:authenticationFailure');
180
        $casFailure->appendChild($casFailureNode);
181
        $casFailure->appendChild($casFailureCode);
182
183
        $root->appendChild($casFailure);
184
185
        $xmlDocument->appendChild($root);
186
187
        return $xmlDocument->saveXML();
188
    }
189
190
191
    /**
192
     * @param string $proxyTicketId
193
     * @return string
194
     */
195
    public function getProxySuccessResponse(string $proxyTicketId): string
196
    {
197
        $xmlDocument = DOMDocumentFactory::create();
198
199
        $root = $xmlDocument->createElement("cas:serviceResponse");
200
        $root->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:cas', 'http://www.yale.edu/tp/cas');
201
202
        $casProxyTicketIdNode = $xmlDocument->createTextNode($proxyTicketId);
203
        $casProxyTicketId = $xmlDocument->createElement('cas:proxyTicket');
204
        $casProxyTicketId->appendChild($casProxyTicketIdNode);
205
206
        $casProxySuccess = $xmlDocument->createElement('cas:proxySuccess');
207
        $casProxySuccess->appendChild($casProxyTicketId);
208
209
        $root->appendChild($casProxySuccess);
210
        $xmlDocument->appendChild($root);
211
212
        return $xmlDocument->saveXML();
213
    }
214
215
216
    /**
217
     * @param string $errorCode
218
     * @param string $explanation
219
     * @return string
220
     */
221
    public function getProxyFailureResponse(string $errorCode, string $explanation): string
222
    {
223
        $xmlDocument = DOMDocumentFactory::create();
224
225
        $root = $xmlDocument->createElement("cas:serviceResponse");
226
        $root->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:cas', 'http://www.yale.edu/tp/cas');
227
228
        $casFailureCode = $xmlDocument->createAttribute('code');
229
        $casFailureCode->value = $errorCode;
230
231
        $casFailureNode = $xmlDocument->createTextNode($explanation);
232
        $casFailure = $xmlDocument->createElement('cas:proxyFailure');
233
        $casFailure->appendChild($casFailureNode);
234
        $casFailure->appendChild($casFailureCode);
235
236
        $root->appendChild($casFailure);
237
238
        $xmlDocument->appendChild($root);
239
240
        return $xmlDocument->saveXML();
241
    }
242
243
244
    /**
245
     * @param \DOMDocument $xmlDocument
246
     * @param string $attributeName
247
     * @param string $attributeValue
248
     * @return \DOMElement
249
     */
250
    private function generateCas20Attribute(
251
        DOMDocument $xmlDocument,
252
        string $attributeName,
253
        string $attributeValue
254
    ): DOMElement {
255
        $attributeValueNode = $xmlDocument->createTextNode($this->base64EncodeAttributes
256
            ? base64_encode($attributeValue)
257
            : $attributeValue);
258
259
        $attributeElement = $xmlDocument->createElement('cas:' . $attributeName);
260
261
        $attributeElement->appendChild($attributeValueNode);
262
263
        return $attributeElement;
264
    }
265
266
267
    /**
268
     * XML element names have a lot of rules and not every SAML attribute name can be converted.
269
     * Ref: https://www.w3.org/TR/REC-xml/#NT-NameChar
270
     * https://stackoverflow.com/q/2519845/54396
271
     * must only start with letter or underscore
272
     * cannot start with 'xml' (or maybe it can - stackoverflow commenters don't agree)
273
     * cannot contain a ':' since those are for namespaces
274
     * cannot contains space
275
     * can only  contain letters, digits, hyphens, underscores, and periods
276
     * @param string $name The attribute name to be used as an element
277
     * @return bool true if $name would make a valid xml element.
278
     */
279
    private function isValidXmlName(string $name): bool
280
    {
281
        try {
282
            new DOMElement($name);
283
            return true;
284
        } catch (DOMException $e) {
285
                return false;
286
        }
287
    }
288
}
289