Passed
Push — master ( ca585a...7cdaa0 )
by Tim
02:07
created

lib/Cas/Protocol/Cas20.php (1 issue)

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
namespace SimpleSAML\Module\casserver\Cas\Protocol;
25
26
use DOMDocument;
27
use DOMElement;
28
use DOMException;
29
use SimpleSAML\Configuration;
30
use SimpleSAML\Logger;
31
32
class Cas20
33
{
34
    /** @var bool $sendAttributes */
35
    private $sendAttributes;
36
37
    /** @var bool $base64EncodeAttributes */
38
    private $base64EncodeAttributes;
39
40
    /** @var string|null $base64IndicatorAttribute */
41
    private $base64IndicatorAttribute;
42
43
    /** @var array $attributes */
44
    private $attributes = [];
45
46
    /** @var string|null $proxyGrantingTicketIOU */
47
    private $proxyGrantingTicketIOU = null;
48
49
50
    /**
51
     * @param \SimpleSAML\Configuration $config
52
     */
53
    public function __construct(Configuration $config)
54
    {
55
        $this->sendAttributes = $config->getValue('attributes', false);
56
        $this->base64EncodeAttributes = $config->getValue('base64attributes', false);
57
        $this->base64IndicatorAttribute = $config->getValue('base64_attributes_indicator_attribute', null);
58
    }
59
60
61
    /**
62
     * @param array $attributes
63
     * @return void
64
     */
65
    public function setAttributes(string $attributes): void
66
    {
67
        $this->attributes = $attributes;
0 ignored issues
show
Documentation Bug introduced by
It seems like $attributes of type string is incompatible with the declared type array of property $attributes.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
68
    }
69
70
71
    /**
72
     * @return array
73
     */
74
    public function getAttributes(): array
75
    {
76
        return $this->attributes;
77
    }
78
79
80
    /**
81
     * @param string $proxyGrantingTicketIOU
82
     * @return void
83
     */
84
    public function setProxyGrantingTicketIOU(string $proxyGrantingTicketIOU): void
85
    {
86
        $this->proxyGrantingTicketIOU = $proxyGrantingTicketIOU;
87
    }
88
89
90
    /**
91
     * @return string|null
92
     */
93
    public function getProxyGrantingTicketIOU(): ?string
94
    {
95
        return $this->proxyGrantingTicketIOU;
96
    }
97
98
99
    /**
100
     * @param string $username
101
     * @return string
102
     */
103
    public function getValidateSuccessResponse(string $username): string
104
    {
105
        $xmlDocument = new DOMDocument("1.0");
106
107
        $root = $xmlDocument->createElement("cas:serviceResponse");
108
        $root->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:cas', 'http://www.yale.edu/tp/cas');
109
110
        $usernameNode = $xmlDocument->createTextNode($username);
111
        $casUser = $xmlDocument->createElement('cas:user');
112
        $casUser->appendChild($usernameNode);
113
114
        $casSuccess = $xmlDocument->createElement('cas:authenticationSuccess');
115
        $casSuccess->appendChild($casUser);
116
117
        if (is_string($this->proxyGrantingTicketIOU)) {
118
            $iouNode = $xmlDocument->createTextNode($this->proxyGrantingTicketIOU);
119
            $iouElement = $xmlDocument->createElement("cas:proxyGrantingTicket");
120
            $iouElement->appendChild($iouNode);
121
            $casSuccess->appendChild($iouElement);
122
        }
123
124
        if ($this->sendAttributes && count($this->attributes) > 0) {
125
            $casAttributes = $xmlDocument->createElement('cas:attributes');
126
127
            foreach ($this->attributes as $name => $values) {
128
                // Fix the most common cause of invalid XML elements
129
                $_name = str_replace(':', '_', $name);
130
                if ($this->isValidXmlName($_name) === true) {
131
                    foreach ($values as $value) {
132
                        $casAttributes->appendChild(
133
                            $this->generateCas20Attribute($xmlDocument, $_name, $value)
134
                        );
135
                    }
136
                } else {
137
                    Logger::warning("Dom exception creating attribute '$_name'. Continuing without atrribute'");
138
                }
139
            }
140
141
            if (!is_null($this->base64IndicatorAttribute)) {
142
                $casAttributes->appendChild(
143
                    $this->generateCas20Attribute(
144
                        $xmlDocument,
145
                        $this->base64IndicatorAttribute,
146
                        $this->base64EncodeAttributes ? "true" : "false"
147
                    )
148
                );
149
            }
150
151
            $casSuccess->appendChild($casAttributes);
152
        }
153
154
        $root->appendChild($casSuccess);
155
        $xmlDocument->appendChild($root);
156
157
        return $this->workAroundForBuggyJasigXmlParser($xmlDocument->saveXML());
158
    }
159
160
161
    /**
162
     * @param string $errorCode
163
     * @param string $explanation
164
     * @return string
165
     */
166
    public function getValidateFailureResponse(string $errorCode, string $explanation): string
167
    {
168
        $xmlDocument = new DOMDocument("1.0");
169
170
        $root = $xmlDocument->createElement("cas:serviceResponse");
171
        $root->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:cas', 'http://www.yale.edu/tp/cas');
172
173
        $casFailureCode = $xmlDocument->createAttribute('code');
174
        $casFailureCode->value = $errorCode;
175
176
        $casFailureNode = $xmlDocument->createTextNode($explanation);
177
        $casFailure = $xmlDocument->createElement('cas:authenticationFailure');
178
        $casFailure->appendChild($casFailureNode);
179
        $casFailure->appendChild($casFailureCode);
180
181
        $root->appendChild($casFailure);
182
183
        $xmlDocument->appendChild($root);
184
185
        return $this->workAroundForBuggyJasigXmlParser($xmlDocument->saveXML());
186
    }
187
188
189
    /**
190
     * @param string $proxyTicketId
191
     * @return string
192
     */
193
    public function getProxySuccessResponse(string $proxyTicketId): string
194
    {
195
        $xmlDocument = new DOMDocument("1.0");
196
197
        $root = $xmlDocument->createElement("cas:serviceResponse");
198
        $root->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:cas', 'http://www.yale.edu/tp/cas');
199
200
        $casProxyTicketIdNode = $xmlDocument->createTextNode($proxyTicketId);
201
        $casProxyTicketId = $xmlDocument->createElement('cas:proxyTicket');
202
        $casProxyTicketId->appendChild($casProxyTicketIdNode);
203
204
        $casProxySuccess = $xmlDocument->createElement('cas:proxySuccess');
205
        $casProxySuccess->appendChild($casProxyTicketId);
206
207
        $root->appendChild($casProxySuccess);
208
        $xmlDocument->appendChild($root);
209
210
        return $this->workAroundForBuggyJasigXmlParser($xmlDocument->saveXML());
211
    }
212
213
214
    /**
215
     * @param string $errorCode
216
     * @param string $explanation
217
     * @return string
218
     */
219
    public function getProxyFailureResponse(string $errorCode, string $explanation): string
220
    {
221
        $xmlDocument = new DOMDocument("1.0");
222
223
        $root = $xmlDocument->createElement("cas:serviceResponse");
224
        $root->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:cas', 'http://www.yale.edu/tp/cas');
225
226
        $casFailureCode = $xmlDocument->createAttribute('code');
227
        $casFailureCode->value = $errorCode;
228
229
        $casFailureNode = $xmlDocument->createTextNode($explanation);
230
        $casFailure = $xmlDocument->createElement('cas:proxyFailure');
231
        $casFailure->appendChild($casFailureNode);
232
        $casFailure->appendChild($casFailureCode);
233
234
        $root->appendChild($casFailure);
235
236
        $xmlDocument->appendChild($root);
237
238
        return $this->workAroundForBuggyJasigXmlParser($xmlDocument->saveXML());
239
    }
240
241
242
    /**
243
     * @param string $xmlString
244
     * @return string
245
     */
246
    private function workAroundForBuggyJasigXmlParser(string $xmlString): string
247
    {
248
        // when will people stop hand coding xml handling....?
249
        return str_replace('><', '>' . PHP_EOL . '<', str_replace(PHP_EOL, '', $xmlString));
250
    }
251
252
253
    /**
254
     * @param \DOMDocument $xmlDocument
255
     * @param string $attributeName
256
     * @param string $attributeValue
257
     * @return \DOMElement
258
     */
259
    private function generateCas20Attribute(
260
        DOMDocument $xmlDocument,
261
        string $attributeName,
262
        string $attributeValue
263
    ): DOMElement {
264
        $attributeValueNode = $xmlDocument->createTextNode($this->base64EncodeAttributes
265
            ? base64_encode($attributeValue)
266
            : $attributeValue);
267
268
        $attributeElement = $xmlDocument->createElement('cas:' . $attributeName);
269
270
        $attributeElement->appendChild($attributeValueNode);
271
272
        return $attributeElement;
273
    }
274
275
276
    /**
277
     * XML element names have a lot of rules and not every SAML attribute name can be converted.
278
     * Ref: https://www.w3.org/TR/REC-xml/#NT-NameChar
279
     * https://stackoverflow.com/q/2519845/54396
280
     * must only start with letter or underscore
281
     * cannot start with 'xml' (or maybe it can - stackoverflow commenters don't agree)
282
     * cannot contain a ':' since those are for namespaces
283
     * cannot contains space
284
     * can only  contain letters, digits, hyphens, underscores, and periods
285
     * @param string $name The attribute name to be used as an element
286
     * @return bool true if $name would make a valid xml element.
287
     */
288
    private function isValidXmlName(string $name): bool
289
    {
290
        try {
291
            new DOMElement($name);
292
            return true;
293
        } catch (DOMException $e) {
294
                return false;
295
        }
296
    }
297
}
298