~ubuntu-branches/ubuntu/utopic/moodle/utopic

« back to all changes in this revision

Viewing changes to auth/cas/CAS/CAS/CookieJar.php

  • Committer: Package Import Robot
  • Author(s): Thijs Kinkhorst
  • Date: 2014-05-12 16:10:38 UTC
  • mfrom: (36.1.3 sid)
  • Revision ID: package-import@ubuntu.com-20140512161038-puyqf65k4e0s8ytz
Tags: 2.6.3-1
New upstream release.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
<?php
 
2
 
 
3
/**
 
4
 * Licensed to Jasig under one or more contributor license
 
5
 * agreements. See the NOTICE file distributed with this work for
 
6
 * additional information regarding copyright ownership.
 
7
 *
 
8
 * Jasig licenses this file to you under the Apache License,
 
9
 * Version 2.0 (the "License"); you may not use this file except in
 
10
 * compliance with the License. You may obtain a copy of the License at:
 
11
 *
 
12
 * http://www.apache.org/licenses/LICENSE-2.0
 
13
 *
 
14
 * Unless required by applicable law or agreed to in writing, software
 
15
 * distributed under the License is distributed on an "AS IS" BASIS,
 
16
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 
17
 * See the License for the specific language governing permissions and
 
18
 * limitations under the License.
 
19
 *
 
20
 * PHP Version 5
 
21
 *
 
22
 * @file     CAS/CookieJar.php
 
23
 * @category Authentication
 
24
 * @package  PhpCAS
 
25
 * @author   Adam Franco <afranco@middlebury.edu>
 
26
 * @license  http://www.apache.org/licenses/LICENSE-2.0  Apache License 2.0
 
27
 * @link     https://wiki.jasig.org/display/CASC/phpCAS
 
28
 */
 
29
 
 
30
/**
 
31
 * This class provides access to service cookies and handles parsing of response
 
32
 * headers to pull out cookie values.
 
33
 *
 
34
 * @class    CAS_CookieJar
 
35
 * @category Authentication
 
36
 * @package  PhpCAS
 
37
 * @author   Adam Franco <afranco@middlebury.edu>
 
38
 * @license  http://www.apache.org/licenses/LICENSE-2.0  Apache License 2.0
 
39
 * @link     https://wiki.jasig.org/display/CASC/phpCAS
 
40
 */
 
41
class CAS_CookieJar
 
42
{
 
43
 
 
44
    private $_cookies;
 
45
 
 
46
    /**
 
47
     * Create a new cookie jar by passing it a reference to an array in which it
 
48
     * should store cookies.
 
49
     *
 
50
     * @param array &$storageArray Array to store cookies
 
51
     *
 
52
     * @return void
 
53
     */
 
54
    public function __construct (array &$storageArray)
 
55
    {
 
56
        $this->_cookies =& $storageArray;
 
57
    }
 
58
 
 
59
    /**
 
60
     * Store cookies for a web service request.
 
61
     * Cookie storage is based on RFC 2965: http://www.ietf.org/rfc/rfc2965.txt
 
62
     *
 
63
     * @param string $request_url      The URL that generated the response headers.
 
64
     * @param array  $response_headers An array of the HTTP response header strings.
 
65
     *
 
66
     * @return void
 
67
     *
 
68
     * @access private
 
69
     */
 
70
    public function storeCookies ($request_url, $response_headers)
 
71
    {
 
72
        $urlParts = parse_url($request_url);
 
73
        $defaultDomain = $urlParts['host'];
 
74
 
 
75
        $cookies = $this->parseCookieHeaders($response_headers, $defaultDomain);
 
76
 
 
77
        // var_dump($cookies);
 
78
        foreach ($cookies as $cookie) {
 
79
            // Enforce the same-origin policy by verifying that the cookie
 
80
            // would match the url that is setting it
 
81
            if (!$this->cookieMatchesTarget($cookie, $urlParts)) {
 
82
                continue;
 
83
            }
 
84
 
 
85
            // store the cookie
 
86
            $this->storeCookie($cookie);
 
87
 
 
88
            phpCAS::trace($cookie['name'].' -> '.$cookie['value']);
 
89
        }
 
90
    }
 
91
 
 
92
    /**
 
93
     * Retrieve cookies applicable for a web service request.
 
94
     * Cookie applicability is based on RFC 2965: http://www.ietf.org/rfc/rfc2965.txt
 
95
     *
 
96
     * @param string $request_url The url that the cookies will be for.
 
97
     *
 
98
     * @return array An array containing cookies. E.g. array('name' => 'val');
 
99
     *
 
100
     * @access private
 
101
     */
 
102
    public function getCookies ($request_url)
 
103
    {
 
104
        if (!count($this->_cookies)) {
 
105
            return array();
 
106
        }
 
107
 
 
108
        // If our request URL can't be parsed, no cookies apply.
 
109
        $target = parse_url($request_url);
 
110
        if ($target === false) {
 
111
            return array();
 
112
        }
 
113
 
 
114
        $this->expireCookies();
 
115
 
 
116
        $matching_cookies = array();
 
117
        foreach ($this->_cookies as $key => $cookie) {
 
118
            if ($this->cookieMatchesTarget($cookie, $target)) {
 
119
                $matching_cookies[$cookie['name']] = $cookie['value'];
 
120
            }
 
121
        }
 
122
        return $matching_cookies;
 
123
    }
 
124
 
 
125
 
 
126
    /**
 
127
     * Parse Cookies without PECL
 
128
     * From the comments in http://php.net/manual/en/function.http-parse-cookie.php
 
129
     *
 
130
     * @param array  $header        array of header lines.
 
131
     * @param string $defaultDomain The domain to use if none is specified in
 
132
     * the cookie.
 
133
     *
 
134
     * @return array of cookies
 
135
     */
 
136
    protected function parseCookieHeaders( $header, $defaultDomain )
 
137
    {
 
138
        phpCAS::traceBegin();
 
139
        $cookies = array();
 
140
        foreach ( $header as $line ) {
 
141
            if ( preg_match('/^Set-Cookie2?: /i', $line)) {
 
142
                $cookies[] = $this->parseCookieHeader($line, $defaultDomain);
 
143
            }
 
144
        }
 
145
 
 
146
        phpCAS::traceEnd($cookies);
 
147
        return $cookies;
 
148
    }
 
149
 
 
150
    /**
 
151
     * Parse a single cookie header line.
 
152
     *
 
153
     * Based on RFC2965 http://www.ietf.org/rfc/rfc2965.txt
 
154
     *
 
155
     * @param string $line          The header line.
 
156
     * @param string $defaultDomain The domain to use if none is specified in
 
157
     * the cookie.
 
158
     *
 
159
     * @return array
 
160
     */
 
161
    protected function parseCookieHeader ($line, $defaultDomain)
 
162
    {
 
163
        if (!$defaultDomain) {
 
164
            throw new CAS_InvalidArgumentException('$defaultDomain was not provided.');
 
165
        }
 
166
 
 
167
        // Set our default values
 
168
        $cookie = array(
 
169
            'domain' => $defaultDomain,
 
170
            'path' => '/',
 
171
            'secure' => false,
 
172
        );
 
173
 
 
174
        $line = preg_replace('/^Set-Cookie2?: /i', '', trim($line));
 
175
 
 
176
        // trim any trailing semicolons.
 
177
        $line = trim($line, ';');
 
178
 
 
179
        phpCAS::trace("Cookie Line: $line");
 
180
 
 
181
        // This implementation makes the assumption that semicolons will not
 
182
        // be present in quoted attribute values. While attribute values that
 
183
        // contain semicolons are allowed by RFC2965, they are hopefully rare
 
184
        // enough to ignore for our purposes. Most browsers make the same
 
185
        // assumption.
 
186
        $attributeStrings = explode(';', $line);
 
187
 
 
188
        foreach ( $attributeStrings as $attributeString ) {
 
189
            // split on the first equals sign and use the rest as value
 
190
            $attributeParts = explode('=', $attributeString, 2);
 
191
 
 
192
            $attributeName = trim($attributeParts[0]);
 
193
            $attributeNameLC = strtolower($attributeName);
 
194
 
 
195
            if (isset($attributeParts[1])) {
 
196
                $attributeValue = trim($attributeParts[1]);
 
197
                // Values may be quoted strings.
 
198
                if (strpos($attributeValue, '"') === 0) {
 
199
                    $attributeValue = trim($attributeValue, '"');
 
200
                    // unescape any escaped quotes:
 
201
                    $attributeValue = str_replace('\"', '"', $attributeValue);
 
202
                }
 
203
            } else {
 
204
                $attributeValue = null;
 
205
            }
 
206
 
 
207
            switch ($attributeNameLC) {
 
208
            case 'expires':
 
209
                $cookie['expires'] = strtotime($attributeValue);
 
210
                break;
 
211
            case 'max-age':
 
212
                $cookie['max-age'] = (int)$attributeValue;
 
213
                // Set an expiry time based on the max-age
 
214
                if ($cookie['max-age']) {
 
215
                    $cookie['expires'] = time() + $cookie['max-age'];
 
216
                } else {
 
217
                    // If max-age is zero, then the cookie should be removed
 
218
                    // imediately so set an expiry before now.
 
219
                    $cookie['expires'] = time() - 1;
 
220
                }
 
221
                break;
 
222
            case 'secure':
 
223
                $cookie['secure'] = true;
 
224
                break;
 
225
            case 'domain':
 
226
            case 'path':
 
227
            case 'port':
 
228
            case 'version':
 
229
            case 'comment':
 
230
            case 'commenturl':
 
231
            case 'discard':
 
232
            case 'httponly':
 
233
                $cookie[$attributeNameLC] = $attributeValue;
 
234
                break;
 
235
            default:
 
236
                $cookie['name'] = $attributeName;
 
237
                $cookie['value'] = $attributeValue;
 
238
            }
 
239
        }
 
240
 
 
241
        return $cookie;
 
242
    }
 
243
 
 
244
    /**
 
245
     * Add, update, or remove a cookie.
 
246
     *
 
247
     * @param array $cookie A cookie array as created by parseCookieHeaders()
 
248
     *
 
249
     * @return void
 
250
     *
 
251
     * @access protected
 
252
     */
 
253
    protected function storeCookie ($cookie)
 
254
    {
 
255
        // Discard any old versions of this cookie.
 
256
        $this->discardCookie($cookie);
 
257
        $this->_cookies[] = $cookie;
 
258
 
 
259
    }
 
260
 
 
261
    /**
 
262
     * Discard an existing cookie
 
263
     *
 
264
     * @param array $cookie An cookie
 
265
     *
 
266
     * @return void
 
267
     *
 
268
     * @access protected
 
269
     */
 
270
    protected function discardCookie ($cookie)
 
271
    {
 
272
        if (!isset($cookie['domain'])
 
273
            || !isset($cookie['path'])
 
274
            || !isset($cookie['path'])
 
275
        ) {
 
276
            throw new CAS_InvalidArgumentException('Invalid Cookie array passed.');
 
277
        }
 
278
 
 
279
        foreach ($this->_cookies as $key => $old_cookie) {
 
280
            if ( $cookie['domain'] == $old_cookie['domain']
 
281
                && $cookie['path'] == $old_cookie['path']
 
282
                && $cookie['name'] == $old_cookie['name']
 
283
            ) {
 
284
                unset($this->_cookies[$key]);
 
285
            }
 
286
        }
 
287
    }
 
288
 
 
289
    /**
 
290
     * Go through our stored cookies and remove any that are expired.
 
291
     *
 
292
     * @return void
 
293
     *
 
294
     * @access protected
 
295
     */
 
296
    protected function expireCookies ()
 
297
    {
 
298
        foreach ($this->_cookies as $key => $cookie) {
 
299
            if (isset($cookie['expires']) && $cookie['expires'] < time()) {
 
300
                unset($this->_cookies[$key]);
 
301
            }
 
302
        }
 
303
    }
 
304
 
 
305
    /**
 
306
     * Answer true if cookie is applicable to a target.
 
307
     *
 
308
     * @param array $cookie An array of cookie attributes.
 
309
     * @param array $target An array of URL attributes as generated by parse_url().
 
310
     *
 
311
     * @return bool
 
312
     *
 
313
     * @access private
 
314
     */
 
315
    protected function cookieMatchesTarget ($cookie, $target)
 
316
    {
 
317
        if (!is_array($target)) {
 
318
            throw new CAS_InvalidArgumentException('$target must be an array of URL attributes as generated by parse_url().');
 
319
        }
 
320
        if (!isset($target['host'])) {
 
321
            throw new CAS_InvalidArgumentException('$target must be an array of URL attributes as generated by parse_url().');
 
322
        }
 
323
 
 
324
        // Verify that the scheme matches
 
325
        if ($cookie['secure'] && $target['scheme'] != 'https') {
 
326
            return false;
 
327
        }
 
328
 
 
329
        // Verify that the host matches
 
330
        // Match domain and mulit-host cookies
 
331
        if (strpos($cookie['domain'], '.') === 0) {
 
332
            // .host.domain.edu cookies are valid for host.domain.edu
 
333
            if (substr($cookie['domain'], 1) == $target['host']) {
 
334
                // continue with other checks
 
335
            } else {
 
336
                // non-exact host-name matches.
 
337
                // check that the target host a.b.c.edu is within .b.c.edu
 
338
                $pos = strripos($target['host'], $cookie['domain']);
 
339
                if (!$pos) {
 
340
                    return false;
 
341
                }
 
342
                // verify that the cookie domain is the last part of the host.
 
343
                if ($pos + strlen($cookie['domain']) != strlen($target['host'])) {
 
344
                    return false;
 
345
                }
 
346
                // verify that the host name does not contain interior dots as per
 
347
                // RFC 2965 section 3.3.2  Rejecting Cookies
 
348
                // http://www.ietf.org/rfc/rfc2965.txt
 
349
                $hostname = substr($target['host'], 0, $pos);
 
350
                if (strpos($hostname, '.') !== false) {
 
351
                    return false;
 
352
                }
 
353
            }
 
354
        } else {
 
355
            // If the cookie host doesn't begin with '.', the host must case-insensitive
 
356
            // match exactly
 
357
            if (strcasecmp($target['host'], $cookie['domain']) !== 0) {
 
358
                return false;
 
359
            }
 
360
        }
 
361
 
 
362
        // Verify that the port matches
 
363
        if (isset($cookie['ports']) && !in_array($target['port'], $cookie['ports'])) {
 
364
            return false;
 
365
        }
 
366
 
 
367
        // Verify that the path matches
 
368
        if (strpos($target['path'], $cookie['path']) !== 0) {
 
369
            return false;
 
370
        }
 
371
 
 
372
        return true;
 
373
    }
 
374
 
 
375
}
 
376
 
 
377
?>