1<?php
2/**
3 * DokuWiki Plugin authucenter (Auth Component)
4 *
5 * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
6 * @author  daxingplay <daxingplay@gmail.com>
7 */
8
9// must be run within Dokuwiki
10if(!defined('DOKU_INC')) die();
11if(!defined('DOKU_PLUGIN_AUTHUCENTER_INC')) define('DOKU_PLUGIN_AUTHUCENTER_INC', dirname(__FILE__).'/');
12
13class auth_plugin_authucenter extends DokuWiki_Auth_Plugin {
14
15
16    /**
17     * Constructor.
18     */
19    public function __construct() {
20        parent::__construct(); // for compatibility
21
22        if (!is_dir(DOKU_INC . 'uc_client')) {
23            $this->_recurse_copy(DOKU_PLUGIN_AUTHUCENTER_INC . 'lib/uc_client', DOKU_INC);
24        }
25
26        if (!file_exists(DOKU_INC . 'api/uc.php')) {
27            $this->_recurse_copy(DOKU_PLUGIN_AUTHUCENTER_INC . 'lib/api', DOKU_INC);
28        }
29
30        if (!file_exists(DOKU_INC . 'conf/uc.auth.php') || $this->getConf('regenerateconfig')) {
31            if ($this->_generate_conf() === false) {
32                $this->success = false;
33                return;
34            }
35        }
36
37        if (!file_exists(DOKU_INC . 'api/uc.php') || !is_dir(DOKU_INC . 'uc_client') || !file_exists(DOKU_INC . 'conf/uc.auth.php') || !file_exists(DOKU_INC . 'uc_client/client.php')) {
38            msg($this->getLang('ucfilecheckfail'), -1);
39            $this->success = false;
40            return;
41        }
42
43        require_once(DOKU_INC.'conf/uc.auth.php');
44        require_once(DOKU_INC.'uc_client/client.php');
45
46        // FIXME set capabilities accordingly
47        $this->cando['addUser']     = false; // can Users be created?
48        $this->cando['delUser']     = false; // can Users be deleted?
49        $this->cando['modLogin']    = false; // can login names be changed?
50        $this->cando['modPass']     = false; // can passwords be changed?
51        $this->cando['modName']     = false; // can real names be changed?
52        $this->cando['modMail']     = false; // can emails be changed?
53        $this->cando['modGroups']   = false; // can groups be changed?
54        $this->cando['getUsers']    = false; // can a (filtered) list of users be retrieved?
55        $this->cando['getUserCount']= false; // can the number of users be retrieved?
56        $this->cando['getGroups']   = false; // can a list of available groups be retrieved?
57        $this->cando['external']    = true; // does the module do external auth checking?
58        $this->cando['logout']      = true; // can the user logout again? (eg. not possible with HTTP auth)
59
60        // FIXME intialize your auth system and set success to true, if successful
61        $this->success = true;
62    }
63
64
65    /**
66     * Log off the current user [ OPTIONAL ]
67     */
68    public function logOff() {
69        $this->_uc_setcookie($this->getConf('cookiename'), '', -1);
70        uc_user_synlogout();
71        msg($this->getLang('logoutsuccess'), 0);
72    }
73
74    /**
75     * Do all authentication [ OPTIONAL ]
76     *
77     * @param   string  $user    Username
78     * @param   string  $pass    Cleartext Password
79     * @param   bool    $sticky  Cookie should not expire
80     * @return  bool             true on successful auth
81     */
82    public function trustExternal($user, $pass, $sticky = false) {
83        global $USERINFO;
84        $sticky ? $sticky = true : $sticky = false; //sanity check
85
86        // do the checking here
87        $uid = '';
88        $username = '';
89        $password = '';
90        $email = '';
91        $checked = false;
92        $user_info = array();
93
94        if(!empty($user)){
95            list($uid, $username, $password, $email) = $this->_uc_user_login($user, $pass);
96            setcookie($this->getConf('cookiename'), '', -86400);
97            if($uid > 0){
98                $_SERVER['REMOTE_USER'] = $username;
99                $user_info = $this->_uc_get_user_full($uid, 1);
100                $password = $user_info['password'];
101                $this->_uc_setcookie($this->getConf('cookiename'), uc_authcode($uid."\t".$user_info['password']."\t".$this->_convert_charset($username), 'ENCODE'));
102                uc_user_synlogin($uid);
103                $checked = true;
104            }else{
105                msg($this->getLang('loginfail'), -1);
106                $checked = false;
107            }
108        }else{
109            $cookie = $_COOKIE[$this->getConf('cookiename')];
110            if(!empty($cookie)){
111                // use password check instead of username check.
112                list($uid, $password, $username) = explode("\t", uc_authcode($cookie, 'DECODE'));
113                $username = $this->_convert_charset($username, 0);
114                if($password && $uid && $username){
115                    // get session info
116                    $session = $_SESSION[DOKU_COOKIE]['auth'];
117                    if(isset($session) && $session['user'] == $username && $session['password'] == $password){
118                        $user_info = $session['info'];
119                        $user = $user_info['name'];
120                        $email = $user_info['mail'];
121                        $group = $user_info['grps'];
122                        $checked = true;
123                    }else{
124                        $user_info = $this->_uc_get_user_full($uid, 1);
125                        if($uid == $user_info['uid'] && $password == $user_info['password']){
126                            // he has logged in from other uc apps
127                            $user = $user_info['username'];
128                            $email = $user_info['email'];
129                            $checked = true;
130                        }
131                    }
132
133                }
134            }
135        }
136
137        if($checked == true){
138            $USERINFO['name'] = $user;
139            $USERINFO['mail'] = $email;
140            $USERINFO['grps'] = array('user');
141            $_SERVER['REMOTE_USER'] = $user;
142            $_SESSION[DOKU_COOKIE]['auth']['user'] = $user;
143            $_SESSION[DOKU_COOKIE]['auth']['pass'] = $pass;
144            $_SESSION[DOKU_COOKIE]['auth']['password'] = $password;
145            $_SESSION[DOKU_COOKIE]['auth']['info'] = $USERINFO;
146        }
147        return $checked;
148    }
149
150    /**
151     * Check user+password
152     *
153     * May be ommited if trustExternal is used.
154     *
155     * @param   string $user the user name
156     * @param   string $pass the clear text password
157     * @return  bool
158     */
159    public function checkPass($user, $pass) {
160        return $this->_uc_user_login($user, $pass); // return true if okay
161    }
162
163    /**
164     * Return user info
165     *
166     * Returns info about the given user needs to contain
167     * at least these fields:
168     *
169     * name string  full name of the user
170     * mail string  email addres of the user
171     * grps array   list of groups the user is in
172     *
173     * @param   string $user the user name
174     * @return  array containing user data or false
175     */
176    public function getUserData($user) {
177        $user_info = false;
178        if($data = $this->_uc_get_user($user)){
179            list($uid, $username, $email) = $data;
180            $user_info = array(
181                'name' => $username,
182                'mail' => $email,
183                'grps' => $this->_get_user_group($uid, 1),
184                'uid' => $uid
185            );
186        }
187        return $user_info;
188    }
189
190    /**
191     * Create a new User [implement only where required/possible]
192     *
193     * Returns false if the user already exists, null when an error
194     * occurred and true if everything went well.
195     *
196     * The new user HAS TO be added to the default group by this
197     * function!
198     *
199     * Set addUser capability when implemented
200     *
201     * @param  string     $user
202     * @param  string     $pass
203     * @param  string     $name
204     * @param  string     $mail
205     * @param  null|array $grps
206     * @return bool|null
207     */
208    public function createUser($user, $pass, $name, $mail, $grps = null) {
209        return $this->_uc_user_register($user, $pass, $mail);
210    }
211
212    /**
213     * Modify user data [implement only where required/possible]
214     *
215     * Set the mod* capabilities according to the implemented features
216     *
217     * @param   string $user    nick of the user to be changed
218     * @param   array  $changes array of field/value pairs to be changed (password will be clear text)
219     * @return  bool
220     */
221    public function modifyUser($user, $changes) {
222        if(!is_array($changes) || !count($changes)){
223            return true;
224        }
225        $ucresult = $this->_uc_user_edit($user, $_POST['oldpass'], $changes['pass'] ? $changes['pass'] : '', $changes['mail'] ? $changes['mail'] : '');
226        $msg = '';
227        switch($ucresult){
228            case 1:
229            case 0:
230            case -7:
231                return true;
232                break;
233            case -1:
234                $msg = 'wrongpassword';
235                break;
236            case -4:
237                $msg = 'wrongemailformat';
238                break;
239            case -5:
240                $msg = 'emailforbidden';
241                break;
242            case -6:
243                $msg = 'emailregistered';
244                break;
245            case -8:
246                $msg = 'userprotected';
247                break;
248        }
249        msg($this->getLang($msg), -1);
250        return false;
251    }
252
253    /**
254     * Delete one or more users [implement only where required/possible]
255     *
256     * Set delUser capability when implemented
257     *
258     * @param   array  $users
259     * @return  int    number of users deleted
260     */
261    public function deleteUsers($users) {
262        $count = 0;
263        if(is_array($users) && count($users)){
264            foreach($users as $user){
265                $uid = $this->get_uid($user);
266                if($uid && uc_user_delete($uid)){
267                    $count++;
268                }
269            }
270        }
271        return $count;
272    }
273
274    /**
275     * Bulk retrieval of user data [implement only where required/possible]
276     *
277     * Set getUsers capability when implemented
278     *
279     * @param   int   $start     index of first user to be returned
280     * @param   int   $limit     max number of users to be returned
281     * @param   array $filter    array of field/pattern pairs, null for no filter
282     * @return  array list of userinfo (refer getUserData for internal userinfo details)
283     */
284    //public function retrieveUsers($start = 0, $limit = -1, $filter = null) {
285        // FIXME implement
286    //    return array();
287    //}
288
289    /**
290     * Return a count of the number of user which meet $filter criteria
291     * [should be implemented whenever retrieveUsers is implemented]
292     *
293     * Set getUserCount capability when implemented
294     *
295     * @param  array $filter array of field/pattern pairs, empty array for no filter
296     * @return int
297     */
298    //public function getUserCount($filter = array()) {
299        // FIXME implement
300    //    return 0;
301    //}
302
303    /**
304     * Define a group [implement only where required/possible]
305     *
306     * Set addGroup capability when implemented
307     *
308     * @param   string $group
309     * @return  bool
310     */
311    //public function addGroup($group) {
312        // FIXME implement
313    //    return false;
314    //}
315
316    /**
317     * Retrieve groups [implement only where required/possible]
318     *
319     * Set getGroups capability when implemented
320     *
321     * @param   int $start
322     * @param   int $limit
323     * @return  array
324     */
325    //public function retrieveGroups($start = 0, $limit = 0) {
326        // FIXME implement
327    //    return array();
328    //}
329
330    /**
331     * Return case sensitivity of the backend
332     *
333     * When your backend is caseinsensitive (eg. you can login with USER and
334     * user) then you need to overwrite this method and return false
335     *
336     * @return bool
337     */
338    public function isCaseSensitive() {
339        return true;
340    }
341
342    /**
343     * Sanitize a given username
344     *
345     * This function is applied to any user name that is given to
346     * the backend and should also be applied to any user name within
347     * the backend before returning it somewhere.
348     *
349     * This should be used to enforce username restrictions.
350     *
351     * @param string $user username
352     * @return string the cleaned username
353     */
354    public function cleanUser($user) {
355        return $user;
356    }
357
358    /**
359     * Sanitize a given groupname
360     *
361     * This function is applied to any groupname that is given to
362     * the backend and should also be applied to any groupname within
363     * the backend before returning it somewhere.
364     *
365     * This should be used to enforce groupname restrictions.
366     *
367     * Groupnames are to be passed without a leading '@' here.
368     *
369     * @param  string $group groupname
370     * @return string the cleaned groupname
371     */
372    public function cleanGroup($group) {
373        return $group;
374    }
375
376    /**
377     * Check Session Cache validity [implement only where required/possible]
378     *
379     * DokuWiki caches user info in the user's session for the timespan defined
380     * in $conf['auth_security_timeout'].
381     *
382     * This makes sure slow authentication backends do not slow down DokuWiki.
383     * This also means that changes to the user database will not be reflected
384     * on currently logged in users.
385     *
386     * To accommodate for this, the user manager plugin will touch a reference
387     * file whenever a change is submitted. This function compares the filetime
388     * of this reference file with the time stored in the session.
389     *
390     * This reference file mechanism does not reflect changes done directly in
391     * the backend's database through other means than the user manager plugin.
392     *
393     * Fast backends might want to return always false, to force rechecks on
394     * each page load. Others might want to use their own checking here. If
395     * unsure, do not override.
396     *
397     * @param  string $user - The username
398     * @return bool
399     */
400    //public function useSessionCache($user) {
401      // FIXME implement
402    //}
403
404    /**
405     * get user id frome ucenter
406     *
407     * @param  string $username  the name of the user
408     * @return int               the user id. 0 on error.
409     */
410    function get_uid($username){
411        $uid = 0;
412        if($data = $this->_uc_get_user($username)) {
413            $uid = $data[0];
414        }
415        return $uid;
416    }
417
418    private function _get_user_group($user, $is_uid = 0) {
419        return array('user');
420    }
421
422    /**
423     * convert charset
424     * @param string $str  the string that to be converted.
425     * @param bool   $out  1: doku convert to other char, 0: other char convert to doku
426     * @return string converted string.
427     */
428    private function _convert_charset($str, $out = 1){
429        if($this->getConf('uccharset') != 'utf-8'){
430            $str = $out ? iconv('utf-8', $this->getConf('uccharset'), $str) : iconv($this->getConf('uccharset'), 'utf-8', $str);
431        }
432        return $str;
433    }
434
435    private function _convert_charset_all($arr, $out = 1){
436        if($this->getConf('uccharset') != 'utf-8'){
437            if(is_array($arr)){
438                foreach($arr as $k=>$v){
439                    $arr[$k] = $this->_convert_charset_all($v, $out);
440                }
441            }else{
442                $arr = $this->_convert_charset($arr, $out);
443            }
444        }
445        return $arr;
446    }
447
448    private function _uc_user_login($username, $password){
449        $return = uc_user_login($this->_convert_charset($username), $password);
450        return array($return[0], $this->_convert_charset($return[1], 0), $return[2], $return[3], $return[4]);
451    }
452
453    private function _uc_get_user($username, $isuid = 0){
454        $return = uc_get_user($this->_convert_charset($username), $isuid);
455        return array($return[0], $this->_convert_charset($return[1], 0), $return[2]);
456    }
457
458    private function _uc_user_register($username, $password, $email){
459        return uc_user_register($this->_convert_charset($username), $password, $email);
460    }
461
462    private function _uc_user_edit($username, $oldpw, $newpw, $email){
463        return uc_user_edit($this->_convert_charset($username), $oldpw, $newpw, $email, 0);
464    }
465
466    private function _uc_setcookie($var, $value = '', $life = 0, $httponly = false) {
467
468        $_COOKIE[$var] = $value;
469
470        $timestamp = time();
471
472        if($value == '' || $life < 0) {
473            $value = '';
474            $life = -1;
475        }
476
477        $life = $life > 0 ? $timestamp + $life : ($life < 0 ? $timestamp - 31536000 : 0);
478        $path = $httponly && PHP_VERSION < '5.2.0' ? $this->getConf('cookiepath').'; HttpOnly' : $this->getConf('cookiepath');
479
480        $secure = $_SERVER['SERVER_PORT'] == 443 ? 1 : 0;
481        if(PHP_VERSION < '5.2.0') {
482            setcookie($var, $value, $life, $path, $this->getConf('cookiedomain'), $secure);
483        } else {
484            setcookie($var, $value, $life, $path, $this->getConf('cookiedomain'), $secure, $httponly);
485        }
486    }
487
488    private function _uc_get_user_full($username, $isuid = 0){
489        global $uc_controls;
490        if(empty($uc_controls['user'])){
491            require_once(DOKU_INC.'/uc_client/lib/db.class.php');
492            require_once(DOKU_INC.'/uc_client/model/base.php');
493            require_once(DOKU_INC.'/uc_client/control/user.php');
494            $uc_controls['user'] = new usercontrol();
495        }
496        $args = uc_addslashes(array('username' => $username, 'isuid' => $isuid), 1, TRUE);
497        $uc_controls['user']->input = $args;
498        $uc_controls['user']->init_input();
499        $username = $uc_controls['user']->input('username');
500        if(!$uc_controls['user']->input('isuid')) {
501            $status = $_ENV['user']->get_user_by_username($username);
502        } else {
503            $status = $_ENV['user']->get_user_by_uid($username);
504        }
505        if($status) {
506            // do not return salt.
507            return array(
508                'uid' => $status['uid'],
509                'username' => $status['username'],
510                'grps' => $this->_get_user_group($status['uid'], 1),
511                'password' => $status['password'],
512                'email' => $status['email'],
513                'regip' => $status['regip'],
514                'regdate' => $status['regdate'],
515                'lastloginip' => $status['lastloginip'],
516                'lastlogintime' => $status['lastlogintime']
517            );
518        } else {
519            return 0;
520        }
521    }
522
523    private function _recurse_copy($src,$dst) {
524        $dir = opendir($src);
525        @mkdir($dst);
526        while(false !== ( $file = readdir($dir)) ) {
527            if (( $file != '.' ) && ( $file != '..' )) {
528                if ( is_dir($src . '/' . $file) ) {
529                    $this->_recurse_copy($src . '/' . $file,$dst . '/' . $file);
530                }
531                else {
532                    copy($src . '/' . $file,$dst . '/' . $file);
533                }
534            }
535        }
536        closedir($dir);
537    }
538
539    /**
540     * generate DOKU_INC/conf/uc.auth.php config according to user config in Admin Settings.
541     * @return bool
542     */
543    private function _generate_conf() {
544        $uc_conf = '<?php' . "\n\n" . $this->getConf('ucappconfig'). "\n\n";
545        $conf_keys = array('COOKIE_PATH', 'COOKIE_DOMAIN', 'COOKIE_NAME');
546        foreach($conf_keys as $conf_name){
547            $conf_value = $this->getConf(str_replace('_', '', strtolower($conf_name)));
548            $uc_conf .= "define('DW_UC_$conf_name', '$conf_value');". "\n\n";
549        }
550        $uc_conf .= "\n\n" . '?>';
551        $handle = fopen(DOKU_INC . 'conf/uc.auth.php', 'w');
552        if ($handle === false) {
553            msg($this->getLang('openucconfigfail'), -1);
554            return false;
555        }
556        if (fwrite($handle, $uc_conf) === false) {
557            msg($this->getLang('writeucconfigfail'), -1);
558            return false;
559        }
560        return fclose($handle);
561    }
562}
563
564// vim:ts=4:sw=4:et: