Dotclear

source: inc/core/class.dc.auth.php @ 3535:014be8614b7f

Revision 3535:014be8614b7f, 15.0 KB checked in by franck <carnet.franck.paul@…>, 8 years ago (diff)

Temporary password will have to be changed at first login (after resetting password)

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

Sites map