Failed Conditions
Push — interwiki-remove-golucky ( 52fcdb...768be5 )
by Henry
12:48 queued 09:48
created

auth.php ➔ auth_aclcheck()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 3
dl 0
loc 9
rs 9.9666
c 0
b 0
f 0
1
<?php
2
/**
3
 * Authentication library
4
 *
5
 * Including this file will automatically try to login
6
 * a user by calling auth_login()
7
 *
8
 * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
9
 * @author     Andreas Gohr <[email protected]>
10
 */
11
12
// some ACL level defines
13
use dokuwiki\PassHash;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, PassHash.

Let’s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let’s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
14
use dokuwiki\Subscriptions\RegistrationSubscriptionSender;
15
use dokuwiki\Extension\AuthPlugin;
16
use dokuwiki\Extension\PluginController;
17
use dokuwiki\Extension\Event;
18
19
define('AUTH_NONE', 0);
20
define('AUTH_READ', 1);
21
define('AUTH_EDIT', 2);
22
define('AUTH_CREATE', 4);
23
define('AUTH_UPLOAD', 8);
24
define('AUTH_DELETE', 16);
25
define('AUTH_ADMIN', 255);
26
27
/**
28
 * Initialize the auth system.
29
 *
30
 * This function is automatically called at the end of init.php
31
 *
32
 * This used to be the main() of the auth.php
33
 *
34
 * @todo backend loading maybe should be handled by the class autoloader
35
 * @todo maybe split into multiple functions at the XXX marked positions
36
 * @triggers AUTH_LOGIN_CHECK
37
 * @return bool
38
 */
39
function auth_setup() {
40
    global $conf;
41
    /* @var AuthPlugin $auth */
42
    global $auth;
43
    /* @var Input $INPUT */
44
    global $INPUT;
45
    global $AUTH_ACL;
46
    global $lang;
47
    /* @var PluginController $plugin_controller */
48
    global $plugin_controller;
49
    $AUTH_ACL = array();
50
51
    if(!$conf['useacl']) return false;
52
53
    // try to load auth backend from plugins
54
    foreach ($plugin_controller->getList('auth') as $plugin) {
55
        if ($conf['authtype'] === $plugin) {
56
            $auth = $plugin_controller->load('auth', $plugin);
57
            break;
58
        }
59
    }
60
61
    if(!isset($auth) || !$auth){
62
        msg($lang['authtempfail'], -1);
63
        return false;
64
    }
65
66
    if ($auth->success == false) {
0 ignored issues
show
Bug introduced by
Accessing success on the interface dokuwiki\Extension\PluginInterface suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
67
        // degrade to unauthenticated user
68
        unset($auth);
69
        auth_logoff();
70
        msg($lang['authtempfail'], -1);
71
        return false;
72
    }
73
74
    // do the login either by cookie or provided credentials XXX
75
    $INPUT->set('http_credentials', false);
76
    if(!$conf['rememberme']) $INPUT->set('r', false);
77
78
    // handle renamed HTTP_AUTHORIZATION variable (can happen when a fix like
79
    // the one presented at
80
    // http://www.besthostratings.com/articles/http-auth-php-cgi.html is used
81
    // for enabling HTTP authentication with CGI/SuExec)
82
    if(isset($_SERVER['REDIRECT_HTTP_AUTHORIZATION']))
83
        $_SERVER['HTTP_AUTHORIZATION'] = $_SERVER['REDIRECT_HTTP_AUTHORIZATION'];
84
    // streamline HTTP auth credentials (IIS/rewrite -> mod_php)
85
    if(isset($_SERVER['HTTP_AUTHORIZATION'])) {
86
        list($_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW']) =
87
            explode(':', base64_decode(substr($_SERVER['HTTP_AUTHORIZATION'], 6)));
88
    }
89
90
    // if no credentials were given try to use HTTP auth (for SSO)
91
    if(!$INPUT->str('u') && empty($_COOKIE[DOKU_COOKIE]) && !empty($_SERVER['PHP_AUTH_USER'])) {
92
        $INPUT->set('u', $_SERVER['PHP_AUTH_USER']);
93
        $INPUT->set('p', $_SERVER['PHP_AUTH_PW']);
94
        $INPUT->set('http_credentials', true);
95
    }
96
97
    // apply cleaning (auth specific user names, remove control chars)
98
    if (true === $auth->success) {
0 ignored issues
show
Bug introduced by
Accessing success on the interface dokuwiki\Extension\PluginInterface suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
99
        $INPUT->set('u', $auth->cleanUser(stripctl($INPUT->str('u'))));
100
        $INPUT->set('p', stripctl($INPUT->str('p')));
101
    }
102
103
    if(!is_null($auth) && $auth->canDo('external')) {
104
        // external trust mechanism in place
105
        $auth->trustExternal($INPUT->str('u'), $INPUT->str('p'), $INPUT->bool('r'));
106
    } else {
107
        $evdata = array(
108
            'user'     => $INPUT->str('u'),
109
            'password' => $INPUT->str('p'),
110
            'sticky'   => $INPUT->bool('r'),
111
            'silent'   => $INPUT->bool('http_credentials')
112
        );
113
        Event::createAndTrigger('AUTH_LOGIN_CHECK', $evdata, 'auth_login_wrapper');
114
    }
115
116
    //load ACL into a global array XXX
117
    $AUTH_ACL = auth_loadACL();
118
119
    return true;
120
}
121
122
/**
123
 * Loads the ACL setup and handle user wildcards
124
 *
125
 * @author Andreas Gohr <[email protected]>
126
 *
127
 * @return array
128
 */
129
function auth_loadACL() {
130
    global $config_cascade;
131
    global $USERINFO;
132
    /* @var Input $INPUT */
133
    global $INPUT;
134
135
    if(!is_readable($config_cascade['acl']['default'])) return array();
136
137
    $acl = file($config_cascade['acl']['default']);
138
139
    $out = array();
140
    foreach($acl as $line) {
141
        $line = trim($line);
142
        if(empty($line) || ($line{0} == '#')) continue; // skip blank lines & comments
143
        list($id,$rest) = preg_split('/[ \t]+/',$line,2);
144
145
        // substitute user wildcard first (its 1:1)
146
        if(strstr($line, '%USER%')){
147
            // if user is not logged in, this ACL line is meaningless - skip it
148
            if (!$INPUT->server->has('REMOTE_USER')) continue;
149
150
            $id   = str_replace('%USER%',cleanID($INPUT->server->str('REMOTE_USER')),$id);
151
            $rest = str_replace('%USER%',auth_nameencode($INPUT->server->str('REMOTE_USER')),$rest);
152
        }
153
154
        // substitute group wildcard (its 1:m)
155
        if(strstr($line, '%GROUP%')){
156
            // if user is not logged in, grps is empty, no output will be added (i.e. skipped)
157
            foreach((array) $USERINFO['grps'] as $grp){
158
                $nid   = str_replace('%GROUP%',cleanID($grp),$id);
159
                $nrest = str_replace('%GROUP%','@'.auth_nameencode($grp),$rest);
160
                $out[] = "$nid\t$nrest";
161
            }
162
        } else {
163
            $out[] = "$id\t$rest";
164
        }
165
    }
166
167
    return $out;
168
}
169
170
/**
171
 * Event hook callback for AUTH_LOGIN_CHECK
172
 *
173
 * @param array $evdata
174
 * @return bool
175
 */
176
function auth_login_wrapper($evdata) {
177
    return auth_login(
178
        $evdata['user'],
179
        $evdata['password'],
180
        $evdata['sticky'],
181
        $evdata['silent']
182
    );
183
}
184
185
/**
186
 * This tries to login the user based on the sent auth credentials
187
 *
188
 * The authentication works like this: if a username was given
189
 * a new login is assumed and user/password are checked. If they
190
 * are correct the password is encrypted with blowfish and stored
191
 * together with the username in a cookie - the same info is stored
192
 * in the session, too. Additonally a browserID is stored in the
193
 * session.
194
 *
195
 * If no username was given the cookie is checked: if the username,
196
 * crypted password and browserID match between session and cookie
197
 * no further testing is done and the user is accepted
198
 *
199
 * If a cookie was found but no session info was availabe the
200
 * blowfish encrypted password from the cookie is decrypted and
201
 * together with username rechecked by calling this function again.
202
 *
203
 * On a successful login $_SERVER[REMOTE_USER] and $USERINFO
204
 * are set.
205
 *
206
 * @author  Andreas Gohr <[email protected]>
207
 *
208
 * @param   string  $user    Username
209
 * @param   string  $pass    Cleartext Password
210
 * @param   bool    $sticky  Cookie should not expire
211
 * @param   bool    $silent  Don't show error on bad auth
212
 * @return  bool             true on successful auth
213
 */
214
function auth_login($user, $pass, $sticky = false, $silent = false) {
215
    global $USERINFO;
216
    global $conf;
217
    global $lang;
218
    /* @var AuthPlugin $auth */
219
    global $auth;
220
    /* @var Input $INPUT */
221
    global $INPUT;
222
223
    $sticky ? $sticky = true : $sticky = false; //sanity check
224
225
    if(!$auth) return false;
226
227
    if(!empty($user)) {
228
        //usual login
229
        if(!empty($pass) && $auth->checkPass($user, $pass)) {
230
            // make logininfo globally available
231
            $INPUT->server->set('REMOTE_USER', $user);
232
            $secret                 = auth_cookiesalt(!$sticky, true); //bind non-sticky to session
233
            auth_setCookie($user, auth_encrypt($pass, $secret), $sticky);
234
            return true;
235
        } else {
236
            //invalid credentials - log off
237
            if(!$silent) {
238
                http_status(403, 'Login failed');
239
                msg($lang['badlogin'], -1);
240
            }
241
            auth_logoff();
242
            return false;
243
        }
244
    } else {
245
        // read cookie information
246
        list($user, $sticky, $pass) = auth_getCookie();
247
        if($user && $pass) {
248
            // we got a cookie - see if we can trust it
249
250
            // get session info
251
            $session = $_SESSION[DOKU_COOKIE]['auth'];
252
            if(isset($session) &&
253
                $auth->useSessionCache($user) &&
254
                ($session['time'] >= time() - $conf['auth_security_timeout']) &&
255
                ($session['user'] == $user) &&
256
                ($session['pass'] == sha1($pass)) && //still crypted
257
                ($session['buid'] == auth_browseruid())
258
            ) {
259
260
                // he has session, cookie and browser right - let him in
261
                $INPUT->server->set('REMOTE_USER', $user);
262
                $USERINFO               = $session['info']; //FIXME move all references to session
263
                return true;
264
            }
265
            // no we don't trust it yet - recheck pass but silent
266
            $secret = auth_cookiesalt(!$sticky, true); //bind non-sticky to session
267
            $pass   = auth_decrypt($pass, $secret);
268
            return auth_login($user, $pass, $sticky, true);
269
        }
270
    }
271
    //just to be sure
272
    auth_logoff(true);
273
    return false;
274
}
275
276
/**
277
 * Builds a pseudo UID from browser and IP data
278
 *
279
 * This is neither unique nor unfakable - still it adds some
280
 * security. Using the first part of the IP makes sure
281
 * proxy farms like AOLs are still okay.
282
 *
283
 * @author  Andreas Gohr <[email protected]>
284
 *
285
 * @return  string  a MD5 sum of various browser headers
286
 */
287
function auth_browseruid() {
288
    /* @var Input $INPUT */
289
    global $INPUT;
290
291
    $ip  = clientIP(true);
292
    $uid = '';
293
    $uid .= $INPUT->server->str('HTTP_USER_AGENT');
294
    $uid .= $INPUT->server->str('HTTP_ACCEPT_CHARSET');
295
    $uid .= substr($ip, 0, strpos($ip, '.'));
296
    $uid = strtolower($uid);
297
    return md5($uid);
298
}
299
300
/**
301
 * Creates a random key to encrypt the password in cookies
302
 *
303
 * This function tries to read the password for encrypting
304
 * cookies from $conf['metadir'].'/_htcookiesalt'
305
 * if no such file is found a random key is created and
306
 * and stored in this file.
307
 *
308
 * @author  Andreas Gohr <[email protected]>
309
 *
310
 * @param   bool $addsession if true, the sessionid is added to the salt
311
 * @param   bool $secure     if security is more important than keeping the old value
312
 * @return  string
313
 */
314
function auth_cookiesalt($addsession = false, $secure = false) {
315
    if (defined('SIMPLE_TEST')) {
316
        return 'test';
317
    }
318
    global $conf;
319
    $file = $conf['metadir'].'/_htcookiesalt';
320
    if ($secure || !file_exists($file)) {
321
        $file = $conf['metadir'].'/_htcookiesalt2';
322
    }
323
    $salt = io_readFile($file);
324
    if(empty($salt)) {
325
        $salt = bin2hex(auth_randombytes(64));
326
        io_saveFile($file, $salt);
327
    }
328
    if($addsession) {
329
        $salt .= session_id();
330
    }
331
    return $salt;
332
}
333
334
/**
335
 * Return cryptographically secure random bytes.
336
 *
337
 * @author Niklas Keller <[email protected]>
338
 *
339
 * @param int $length number of bytes
340
 * @return string cryptographically secure random bytes
341
 */
342
function auth_randombytes($length) {
343
    return random_bytes($length);
344
}
345
346
/**
347
 * Cryptographically secure random number generator.
348
 *
349
 * @author Niklas Keller <[email protected]>
350
 *
351
 * @param int $min
352
 * @param int $max
353
 * @return int
354
 */
355
function auth_random($min, $max) {
356
    return random_int($min, $max);
357
}
358
359
/**
360
 * Encrypt data using the given secret using AES
361
 *
362
 * The mode is CBC with a random initialization vector, the key is derived
363
 * using pbkdf2.
364
 *
365
 * @param string $data   The data that shall be encrypted
366
 * @param string $secret The secret/password that shall be used
367
 * @return string The ciphertext
368
 */
369
function auth_encrypt($data, $secret) {
370
    $iv     = auth_randombytes(16);
371
    $cipher = new \phpseclib\Crypt\AES();
372
    $cipher->setPassword($secret);
373
374
    /*
375
    this uses the encrypted IV as IV as suggested in
376
    http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf, Appendix C
377
    for unique but necessarily random IVs. The resulting ciphertext is
378
    compatible to ciphertext that was created using a "normal" IV.
379
    */
380
    return $cipher->encrypt($iv.$data);
381
}
382
383
/**
384
 * Decrypt the given AES ciphertext
385
 *
386
 * The mode is CBC, the key is derived using pbkdf2
387
 *
388
 * @param string $ciphertext The encrypted data
389
 * @param string $secret     The secret/password that shall be used
390
 * @return string The decrypted data
391
 */
392
function auth_decrypt($ciphertext, $secret) {
393
    $iv     = substr($ciphertext, 0, 16);
394
    $cipher = new \phpseclib\Crypt\AES();
395
    $cipher->setPassword($secret);
396
    $cipher->setIV($iv);
397
398
    return $cipher->decrypt(substr($ciphertext, 16));
399
}
400
401
/**
402
 * Log out the current user
403
 *
404
 * This clears all authentication data and thus log the user
405
 * off. It also clears session data.
406
 *
407
 * @author  Andreas Gohr <[email protected]>
408
 *
409
 * @param bool $keepbc - when true, the breadcrumb data is not cleared
410
 */
411
function auth_logoff($keepbc = false) {
412
    global $conf;
413
    global $USERINFO;
414
    /* @var AuthPlugin $auth */
415
    global $auth;
416
    /* @var Input $INPUT */
417
    global $INPUT;
418
419
    // make sure the session is writable (it usually is)
420
    @session_start();
421
422
    if(isset($_SESSION[DOKU_COOKIE]['auth']['user']))
423
        unset($_SESSION[DOKU_COOKIE]['auth']['user']);
424
    if(isset($_SESSION[DOKU_COOKIE]['auth']['pass']))
425
        unset($_SESSION[DOKU_COOKIE]['auth']['pass']);
426
    if(isset($_SESSION[DOKU_COOKIE]['auth']['info']))
427
        unset($_SESSION[DOKU_COOKIE]['auth']['info']);
428
    if(!$keepbc && isset($_SESSION[DOKU_COOKIE]['bc']))
429
        unset($_SESSION[DOKU_COOKIE]['bc']);
430
    $INPUT->server->remove('REMOTE_USER');
431
    $USERINFO = null; //FIXME
432
433
    $cookieDir = empty($conf['cookiedir']) ? DOKU_REL : $conf['cookiedir'];
434
    setcookie(DOKU_COOKIE, '', time() - 600000, $cookieDir, '', ($conf['securecookie'] && is_ssl()), true);
435
436
    if($auth) $auth->logOff();
437
}
438
439
/**
440
 * Check if a user is a manager
441
 *
442
 * Should usually be called without any parameters to check the current
443
 * user.
444
 *
445
 * The info is available through $INFO['ismanager'], too
446
 *
447
 * @author Andreas Gohr <[email protected]>
448
 * @see    auth_isadmin
449
 *
450
 * @param  string $user       Username
451
 * @param  array  $groups     List of groups the user is in
452
 * @param  bool   $adminonly  when true checks if user is admin
453
 * @return bool
454
 */
455
function auth_ismanager($user = null, $groups = null, $adminonly = false) {
456
    global $conf;
457
    global $USERINFO;
458
    /* @var AuthPlugin $auth */
459
    global $auth;
460
    /* @var Input $INPUT */
461
    global $INPUT;
462
463
464
    if(!$auth) return false;
465
    if(is_null($user)) {
466
        if(!$INPUT->server->has('REMOTE_USER')) {
467
            return false;
468
        } else {
469
            $user = $INPUT->server->str('REMOTE_USER');
470
        }
471
    }
472
    if(is_null($groups)) {
473
        $groups = (array) $USERINFO['grps'];
474
    }
475
476
    // check superuser match
477
    if(auth_isMember($conf['superuser'], $user, $groups)) return true;
478
    if($adminonly) return false;
479
    // check managers
480
    if(auth_isMember($conf['manager'], $user, $groups)) return true;
481
482
    return false;
483
}
484
485
/**
486
 * Check if a user is admin
487
 *
488
 * Alias to auth_ismanager with adminonly=true
489
 *
490
 * The info is available through $INFO['isadmin'], too
491
 *
492
 * @author Andreas Gohr <[email protected]>
493
 * @see auth_ismanager()
494
 *
495
 * @param  string $user       Username
496
 * @param  array  $groups     List of groups the user is in
497
 * @return bool
498
 */
499
function auth_isadmin($user = null, $groups = null) {
500
    return auth_ismanager($user, $groups, true);
501
}
502
503
/**
504
 * Match a user and his groups against a comma separated list of
505
 * users and groups to determine membership status
506
 *
507
 * Note: all input should NOT be nameencoded.
508
 *
509
 * @param string $memberlist commaseparated list of allowed users and groups
510
 * @param string $user       user to match against
511
 * @param array  $groups     groups the user is member of
512
 * @return bool       true for membership acknowledged
513
 */
514
function auth_isMember($memberlist, $user, array $groups) {
515
    /* @var AuthPlugin $auth */
516
    global $auth;
517
    if(!$auth) return false;
518
519
    // clean user and groups
520
    if(!$auth->isCaseSensitive()) {
521
        $user   = \dokuwiki\Utf8\PhpString::strtolower($user);
522
        $groups = array_map('utf8_strtolower', $groups);
523
    }
524
    $user   = $auth->cleanUser($user);
525
    $groups = array_map(array($auth, 'cleanGroup'), $groups);
526
527
    // extract the memberlist
528
    $members = explode(',', $memberlist);
529
    $members = array_map('trim', $members);
530
    $members = array_unique($members);
531
    $members = array_filter($members);
532
533
    // compare cleaned values
534
    foreach($members as $member) {
535
        if($member == '@ALL' ) return true;
536
        if(!$auth->isCaseSensitive()) $member = \dokuwiki\Utf8\PhpString::strtolower($member);
537
        if($member[0] == '@') {
538
            $member = $auth->cleanGroup(substr($member, 1));
539
            if(in_array($member, $groups)) return true;
540
        } else {
541
            $member = $auth->cleanUser($member);
542
            if($member == $user) return true;
543
        }
544
    }
545
546
    // still here? not a member!
547
    return false;
548
}
549
550
/**
551
 * Convinience function for auth_aclcheck()
552
 *
553
 * This checks the permissions for the current user
554
 *
555
 * @author  Andreas Gohr <[email protected]>
556
 *
557
 * @param  string  $id  page ID (needs to be resolved and cleaned)
558
 * @return int          permission level
559
 */
560
function auth_quickaclcheck($id) {
561
    global $conf;
562
    global $USERINFO;
563
    /* @var Input $INPUT */
564
    global $INPUT;
565
    # if no ACL is used always return upload rights
566
    if(!$conf['useacl']) return AUTH_UPLOAD;
567
    return auth_aclcheck($id, $INPUT->server->str('REMOTE_USER'), $USERINFO['grps']);
568
}
569
570
/**
571
 * Returns the maximum rights a user has for the given ID or its namespace
572
 *
573
 * @author  Andreas Gohr <[email protected]>
574
 *
575
 * @triggers AUTH_ACL_CHECK
576
 * @param  string       $id     page ID (needs to be resolved and cleaned)
577
 * @param  string       $user   Username
578
 * @param  array|null   $groups Array of groups the user is in
579
 * @return int             permission level
580
 */
581
function auth_aclcheck($id, $user, $groups) {
582
    $data = array(
583
        'id'     => $id,
584
        'user'   => $user,
585
        'groups' => $groups
586
    );
587
588
    return Event::createAndTrigger('AUTH_ACL_CHECK', $data, 'auth_aclcheck_cb');
589
}
590
591
/**
592
 * default ACL check method
593
 *
594
 * DO NOT CALL DIRECTLY, use auth_aclcheck() instead
595
 *
596
 * @author  Andreas Gohr <[email protected]>
597
 *
598
 * @param  array $data event data
599
 * @return int   permission level
600
 */
601
function auth_aclcheck_cb($data) {
602
    $id     =& $data['id'];
603
    $user   =& $data['user'];
604
    $groups =& $data['groups'];
605
606
    global $conf;
607
    global $AUTH_ACL;
608
    /* @var AuthPlugin $auth */
609
    global $auth;
610
611
    // if no ACL is used always return upload rights
612
    if(!$conf['useacl']) return AUTH_UPLOAD;
613
    if(!$auth) return AUTH_NONE;
614
615
    //make sure groups is an array
616
    if(!is_array($groups)) $groups = array();
617
618
    //if user is superuser or in superusergroup return 255 (acl_admin)
619
    if(auth_isadmin($user, $groups)) {
620
        return AUTH_ADMIN;
621
    }
622
623
    if(!$auth->isCaseSensitive()) {
624
        $user   = \dokuwiki\Utf8\PhpString::strtolower($user);
625
        $groups = array_map('utf8_strtolower', $groups);
626
    }
627
    $user   = auth_nameencode($auth->cleanUser($user));
628
    $groups = array_map(array($auth, 'cleanGroup'), (array) $groups);
629
630
    //prepend groups with @ and nameencode
631
    foreach($groups as &$group) {
632
        $group = '@'.auth_nameencode($group);
633
    }
634
635
    $ns   = getNS($id);
636
    $perm = -1;
637
638
    //add ALL group
639
    $groups[] = '@ALL';
640
641
    //add User
642
    if($user) $groups[] = $user;
643
644
    //check exact match first
645
    $matches = preg_grep('/^'.preg_quote($id, '/').'[ \t]+([^ \t]+)[ \t]+/', $AUTH_ACL);
646
    if(count($matches)) {
647
        foreach($matches as $match) {
648
            $match = preg_replace('/#.*$/', '', $match); //ignore comments
649
            $acl   = preg_split('/[ \t]+/', $match);
650
            if(!$auth->isCaseSensitive() && $acl[1] !== '@ALL') {
651
                $acl[1] = \dokuwiki\Utf8\PhpString::strtolower($acl[1]);
652
            }
653
            if(!in_array($acl[1], $groups)) {
654
                continue;
655
            }
656
            if($acl[2] > AUTH_DELETE) $acl[2] = AUTH_DELETE; //no admins in the ACL!
657
            if($acl[2] > $perm) {
658
                $perm = $acl[2];
659
            }
660
        }
661
        if($perm > -1) {
662
            //we had a match - return it
663
            return (int) $perm;
664
        }
665
    }
666
667
    //still here? do the namespace checks
668
    if($ns) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $ns of type string|false is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
669
        $path = $ns.':*';
670
    } else {
671
        $path = '*'; //root document
672
    }
673
674
    do {
675
        $matches = preg_grep('/^'.preg_quote($path, '/').'[ \t]+([^ \t]+)[ \t]+/', $AUTH_ACL);
676
        if(count($matches)) {
677
            foreach($matches as $match) {
678
                $match = preg_replace('/#.*$/', '', $match); //ignore comments
679
                $acl   = preg_split('/[ \t]+/', $match);
680
                if(!$auth->isCaseSensitive() && $acl[1] !== '@ALL') {
681
                    $acl[1] = \dokuwiki\Utf8\PhpString::strtolower($acl[1]);
682
                }
683
                if(!in_array($acl[1], $groups)) {
684
                    continue;
685
                }
686
                if($acl[2] > AUTH_DELETE) $acl[2] = AUTH_DELETE; //no admins in the ACL!
687
                if($acl[2] > $perm) {
688
                    $perm = $acl[2];
689
                }
690
            }
691
            //we had a match - return it
692
            if($perm != -1) {
693
                return (int) $perm;
694
            }
695
        }
696
        //get next higher namespace
697
        $ns = getNS($ns);
0 ignored issues
show
Security Bug introduced by
It seems like $ns defined by getNS($ns) on line 697 can also be of type false; however, getNS() does only seem to accept string, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
698
699
        if($path != '*') {
700
            $path = $ns.':*';
701
            if($path == ':*') $path = '*';
702
        } else {
703
            //we did this already
704
            //looks like there is something wrong with the ACL
705
            //break here
706
            msg('No ACL setup yet! Denying access to everyone.');
707
            return AUTH_NONE;
708
        }
709
    } while(1); //this should never loop endless
710
    return AUTH_NONE;
711
}
712
713
/**
714
 * Encode ASCII special chars
715
 *
716
 * Some auth backends allow special chars in their user and groupnames
717
 * The special chars are encoded with this function. Only ASCII chars
718
 * are encoded UTF-8 multibyte are left as is (different from usual
719
 * urlencoding!).
720
 *
721
 * Decoding can be done with rawurldecode
722
 *
723
 * @author Andreas Gohr <[email protected]>
724
 * @see rawurldecode()
725
 *
726
 * @param string $name
727
 * @param bool $skip_group
728
 * @return string
729
 */
730
function auth_nameencode($name, $skip_group = false) {
731
    global $cache_authname;
732
    $cache =& $cache_authname;
733
    $name  = (string) $name;
734
735
    // never encode wildcard FS#1955
736
    if($name == '%USER%') return $name;
737
    if($name == '%GROUP%') return $name;
738
739
    if(!isset($cache[$name][$skip_group])) {
740
        if($skip_group && $name{0} == '@') {
741
            $cache[$name][$skip_group] = '@'.preg_replace_callback(
742
                '/([\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\x7f])/',
743
                'auth_nameencode_callback', substr($name, 1)
744
            );
745
        } else {
746
            $cache[$name][$skip_group] = preg_replace_callback(
747
                '/([\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\x7f])/',
748
                'auth_nameencode_callback', $name
749
            );
750
        }
751
    }
752
753
    return $cache[$name][$skip_group];
754
}
755
756
/**
757
 * callback encodes the matches
758
 *
759
 * @param array $matches first complete match, next matching subpatterms
760
 * @return string
761
 */
762
function auth_nameencode_callback($matches) {
763
    return '%'.dechex(ord(substr($matches[1],-1)));
764
}
765
766
/**
767
 * Create a pronouncable password
768
 *
769
 * The $foruser variable might be used by plugins to run additional password
770
 * policy checks, but is not used by the default implementation
771
 *
772
 * @author   Andreas Gohr <[email protected]>
773
 * @link     http://www.phpbuilder.com/annotate/message.php3?id=1014451
774
 * @triggers AUTH_PASSWORD_GENERATE
775
 *
776
 * @param  string $foruser username for which the password is generated
777
 * @return string  pronouncable password
778
 */
779
function auth_pwgen($foruser = '') {
780
    $data = array(
781
        'password' => '',
782
        'foruser'  => $foruser
783
    );
784
785
    $evt = new Event('AUTH_PASSWORD_GENERATE', $data);
786
    if($evt->advise_before(true)) {
787
        $c = 'bcdfghjklmnprstvwz'; //consonants except hard to speak ones
788
        $v = 'aeiou'; //vowels
789
        $a = $c.$v; //both
790
        $s = '!$%&?+*~#-_:.;,'; // specials
791
792
        //use thre syllables...
793
        for($i = 0; $i < 3; $i++) {
794
            $data['password'] .= $c[auth_random(0, strlen($c) - 1)];
795
            $data['password'] .= $v[auth_random(0, strlen($v) - 1)];
796
            $data['password'] .= $a[auth_random(0, strlen($a) - 1)];
797
        }
798
        //... and add a nice number and special
799
        $data['password'] .= $s[auth_random(0, strlen($s) - 1)].auth_random(10, 99);
800
    }
801
    $evt->advise_after();
802
803
    return $data['password'];
804
}
805
806
/**
807
 * Sends a password to the given user
808
 *
809
 * @author  Andreas Gohr <[email protected]>
810
 *
811
 * @param string $user Login name of the user
812
 * @param string $password The new password in clear text
813
 * @return bool  true on success
814
 */
815
function auth_sendPassword($user, $password) {
816
    global $lang;
817
    /* @var AuthPlugin $auth */
818
    global $auth;
819
    if(!$auth) return false;
820
821
    $user     = $auth->cleanUser($user);
822
    $userinfo = $auth->getUserData($user, $requireGroups = false);
823
824
    if(!$userinfo['mail']) return false;
825
826
    $text = rawLocale('password');
827
    $trep = array(
828
        'FULLNAME' => $userinfo['name'],
829
        'LOGIN'    => $user,
830
        'PASSWORD' => $password
831
    );
832
833
    $mail = new Mailer();
834
    $mail->to($mail->getCleanName($userinfo['name']).' <'.$userinfo['mail'].'>');
835
    $mail->subject($lang['regpwmail']);
836
    $mail->setBody($text, $trep);
837
    return $mail->send();
838
}
839
840
/**
841
 * Register a new user
842
 *
843
 * This registers a new user - Data is read directly from $_POST
844
 *
845
 * @author  Andreas Gohr <[email protected]>
846
 *
847
 * @return bool  true on success, false on any error
848
 */
849
function register() {
850
    global $lang;
851
    global $conf;
852
    /* @var \dokuwiki\Extension\AuthPlugin $auth */
853
    global $auth;
854
    global $INPUT;
855
856
    if(!$INPUT->post->bool('save')) return false;
857
    if(!actionOK('register')) return false;
858
859
    // gather input
860
    $login    = trim($auth->cleanUser($INPUT->post->str('login')));
861
    $fullname = trim(preg_replace('/[\x00-\x1f:<>&%,;]+/', '', $INPUT->post->str('fullname')));
862
    $email    = trim(preg_replace('/[\x00-\x1f:<>&%,;]+/', '', $INPUT->post->str('email')));
863
    $pass     = $INPUT->post->str('pass');
864
    $passchk  = $INPUT->post->str('passchk');
865
866
    if(empty($login) || empty($fullname) || empty($email)) {
867
        msg($lang['regmissing'], -1);
868
        return false;
869
    }
870
871
    if($conf['autopasswd']) {
872
        $pass = auth_pwgen($login); // automatically generate password
873
    } elseif(empty($pass) || empty($passchk)) {
874
        msg($lang['regmissing'], -1); // complain about missing passwords
875
        return false;
876
    } elseif($pass != $passchk) {
877
        msg($lang['regbadpass'], -1); // complain about misspelled passwords
878
        return false;
879
    }
880
881
    //check mail
882
    if(!mail_isvalid($email)) {
883
        msg($lang['regbadmail'], -1);
884
        return false;
885
    }
886
887
    //okay try to create the user
888
    if(!$auth->triggerUserMod('create', array($login, $pass, $fullname, $email))) {
889
        msg($lang['regfail'], -1);
890
        return false;
891
    }
892
893
    // send notification about the new user
894
    $subscription = new RegistrationSubscriptionSender();
895
    $subscription->sendRegister($login, $fullname, $email);
896
897
    // are we done?
898
    if(!$conf['autopasswd']) {
899
        msg($lang['regsuccess2'], 1);
900
        return true;
901
    }
902
903
    // autogenerated password? then send password to user
904
    if(auth_sendPassword($login, $pass)) {
905
        msg($lang['regsuccess'], 1);
906
        return true;
907
    } else {
908
        msg($lang['regmailfail'], -1);
909
        return false;
910
    }
911
}
912
913
/**
914
 * Update user profile
915
 *
916
 * @author    Christopher Smith <[email protected]>
917
 */
918
function updateprofile() {
919
    global $conf;
920
    global $lang;
921
    /* @var AuthPlugin $auth */
922
    global $auth;
923
    /* @var Input $INPUT */
924
    global $INPUT;
925
926
    if(!$INPUT->post->bool('save')) return false;
927
    if(!checkSecurityToken()) return false;
928
929
    if(!actionOK('profile')) {
930
        msg($lang['profna'], -1);
931
        return false;
932
    }
933
934
    $changes         = array();
935
    $changes['pass'] = $INPUT->post->str('newpass');
936
    $changes['name'] = $INPUT->post->str('fullname');
937
    $changes['mail'] = $INPUT->post->str('email');
938
939
    // check misspelled passwords
940
    if($changes['pass'] != $INPUT->post->str('passchk')) {
941
        msg($lang['regbadpass'], -1);
942
        return false;
943
    }
944
945
    // clean fullname and email
946
    $changes['name'] = trim(preg_replace('/[\x00-\x1f:<>&%,;]+/', '', $changes['name']));
947
    $changes['mail'] = trim(preg_replace('/[\x00-\x1f:<>&%,;]+/', '', $changes['mail']));
948
949
    // no empty name and email (except the backend doesn't support them)
950
    if((empty($changes['name']) && $auth->canDo('modName')) ||
951
        (empty($changes['mail']) && $auth->canDo('modMail'))
952
    ) {
953
        msg($lang['profnoempty'], -1);
954
        return false;
955
    }
956
    if(!mail_isvalid($changes['mail']) && $auth->canDo('modMail')) {
957
        msg($lang['regbadmail'], -1);
958
        return false;
959
    }
960
961
    $changes = array_filter($changes);
962
963
    // check for unavailable capabilities
964
    if(!$auth->canDo('modName')) unset($changes['name']);
965
    if(!$auth->canDo('modMail')) unset($changes['mail']);
966
    if(!$auth->canDo('modPass')) unset($changes['pass']);
967
968
    // anything to do?
969
    if(!count($changes)) {
970
        msg($lang['profnochange'], -1);
971
        return false;
972
    }
973
974
    if($conf['profileconfirm']) {
975
        if(!$auth->checkPass($INPUT->server->str('REMOTE_USER'), $INPUT->post->str('oldpass'))) {
976
            msg($lang['badpassconfirm'], -1);
977
            return false;
978
        }
979
    }
980
981
    if(!$auth->triggerUserMod('modify', array($INPUT->server->str('REMOTE_USER'), &$changes))) {
982
        msg($lang['proffail'], -1);
983
        return false;
984
    }
985
986
    if($changes['pass']) {
987
        // update cookie and session with the changed data
988
        list( /*user*/, $sticky, /*pass*/) = auth_getCookie();
989
        $pass = auth_encrypt($changes['pass'], auth_cookiesalt(!$sticky, true));
0 ignored issues
show
Bug introduced by
It seems like auth_cookiesalt(!$sticky, true) targeting auth_cookiesalt() can also be of type boolean; however, auth_encrypt() does only seem to accept string, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
990
        auth_setCookie($INPUT->server->str('REMOTE_USER'), $pass, (bool) $sticky);
991
    } else {
992
        // make sure the session is writable
993
        @session_start();
994
        // invalidate session cache
995
        $_SESSION[DOKU_COOKIE]['auth']['time'] = 0;
996
        session_write_close();
997
    }
998
999
    return true;
1000
}
1001
1002
/**
1003
 * Delete the current logged-in user
1004
 *
1005
 * @return bool true on success, false on any error
1006
 */
1007
function auth_deleteprofile(){
1008
    global $conf;
1009
    global $lang;
1010
    /* @var \dokuwiki\Extension\AuthPlugin $auth */
1011
    global $auth;
1012
    /* @var Input $INPUT */
1013
    global $INPUT;
1014
1015
    if(!$INPUT->post->bool('delete')) return false;
1016
    if(!checkSecurityToken()) return false;
1017
1018
    // action prevented or auth module disallows
1019
    if(!actionOK('profile_delete') || !$auth->canDo('delUser')) {
1020
        msg($lang['profnodelete'], -1);
1021
        return false;
1022
    }
1023
1024
    if(!$INPUT->post->bool('confirm_delete')){
1025
        msg($lang['profconfdeletemissing'], -1);
1026
        return false;
1027
    }
1028
1029
    if($conf['profileconfirm']) {
1030
        if(!$auth->checkPass($INPUT->server->str('REMOTE_USER'), $INPUT->post->str('oldpass'))) {
1031
            msg($lang['badpassconfirm'], -1);
1032
            return false;
1033
        }
1034
    }
1035
1036
    $deleted = array();
1037
    $deleted[] = $INPUT->server->str('REMOTE_USER');
1038
    if($auth->triggerUserMod('delete', array($deleted))) {
1039
        // force and immediate logout including removing the sticky cookie
1040
        auth_logoff();
1041
        return true;
1042
    }
1043
1044
    return false;
1045
}
1046
1047
/**
1048
 * Send a  new password
1049
 *
1050
 * This function handles both phases of the password reset:
1051
 *
1052
 *   - handling the first request of password reset
1053
 *   - validating the password reset auth token
1054
 *
1055
 * @author Benoit Chesneau <[email protected]>
1056
 * @author Chris Smith <[email protected]>
1057
 * @author Andreas Gohr <[email protected]>
1058
 *
1059
 * @return bool true on success, false on any error
1060
 */
1061
function act_resendpwd() {
1062
    global $lang;
1063
    global $conf;
1064
    /* @var AuthPlugin $auth */
1065
    global $auth;
1066
    /* @var Input $INPUT */
1067
    global $INPUT;
1068
1069
    if(!actionOK('resendpwd')) {
1070
        msg($lang['resendna'], -1);
1071
        return false;
1072
    }
1073
1074
    $token = preg_replace('/[^a-f0-9]+/', '', $INPUT->str('pwauth'));
1075
1076
    if($token) {
1077
        // we're in token phase - get user info from token
1078
1079
        $tfile = $conf['cachedir'].'/'.$token{0}.'/'.$token.'.pwauth';
1080
        if(!file_exists($tfile)) {
1081
            msg($lang['resendpwdbadauth'], -1);
1082
            $INPUT->remove('pwauth');
1083
            return false;
1084
        }
1085
        // token is only valid for 3 days
1086
        if((time() - filemtime($tfile)) > (3 * 60 * 60 * 24)) {
1087
            msg($lang['resendpwdbadauth'], -1);
1088
            $INPUT->remove('pwauth');
1089
            @unlink($tfile);
1090
            return false;
1091
        }
1092
1093
        $user     = io_readfile($tfile);
1094
        $userinfo = $auth->getUserData($user, $requireGroups = false);
1095
        if(!$userinfo['mail']) {
1096
            msg($lang['resendpwdnouser'], -1);
1097
            return false;
1098
        }
1099
1100
        if(!$conf['autopasswd']) { // we let the user choose a password
1101
            $pass = $INPUT->str('pass');
1102
1103
            // password given correctly?
1104
            if(!$pass) return false;
1105
            if($pass != $INPUT->str('passchk')) {
1106
                msg($lang['regbadpass'], -1);
1107
                return false;
1108
            }
1109
1110
            // change it
1111
            if(!$auth->triggerUserMod('modify', array($user, array('pass' => $pass)))) {
1112
                msg($lang['proffail'], -1);
1113
                return false;
1114
            }
1115
1116
        } else { // autogenerate the password and send by mail
1117
1118
            $pass = auth_pwgen($user);
1119
            if(!$auth->triggerUserMod('modify', array($user, array('pass' => $pass)))) {
1120
                msg($lang['proffail'], -1);
1121
                return false;
1122
            }
1123
1124
            if(auth_sendPassword($user, $pass)) {
1125
                msg($lang['resendpwdsuccess'], 1);
1126
            } else {
1127
                msg($lang['regmailfail'], -1);
1128
            }
1129
        }
1130
1131
        @unlink($tfile);
1132
        return true;
1133
1134
    } else {
1135
        // we're in request phase
1136
1137
        if(!$INPUT->post->bool('save')) return false;
1138
1139
        if(!$INPUT->post->str('login')) {
1140
            msg($lang['resendpwdmissing'], -1);
1141
            return false;
1142
        } else {
1143
            $user = trim($auth->cleanUser($INPUT->post->str('login')));
1144
        }
1145
1146
        $userinfo = $auth->getUserData($user, $requireGroups = false);
1147
        if(!$userinfo['mail']) {
1148
            msg($lang['resendpwdnouser'], -1);
1149
            return false;
1150
        }
1151
1152
        // generate auth token
1153
        $token = md5(auth_randombytes(16)); // random secret
1154
        $tfile = $conf['cachedir'].'/'.$token{0}.'/'.$token.'.pwauth';
1155
        $url   = wl('', array('do'=> 'resendpwd', 'pwauth'=> $token), true, '&');
1156
1157
        io_saveFile($tfile, $user);
1158
1159
        $text = rawLocale('pwconfirm');
1160
        $trep = array(
1161
            'FULLNAME' => $userinfo['name'],
1162
            'LOGIN'    => $user,
1163
            'CONFIRM'  => $url
1164
        );
1165
1166
        $mail = new Mailer();
1167
        $mail->to($userinfo['name'].' <'.$userinfo['mail'].'>');
1168
        $mail->subject($lang['regpwmail']);
1169
        $mail->setBody($text, $trep);
1170
        if($mail->send()) {
1171
            msg($lang['resendpwdconfirm'], 1);
1172
        } else {
1173
            msg($lang['regmailfail'], -1);
1174
        }
1175
        return true;
1176
    }
1177
    // never reached
1178
}
1179
1180
/**
1181
 * Encrypts a password using the given method and salt
1182
 *
1183
 * If the selected method needs a salt and none was given, a random one
1184
 * is chosen.
1185
 *
1186
 * @author  Andreas Gohr <[email protected]>
1187
 *
1188
 * @param string $clear The clear text password
1189
 * @param string $method The hashing method
1190
 * @param string $salt A salt, null for random
1191
 * @return  string  The crypted password
1192
 */
1193
function auth_cryptPassword($clear, $method = '', $salt = null) {
1194
    global $conf;
1195
    if(empty($method)) $method = $conf['passcrypt'];
1196
1197
    $pass = new PassHash();
1198
    $call = 'hash_'.$method;
1199
1200
    if(!method_exists($pass, $call)) {
1201
        msg("Unsupported crypt method $method", -1);
1202
        return false;
1203
    }
1204
1205
    return $pass->$call($clear, $salt);
1206
}
1207
1208
/**
1209
 * Verifies a cleartext password against a crypted hash
1210
 *
1211
 * @author Andreas Gohr <[email protected]>
1212
 *
1213
 * @param  string $clear The clear text password
1214
 * @param  string $crypt The hash to compare with
1215
 * @return bool true if both match
1216
 */
1217
function auth_verifyPassword($clear, $crypt) {
1218
    $pass = new PassHash();
1219
    return $pass->verify_hash($clear, $crypt);
1220
}
1221
1222
/**
1223
 * Set the authentication cookie and add user identification data to the session
1224
 *
1225
 * @param string  $user       username
1226
 * @param string  $pass       encrypted password
1227
 * @param bool    $sticky     whether or not the cookie will last beyond the session
1228
 * @return bool
1229
 */
1230
function auth_setCookie($user, $pass, $sticky) {
1231
    global $conf;
1232
    /* @var AuthPlugin $auth */
1233
    global $auth;
1234
    global $USERINFO;
1235
1236
    if(!$auth) return false;
1237
    $USERINFO = $auth->getUserData($user);
1238
1239
    // set cookie
1240
    $cookie    = base64_encode($user).'|'.((int) $sticky).'|'.base64_encode($pass);
1241
    $cookieDir = empty($conf['cookiedir']) ? DOKU_REL : $conf['cookiedir'];
1242
    $time      = $sticky ? (time() + 60 * 60 * 24 * 365) : 0; //one year
1243
    setcookie(DOKU_COOKIE, $cookie, $time, $cookieDir, '', ($conf['securecookie'] && is_ssl()), true);
1244
1245
    // set session
1246
    $_SESSION[DOKU_COOKIE]['auth']['user'] = $user;
1247
    $_SESSION[DOKU_COOKIE]['auth']['pass'] = sha1($pass);
1248
    $_SESSION[DOKU_COOKIE]['auth']['buid'] = auth_browseruid();
1249
    $_SESSION[DOKU_COOKIE]['auth']['info'] = $USERINFO;
1250
    $_SESSION[DOKU_COOKIE]['auth']['time'] = time();
1251
1252
    return true;
1253
}
1254
1255
/**
1256
 * Returns the user, (encrypted) password and sticky bit from cookie
1257
 *
1258
 * @returns array
1259
 */
1260
function auth_getCookie() {
1261
    if(!isset($_COOKIE[DOKU_COOKIE])) {
1262
        return array(null, null, null);
1263
    }
1264
    list($user, $sticky, $pass) = explode('|', $_COOKIE[DOKU_COOKIE], 3);
1265
    $sticky = (bool) $sticky;
1266
    $pass   = base64_decode($pass);
1267
    $user   = base64_decode($user);
1268
    return array($user, $sticky, $pass);
1269
}
1270
1271
//Setup VIM: ex: et ts=2 :
1272