Dotclear

source: inc/core/class.dc.auth.php @ 3761:849987324197

Revision 3761:849987324197, 19.9 KB checked in by franck <carnet.franck.paul@…>, 7 years ago (diff)

Apply SQL Statement in DC code, work in progress

RevLine 
[0]1<?php
2/**
[3730]3 * @brief Authentication and user credentials management
4 *
5 * dcAuth is a class used to handle everything related to user authentication
6 * and credentials. Object is provided by dcCore $auth property.
[3731]7 *
8 * @package Dotclear
9 * @subpackage Core
10 *
11 * @copyright Olivier Meunier & Association Dotclear
12 * @copyright GPL-2.0-only
[3730]13 */
[3731]14
15if (!defined('DC_RC_PATH')) {return;}
16
[0]17class dcAuth
18{
[3730]19    /** @var dcCore dcCore instance */
20    protected $core;
21    /** @var connection Database connection object */
22    protected $con;
[2566]23
[3730]24    /** @var string User table name */
25    protected $user_table;
26    /** @var string Perm table name */
27    protected $perm_table;
[2566]28
[3730]29    /** @var string Current user ID */
30    protected $user_id;
31    /** @var array Array with user information */
32    protected $user_info = array();
33    /** @var array Array with user options */
34    protected $user_options = array();
35    /** @var boolean User must change his password after login */
36    protected $user_change_pwd;
37    /** @var boolean User is super admin */
38    protected $user_admin;
39    /** @var array Permissions for each blog */
40    protected $permissions = array();
41    /** @var boolean User can change its password */
42    protected $allow_pass_change = true;
43    /** @var array List of blogs on which the user has permissions */
44    protected $blogs = array();
45    /** @var integer Count of user blogs */
46    public $blog_count = null;
[2566]47
[3730]48    /** @var array Permission types */
49    protected $perm_types;
[2566]50
[3730]51    /** @var dcPrefs dcPrefs object */
52    public $user_prefs;
[2566]53
[3730]54    /**
55     * Class constructor. Takes dcCore object as single argument.
56     *
57     * @param dcCore    $core        dcCore object
58     */
59    public function __construct($core)
60    {
61        $this->core       = &$core;
62        $this->con        = &$core->con;
63        $this->blog_table = $core->prefix . 'blog';
64        $this->user_table = $core->prefix . 'user';
65        $this->perm_table = $core->prefix . 'permissions';
[2566]66
[3730]67        $this->perm_types = array(
68            'admin'        => __('administrator'),
69            'usage'        => __('manage their own entries and comments'),
70            'publish'      => __('publish entries and comments'),
71            'delete'       => __('delete entries and comments'),
72            'contentadmin' => __('manage all entries and comments'),
73            'categories'   => __('manage categories'),
74            'media'        => __('manage their own media items'),
75            'media_admin'  => __('manage all media items')
76        );
77    }
[2566]78
[3730]79    /// @name Credentials and user permissions
80    //@{
81    /**
82     * Checks if user exists and can log in. <var>$pwd</var> argument is optionnal
83     * while you may need to check user without password. This method will create
84     * credentials and populate all needed object properties.
85     *
86     * @param string    $user_id        User ID
87     * @param string    $pwd            User password
88     * @param string    $user_key        User key check
89     * @param boolean    $check_blog    checks if user is associated to a blog or not.
90     * @return boolean
91     */
92    public function checkUser($user_id, $pwd = null, $user_key = null, $check_blog = true)
93    {
94        # Check user and password
[3761]95        $sql = new dcSelectStatement($this->core, 'coreAuthCheckUser');
96        $sql
97            ->columns(array('user_id', 'user_super', 'user_pwd', 'user_change_pwd', 'user_name', 'user_firstname',
98                'user_displayname', 'user_email', 'user_url', 'user_default_blog', 'user_options', 'user_lang',
99                'user_tz', 'user_post_status', 'user_creadt', 'user_upddt'))
100            ->from($this->user_table)
101            ->where('user_id = ' . $sql->quote($user_id));
[2566]102
[3730]103        try {
[3761]104            $rs = $this->con->select($sql->statement());
[3730]105        } catch (Exception $e) {
106            $err = $e->getMessage();
107            return false;
108        }
[2566]109
[3730]110        if ($rs->isEmpty()) {
111            sleep(rand(2, 5));
112            return false;
113        }
[2566]114
[3730]115        $rs->extend('rsExtUser');
[2566]116
[3730]117        if ($pwd != '') {
118            $rehash = false;
119            if (password_verify($pwd, $rs->user_pwd)) {
120                // User password ok
121                if (password_needs_rehash($rs->user_pwd, PASSWORD_DEFAULT)) {
122                    $rs->user_pwd = $this->crypt($pwd);
123                    $rehash       = true;
124                }
125            } else {
126                // Check if pwd still stored in old fashion way
127                $ret = password_get_info($rs->user_pwd);
128                if (is_array($ret) && isset($ret['algo']) && $ret['algo'] == 0) {
129                    // hash not done with password_hash() function, check by old fashion way
130                    if (crypt::hmac(DC_MASTER_KEY, $pwd, DC_CRYPT_ALGO) == $rs->user_pwd) {
131                        // Password Ok, need to store it in new fashion way
132                        $rs->user_pwd = $this->crypt($pwd);
133                        $rehash       = true;
134                    } else {
135                        // Password KO
136                        sleep(rand(2, 5));
137                        return false;
138                    }
139                } else {
140                    // Password KO
141                    sleep(rand(2, 5));
142                    return false;
143                }
144            }
145            if ($rehash) {
146                // Store new hash in DB
147                $cur           = $this->con->openCursor($this->user_table);
148                $cur->user_pwd = (string) $rs->user_pwd;
[3761]149
150                $sql = new dcUpdateStatement($this->core, 'coreAuthCheckUser');
151                $sql->where('user_id = ' . $sql->quote($rs->user_id));
152                $cur->update($sql->whereStatement());
[3730]153            }
154        } elseif ($user_key != '') {
155            if (http::browserUID(DC_MASTER_KEY . $rs->user_id . $this->cryptLegacy($rs->user_id)) != $user_key) {
156                return false;
157            }
158        }
[2566]159
[3730]160        $this->user_id         = $rs->user_id;
161        $this->user_change_pwd = (boolean) $rs->user_change_pwd;
162        $this->user_admin      = (boolean) $rs->user_super;
[2566]163
[3730]164        $this->user_info['user_pwd']          = $rs->user_pwd;
165        $this->user_info['user_name']         = $rs->user_name;
166        $this->user_info['user_firstname']    = $rs->user_firstname;
167        $this->user_info['user_displayname']  = $rs->user_displayname;
168        $this->user_info['user_email']        = $rs->user_email;
169        $this->user_info['user_url']          = $rs->user_url;
170        $this->user_info['user_default_blog'] = $rs->user_default_blog;
171        $this->user_info['user_lang']         = $rs->user_lang;
172        $this->user_info['user_tz']           = $rs->user_tz;
173        $this->user_info['user_post_status']  = $rs->user_post_status;
174        $this->user_info['user_creadt']       = $rs->user_creadt;
175        $this->user_info['user_upddt']        = $rs->user_upddt;
[2566]176
[3730]177        $this->user_info['user_cn'] = dcUtils::getUserCN($rs->user_id, $rs->user_name,
178            $rs->user_firstname, $rs->user_displayname);
[2566]179
[3730]180        $this->user_options = array_merge($this->core->userDefaults(), $rs->options());
[2566]181
[3730]182        $this->user_prefs = new dcPrefs($this->core, $this->user_id);
[2566]183
[3730]184        # Get permissions on blogs
185        if ($check_blog && ($this->findUserBlog() === false)) {
186            return false;
187        }
188        return true;
189    }
[2566]190
[3730]191    /**
192     * This method crypt given string (password, session_id, …).
193     *
194     * @param string $pwd string to be crypted
195     * @return string crypted value
196     */
197    public function crypt($pwd)
198    {
199        return password_hash($pwd, PASSWORD_DEFAULT);
200    }
[3627]201
[3730]202    /**
203     * This method crypt given string (password, session_id, …).
204     *
205     * @param string $pwd string to be crypted
206     * @return string crypted value
207     */
208    public function cryptLegacy($pwd)
209    {
210        return crypt::hmac(DC_MASTER_KEY, $pwd, DC_CRYPT_ALGO);
211    }
[3036]212
[3730]213    /**
214     * This method only check current user password.
215     *
216     * @param string    $pwd            User password
217     * @return boolean
218     */
219    public function checkPassword($pwd)
220    {
221        if (!empty($this->user_info['user_pwd'])) {
222            return password_verify($pwd, $this->user_info['user_pwd']);
223        }
[2566]224
[3730]225        return false;
226    }
[2566]227
[3730]228    /**
229     * This method checks if user session cookie exists
230     *
231     * @return boolean
232     */
233    public function sessionExists()
234    {
235        return isset($_COOKIE[DC_SESSION_NAME]);
236    }
[2566]237
[3730]238    /**
239     * This method checks user session validity.
240     *
241     * @return boolean
242     */
243    public function checkSession($uid = null)
244    {
245        $this->core->session->start();
[2566]246
[3730]247        # If session does not exist, logout.
248        if (!isset($_SESSION['sess_user_id'])) {
249            $this->core->session->destroy();
250            return false;
251        }
[2566]252
[3730]253        # Check here for user and IP address
254        $this->checkUser($_SESSION['sess_user_id']);
255        $uid = $uid ?: http::browserUID(DC_MASTER_KEY);
[2566]256
[3730]257        $user_can_log = $this->userID() !== null && $uid == $_SESSION['sess_browser_uid'];
[2566]258
[3730]259        if (!$user_can_log) {
260            $this->core->session->destroy();
261            return false;
262        }
[2566]263
[3730]264        return true;
265    }
[2566]266
[3730]267    /**
268     * Checks if user must change his password in order to login.
269     *
270     * @return boolean
271     */
272    public function mustChangePassword()
273    {
274        return $this->user_change_pwd;
275    }
[2566]276
[3730]277    /**
278     * Checks if user is super admin
279     *
280     * @return boolean
281     */
282    public function isSuperAdmin()
283    {
284        return $this->user_admin;
285    }
[2566]286
[3730]287    /**
288     * Checks if user has permissions given in <var>$permissions</var> for blog
289     * <var>$blog_id</var>. <var>$permissions</var> is a coma separated list of
290     * permissions.
291     *
292     * @param string    $permissions    Permissions list
293     * @param string    $blog_id        Blog ID
294     * @return boolean
295     */
296    public function check($permissions, $blog_id)
297    {
298        if ($this->user_admin) {
299            return true;
300        }
[2566]301
[3730]302        $p = array_map('trim', explode(',', $permissions));
303        $b = $this->getPermissions($blog_id);
[2566]304
[3730]305        if ($b != false) {
306            if (isset($b['admin'])) {
307                return true;
308            }
[2566]309
[3730]310            foreach ($p as $v) {
311                if (isset($b[$v])) {
312                    return true;
313                }
314            }
315        }
[2566]316
[3730]317        return false;
318    }
[2566]319
[3730]320    /**
321     * Returns true if user is allowed to change its password.
322     *
323     * @return    boolean
324     */
325    public function allowPassChange()
326    {
327        return $this->allow_pass_change;
328    }
329    //@}
[2566]330
[3730]331    /// @name User code handlers
332    //@{
333    public function getUserCode()
334    {
335        $code =
336        pack('a32', $this->userID()) .
337        pack('H*', $this->crypt($this->getInfo('user_pwd')));
338        return bin2hex($code);
339    }
[2566]340
[3730]341    public function checkUserCode($code)
342    {
343        $code = @pack('H*', $code);
[2566]344
[3730]345        $user_id = trim(@pack('a32', substr($code, 0, 32)));
346        $pwd     = @unpack('H*hex', substr($code, 32));
[2566]347
[3730]348        if ($user_id === false || $pwd === false) {
349            return false;
350        }
[2566]351
[3730]352        $pwd = $pwd['hex'];
[2566]353
[3730]354        $strReq = 'SELECT user_id, user_pwd ' .
355        'FROM ' . $this->user_table . ' ' .
356        "WHERE user_id = '" . $this->con->escape($user_id) . "' ";
[2566]357
[3730]358        $rs = $this->con->select($strReq);
[2566]359
[3730]360        if ($rs->isEmpty()) {
361            return false;
362        }
[2566]363
[3730]364        if ($this->crypt($rs->user_pwd) != $pwd) {
365            return false;
366        }
[2566]367
[3730]368        return $rs->user_id;
369    }
370    //@}
[2566]371
[3730]372    /// @name Sudo
373    //@{
374    /**
375     * Calls $f function with super admin rights.
376     * Returns the function result.
377     *
378     * @param callback    $f            Callback function
379     * @return mixed
380     */
381    public function sudo($f)
382    {
383        if (!is_callable($f)) {
384            throw new Exception($f . ' function doest not exist');
385        }
[2566]386
[3730]387        $args = func_get_args();
388        array_shift($args);
[2566]389
[3730]390        if ($this->user_admin) {
391            $res = call_user_func_array($f, $args);
392        } else {
393            $this->user_admin = true;
394            try {
395                $res              = call_user_func_array($f, $args);
396                $this->user_admin = false;
397            } catch (Exception $e) {
398                $this->user_admin = false;
399                throw $e;
400            }
401        }
[2566]402
[3730]403        return $res;
404    }
405    //@}
[2566]406
[3730]407    /// @name User information and options
408    //@{
409    /**
410     * Returns user permissions for a blog as an array which looks like:
411     *
412     *  - [blog_id]
413     *    - [permission] => true
414     *    - ...
415     *
416     * @param string    $blog_id        Blog ID
417     * @return array
418     */
419    public function getPermissions($blog_id)
420    {
421        if (isset($this->blogs[$blog_id])) {
422            return $this->blogs[$blog_id];
423        }
[2566]424
[3730]425        if ($this->user_admin) {
426            $strReq = 'SELECT blog_id ' .
427            'from ' . $this->blog_table . ' ' .
428            "WHERE blog_id = '" . $this->con->escape($blog_id) . "' ";
429            $rs = $this->con->select($strReq);
[2159]430
[3730]431            $this->blogs[$blog_id] = $rs->isEmpty() ? false : array('admin' => true);
[2566]432
[3730]433            return $this->blogs[$blog_id];
434        }
[2566]435
[3730]436        $strReq = 'SELECT permissions ' .
437        'FROM ' . $this->perm_table . ' ' .
438        "WHERE user_id = '" . $this->con->escape($this->user_id) . "' " .
439        "AND blog_id = '" . $this->con->escape($blog_id) . "' " .
440            "AND (permissions LIKE '%|usage|%' OR permissions LIKE '%|admin|%' OR permissions LIKE '%|contentadmin|%') ";
441        $rs = $this->con->select($strReq);
[2566]442
[3730]443        $this->blogs[$blog_id] = $rs->isEmpty() ? false : $this->parsePermissions($rs->permissions);
[2566]444
[3730]445        return $this->blogs[$blog_id];
446    }
[2566]447
[3730]448    public function getBlogCount()
449    {
450        if ($this->blog_count === null) {
451            $this->blog_count = $this->core->getBlogs(array(), true)->f(0);
452        }
[2159]453
454        return $this->blog_count;
455    }
[2566]456
[3730]457    public function findUserBlog($blog_id = null)
458    {
459        if ($blog_id && $this->getPermissions($blog_id) !== false) {
460            return $blog_id;
461        } else {
462            if ($this->user_admin) {
463                $strReq = 'SELECT blog_id ' .
464                'FROM ' . $this->blog_table . ' ' .
465                'ORDER BY blog_id ASC ' .
466                $this->con->limit(1);
467            } else {
468                $strReq = 'SELECT P.blog_id ' .
469                'FROM ' . $this->perm_table . ' P, ' . $this->blog_table . ' B ' .
470                "WHERE user_id = '" . $this->con->escape($this->user_id) . "' " .
471                "AND P.blog_id = B.blog_id " .
472                "AND (permissions LIKE '%|usage|%' OR permissions LIKE '%|admin|%' OR permissions LIKE '%|contentadmin|%') " .
473                "AND blog_status >= 0 " .
474                'ORDER BY P.blog_id ASC ' .
475                $this->con->limit(1);
476            }
[2566]477
[3730]478            $rs = $this->con->select($strReq);
479            if (!$rs->isEmpty()) {
480                return $rs->blog_id;
481            }
482        }
[2566]483
[3730]484        return false;
485    }
[2566]486
[3730]487    /**
488     * Returns current user ID
489     *
490     * @return string
491     */
492    public function userID()
493    {
494        return $this->user_id;
495    }
[2566]496
[3730]497    /**
498     * Returns information about a user .
499     *
500     * @param string    $n            Information name
501     * @return string
502     */
503    public function getInfo($n)
504    {
505        if (isset($this->user_info[$n])) {
506            return $this->user_info[$n];
507        }
[2566]508
[3730]509        return;
510    }
[2566]511
[3730]512    /**
513     * Returns a specific user option
514     *
515     * @param string    $n            Option name
516     * @return string
517     */
518    public function getOption($n)
519    {
520        if (isset($this->user_options[$n])) {
521            return $this->user_options[$n];
522        }
523        return;
524    }
[2566]525
[3730]526    /**
527     * Returns all user options in an associative array.
528     *
529     * @return array
530     */
531    public function getOptions()
532    {
533        return $this->user_options;
534    }
535    //@}
[2566]536
[3730]537    /// @name Permissions
538    //@{
539    /**
540     * Returns an array with permissions parsed from the string <var>$level</var>
541     *
542     * @param string    $level        Permissions string
543     * @return array
544     */
545    public function parsePermissions($level)
546    {
547        $level = preg_replace('/^\|/', '', $level);
548        $level = preg_replace('/\|$/', '', $level);
[2566]549
[3730]550        $res = array();
551        foreach (explode('|', $level) as $v) {
552            $res[$v] = true;
553        }
554        return $res;
555    }
[2566]556
[3730]557    /**
558     * Returns <var>perm_types</var> property content.
559     *
560     * @return array
561     */
562    public function getPermissionsTypes()
563    {
564        return $this->perm_types;
565    }
[2566]566
[3730]567    /**
568     * Adds a new permission type.
569     *
570     * @param string    $name        Permission name
571     * @param string    $title        Permission title
572     */
573    public function setPermissionType($name, $title)
574    {
575        $this->perm_types[$name] = $title;
576    }
577    //@}
[2566]578
[3730]579    /// @name Password recovery
580    //@{
581    /**
582     * Add a recover key to a specific user identified by its email and
583     * password.
584     *
585     * @param string    $user_id        User ID
586     * @param string    $user_email    User Email
587     * @return string
588     */
589    public function setRecoverKey($user_id, $user_email)
590    {
591        $strReq = 'SELECT user_id ' .
592        'FROM ' . $this->user_table . ' ' .
593        "WHERE user_id = '" . $this->con->escape($user_id) . "' " .
594        "AND user_email = '" . $this->con->escape($user_email) . "' ";
[2566]595
[3730]596        $rs = $this->con->select($strReq);
[2566]597
[3730]598        if ($rs->isEmpty()) {
599            throw new Exception(__('That user does not exist in the database.'));
600        }
[2566]601
[3730]602        $key = md5(uniqid('', true));
[2566]603
[3730]604        $cur                   = $this->con->openCursor($this->user_table);
605        $cur->user_recover_key = $key;
[2566]606
[3730]607        $cur->update("WHERE user_id = '" . $this->con->escape($user_id) . "'");
[2566]608
[3730]609        return $key;
610    }
[2566]611
[3730]612    /**
613     * Creates a new user password using recovery key. Returns an array:
614     *
615     * - user_email
616     * - user_id
617     * - new_pass
618     *
619     * @param string    $recover_key    Recovery key
620     * @return array
621     */
622    public function recoverUserPassword($recover_key)
623    {
[3761]624        $sql = new dcSelectStatement($this->core, 'coreAuthRecoverUserPwd');
625        $sql
626            ->columns(array('user_id', 'user_email'))
627            ->from($this->user_table)
628            ->where('user_recover_key = ' . $sql->quote($recover_key));
[2566]629
[3730]630        $rs = $this->con->select($strReq);
[2566]631
[3730]632        if ($rs->isEmpty()) {
633            throw new Exception(__('That key does not exist in the database.'));
634        }
[2566]635
[3730]636        $new_pass = crypt::createPassword();
[2566]637
[3730]638        $cur                   = $this->con->openCursor($this->user_table);
639        $cur->user_pwd         = $this->crypt($new_pass);
640        $cur->user_recover_key = null;
641        $cur->user_change_pwd  = 1; // User will have to change this temporary password at next login
[2566]642
[3761]643        $sql = new dcUpdateStatement($this->core, 'coreAuthRecoverUserPwd');
644        $sql->where('user_recover_key = ' . $sql->quote($recover_key));
645        $cur->update($sql->whereStatement());
[2566]646
[3730]647        return array('user_email' => $rs->user_email, 'user_id' => $rs->user_id, 'new_pass' => $new_pass);
648    }
649    //@}
[2566]650
[3730]651    /** @name User management callbacks
652    This 3 functions only matter if you extend this class and use
653    DC_AUTH_CLASS constant.
654    These are called after core user management functions.
655    Could be useful if you need to add/update/remove stuff in your
656    LDAP directory    or other third party authentication database.
657     */
658    //@{
[2566]659
[3730]660    /**
661     * Called after core->addUser
662     * @see dcCore::addUser
663     * @param cursor    $cur            User cursor
664     */
665    public function afterAddUser($cur)
666    {}
[2566]667
[3730]668    /**
669     * Called after core->updUser
670     * @see dcCore::updUser
671     * @param string    $id            User ID
672     * @param cursor    $cur            User cursor
673     */
674    public function afterUpdUser($id, $cur)
675    {}
[2566]676
[3730]677    /**
678     * Called after core->delUser
679     * @see dcCore::delUser
680     * @param string    $id            User ID
681     */
682    public function afterDelUser($id)
683    {}
684    //@}
[0]685}
Note: See TracBrowser for help on using the repository browser.

Sites map