root/trunk/wifidog-auth/wifidog/classes/User.php @ 1428

Revision 1428, 48.8 KB (checked in by gbastien, 4 years ago)

* Network can now be set as case insensitive. Emails are now never case sensitive and usernames' case sensitivity is consistent with network settings (#616)
* Yet again some tab alignment

  • Property svn:eol-style set to native
  • Property svn:executable set to *
  • Property svn:keywords set to Author Date Id Revision
Line 
1<?php
2
3
4/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
5
6// +-------------------------------------------------------------------+
7// | WiFiDog Authentication Server                                     |
8// | =============================                                     |
9// |                                                                   |
10// | The WiFiDog Authentication Server is part of the WiFiDog captive  |
11// | portal suite.                                                     |
12// +-------------------------------------------------------------------+
13// | PHP version 5 required.                                           |
14// +-------------------------------------------------------------------+
15// | Homepage:     http://www.wifidog.org/                             |
16// | Source Forge: http://sourceforge.net/projects/wifidog/            |
17// +-------------------------------------------------------------------+
18// | This program is free software; you can redistribute it and/or     |
19// | modify it under the terms of the GNU General Public License as    |
20// | published by the Free Software Foundation; either version 2 of    |
21// | the License, or (at your option) any later version.               |
22// |                                                                   |
23// | This program is distributed in the hope that it will be useful,   |
24// | but WITHOUT ANY WARRANTY; without even the implied warranty of    |
25// | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the     |
26// | GNU General Public License for more details.                      |
27// |                                                                   |
28// | You should have received a copy of the GNU General Public License |
29// | along with this program; if not, contact:                         |
30// |                                                                   |
31// | Free Software Foundation           Voice:  +1-617-542-5942        |
32// | 59 Temple Place - Suite 330        Fax:    +1-617-542-2652        |
33// | Boston, MA  02111-1307,  USA       gnu@gnu.org                    |
34// |                                                                   |
35// +-------------------------------------------------------------------+
36
37/**
38 * @package    WiFiDogAuthServer
39 * @author     Benoit Grégoire <benoitg@coeus.ca>
40 * @copyright  2005-2006 Benoit Grégoire, Technologies Coeus inc.
41 * @version    Subversion $Id$
42 * @link       http://www.wifidog.org/
43 */
44
45/**
46 * Load required classes
47 */
48require_once ('classes/Network.php');
49require_once ('classes/InterfaceElements.php');
50require_once ('classes/ProfileTemplate.php');
51require_once ('classes/Profile.php');
52require_once ('classes/Permission.php');
53/**
54 * Abstract a User
55 *
56 * @package    WiFiDogAuthServer
57 * @author     Benoit Grégoire <benoitg@coeus.ca>
58 * @copyright  2005-2006 Benoit Grégoire, Technologies Coeus inc.
59 */
60class User implements GenericObject {
61    private $_row;
62    private $id;
63    /** Object cache for the object factory (getObject())*/
64    private static $instanceArray = array();
65
66    /** Instantiate a user object
67     * @param $id The user id of the requested user
68     * @return a User object, or null if there was an error
69     */
70    public static function &getObject($id) {
71        if(!isset(self::$instanceArray[$id]))
72        {
73            self::$instanceArray[$id] = new self($id);
74        }
75        return self::$instanceArray[$id];
76    }
77
78    static function createNewObject() {
79        echo "<h1>Use User::createUser() instead</h1>";
80    }
81    /** Get an interface to create a new object.
82     * @return html markup
83     */
84    public static function getCreateNewObjectUI() {
85        return null;
86    }
87
88    /** Process the new object interface.
89     *  Will       return the new object if the user has the credentials
90     * necessary (Else an exception is thrown) and and the form was fully
91     * filled (Else the object returns null).
92     * @return the node object or null if no new node was created.
93     */
94    static function processCreateNewObjectUI() {
95        return self :: createNewObject();
96    }
97    /**
98     * Instantiate the current user
99     *
100     * @return mixed A User object, or null if there was an error
101
102     */
103    public static function getCurrentUser() {
104        require_once ('classes/Session.php');
105        $session = Session::getObject();
106        $sessCurrentUserId = $session->get(SESS_USER_ID_VAR);
107        $user = null;
108        if(!empty($sessCurrentUserId)){
109            try {
110                $user = self :: getObject($sessCurrentUserId);
111                //$user = new User($session->get(SESS_USER_ID_VAR));
112            } catch (Exception $e) {
113                /**If any problem occurs, the user should be considered logged out*/
114                $session->set(SESS_USER_ID_VAR, null);
115            }
116        }
117        return $user;
118    }
119
120    /**
121     * Associates the user passed in parameter with the session
122     *
123     * This should NOT be called by anything except the Authenticators
124     *
125     * @param object $user User a user object, or null
126     *
127     * @return bool True if everything went well setting the session
128
129     */
130    public static function setCurrentUser($user) {
131
132        if (get_class($user) == 'User'){
133            $userId = $user->getId();
134            $passwordHash = $user->getPasswordHash();
135        }
136        else {
137            $userId = null;
138            $passwordHash = null;
139        }
140
141        try {
142            $session = Session::getObject();
143            $session->set(SESS_USER_ID_VAR, $userId);
144            $session->set(SESS_PASSWORD_HASH_VAR, $passwordHash);
145            return true;
146        } catch (Exception $e) {
147            return false;
148        }
149    }
150
151    /** Instantiate a user object
152     * @param $username The username of the user
153     * @param $account_origin Network:  The account origin
154     * @param &$errMsg An error message will be appended to this if the username is not empty, but the user doesn't exist.
155     * @return a User object, or null if there was an error
156     */
157    public static function getUserByUsernameAndOrigin($username, Network $account_origin, &$errMsg = null) {
158        $db = AbstractDb::getObject();
159        $object = null;
160
161        $username_str = $db->escapeString($username);
162        $comparison = ($account_origin->getUsernamesCaseSensitive()? '=': 'ILike');
163        $account_origin_str = $db->escapeString($account_origin->getId());
164        $db->execSqlUniqueRes("SELECT user_id FROM users WHERE username {$comparison} '$username_str' AND account_origin = '$account_origin_str'", $user_info, false);
165
166        if ($user_info != null) {
167            $object = self::getObject($user_info['user_id']);
168        }
169        else if (!empty($username)) {
170            $errMsg .= sprintf(_("There is no user with username %s"),$username);
171        }
172        return $object;
173    }
174
175    /** Instantiate a user object
176     * @param $usernameOrEmail The username or the email address of the user
177     * @param &$errMsg An error message will be appended to this if the username is not empty, but the user doesn't exist.
178     * @return a User object, or null if there was an error
179     */
180    public static function getUserByUsernameOrEmail($usernameOrEmail, &$errMsg = null) {
181        $db = AbstractDb::getObject();
182        $object = null;
183
184        $usernameOrEmail_str = $db->escapeString($usernameOrEmail);
185        $db->execSqlUniqueRes("SELECT user_id FROM users WHERE username = '$usernameOrEmail_str' OR email ILike '$usernameOrEmail_str'", $user_info, false);
186
187        if ($user_info != null) {
188            $object = self::getObject($user_info['user_id']);
189        }
190        else if (!empty($usernameOrEmail)) {
191            $errMsg .= sprintf(_("There is no user with username or email %s"),$usernameOrEmail);
192        }
193        return $object;
194    }
195 
196
197   /** Instantiate a user object
198     * @param $url The OpenId url
199     * @return a User object, or null if none matched
200     */
201    public static function getUserByOpenIdUrl($url) {
202        $db = AbstractDb::getObject();
203        $object = null;
204
205        $url_str = $db->escapeString($url);
206        $db->execSqlUniqueRes("SELECT user_id FROM users WHERE open_id_url = '$url_str'", $user_rows, false);
207
208        if ($user_rows != null) {
209            $object = self::getObject($user_rows[0]['user_id']);
210        }
211        return $object;
212    }
213    /** Instantiate a user object
214     * @param $email The email of the user
215     * @param $account_origin Network:  The account origin
216     * @return a User object, or null if there was an error
217     */
218    public static function getUserByEmailAndOrigin($email, Network $account_origin) {
219        $db = AbstractDb::getObject();
220        $object = null;
221
222        $email_str = $db->escapeString($email);
223        $account_origin_str = $db->escapeString($account_origin->getId());
224        $db->execSqlUniqueRes("SELECT user_id FROM users WHERE email ILike '$email_str' AND account_origin = '$account_origin_str'", $user_info, false);
225
226        if ($user_info != null)
227        $object = self::getObject($user_info['user_id']);
228        return $object;
229    }
230
231    /** Returns the hash of the password suitable for storing or comparing in the database.  This hash is the same one as used in NoCat
232     * @return The 32 character hash.
233     */
234    public static function passwordHash($password) {
235        /**
236         * utf8_decode is used for backward compatibility with old passwords
237         * containing special characters.
238         * Conversion from UTF-8 to ISO-8859-1 is done to match the MD5 hash
239         */
240        return base64_encode(pack("H*", md5(utf8_decode($password))));
241    }
242
243    /** Create a new User in the database
244     * @param $id The id to be given to the new user
245     * @return the newly created User object, or null if there was an error
246     */
247    static function createUser($id, $username, Network $account_origin, $email, $password) {
248        $db = AbstractDb::getObject();
249
250        $object = null;
251        $id_str = $db->escapeString($id);
252        $username_str = $db->escapeString($username);
253        $account_origin_str = $db->escapeString($account_origin->getId());
254        $email_str = $db->escapeString($email);
255
256        $password_hash = $db->escapeString(User :: passwordHash($password));
257        $status = ACCOUNT_STATUS_VALIDATION;
258        $token = User :: generateToken();
259
260        $db->execSqlUpdate("INSERT INTO users (user_id,username, account_origin,email,pass,account_status,validation_token,reg_date) VALUES ('$id_str','$username_str','$account_origin_str','$email_str','$password_hash','$status','$token',CURRENT_TIMESTAMP)");
261
262        $object = self::getObject($id);
263        return $object;
264    }
265
266    /*    public static function purgeUnvalidatedUsers($days_since_creation)
267     {
268     $db = AbstractDb::getObject();
269     $days_since_creation = $db->escapeString($days_since_creation);
270
271     //$db->execSqlUpdate("INSERT INTO users (user_id,username, account_origin,email,pass,account_status,validation_token,reg_date) VALUES ('$id_str','$username_str','$account_origin_str','$email_str','$password_hash','$status','$token',CURRENT_TIMESTAMP)");
272     }*/
273
274    /** @param $object_id The id of the user */
275    function __construct($object_id) {
276        $db = AbstractDb::getObject();
277        $this->mDb = & $db;
278        $object_id_str = $db->escapeString($object_id);
279        $sql = "SELECT * FROM users WHERE user_id='{$object_id_str}'";
280        $db->execSqlUniqueRes($sql, $row, false);
281        if ($row == null) {
282            throw new Exception(sprintf(_("User id: %s could not be found in the database"), $object_id_str));
283        }
284        $this->_row = $row;
285        $this->id = $row['user_id'];
286    } //End class
287
288    function getId() {
289        return $this->id;
290    }
291
292    /** Gets the Network to which the user belongs
293     * @return Network object (never returns null)
294     */
295    public function getNetwork() {
296        return Network :: getObject($this->_row['account_origin']);
297    }
298
299    /** Get a user display suitable for a user list.  Will include link to the user profile. */
300    function getListUI() {
301        $html = '';
302        if ($this->isSplashOnlyUser()) {
303            $html .= _("Guest");
304        }
305        else {
306            $nickname = null;
307            $avatar = null;
308            $profile = $this->getAllProfiles();
309            if(!empty($profile)) {
310                // Use the first profile for now
311                $profile = $profile[0];
312
313                $nickname_fields = $profile->getFieldsBySemanticId("foaf:nick");
314                // Try using the first nickname available
315                if(!empty($nickname_fields)) {
316                    $nickname_content = $nickname_fields[0]->getContentField();
317                    if(!empty($nickname_content)) {
318                        // Force non-verbose output
319                        $str = $nickname_content->__toString(false);
320                        if(!empty($str))
321                        $nickname = $str;
322                    }
323                }
324
325                $avatar_fields = $profile->getFieldsBySemanticId("foaf:img");
326                // Try using the first avatar available
327                if(!empty($avatar_fields)) {
328                    $avatar_content = $avatar_fields[0]->getContentField();
329                    if(!empty($avatar_content)) {
330                        $avatar = $avatar_content->getUserUI();
331                    }
332                }
333            }
334
335
336
337            // Display the avatar
338            if(empty($avatar))
339            $html .= Avatar::getDefaultUserUI();
340            else
341            $html .= $avatar;
342
343            // Display the nickname or the username
344            $profiles=$this->getAllProfiles();
345            if($profiles){
346                $html .= "<a href='".BASE_URL_PATH."profile/?user_id=".$this->getId()."' title='".htmlentities(_("View this user's profile."), ENT_QUOTES)."' class='user_nickname'>";
347            }
348            if(empty($nickname))
349            $html .= $this->getUserName();
350            else
351            $html .= $nickname;
352            if($profiles){
353                $html .= "</a>\n";
354            }
355            $profileTemplates = $this->getNetwork()->getAllProfileTemplates();
356            /*if($this==User::getCurrentUser() && $profileTemplates) {
357             $html .= "<div class='user_edit_profile_link'><br/>(<a href='".BASE_SSL_PATH."admin/generic_object_admin.php?object_id=".$this->getId()."&object_class=User&action=edit'>"._("edit profile")."</a>)</div>";
358             }*/
359
360        }
361        return $html;
362    }
363
364    function getOpenIdUrl() {
365        return $this->_row['open_id_url'];
366    }
367
368    function getUsername() {
369        return $this->_row['username'];
370    }
371
372    /** Set the user's username
373     * @param $value The new value
374     * @return true on success, false on failure
375     * @throws exception if the user tries to set a duplicate username
376     */
377    function setUsername($value) {
378        $retval = true;
379        if ($value != $this->getUsername()) {
380            $db = AbstractDb::getObject();
381            $otherUser = User::getUserByUsernameAndOrigin($value, $this->getNetwork());
382            if (!is_null($otherUser)) {
383                throw new exception(sprintf(_("Sorry, the username %s is not available"), $value));
384            }
385            $value = $db->escapeString($value);
386            $retval = @ $db->execSqlUpdate("UPDATE users SET username = '{$value}' WHERE user_id='{$this->id}'", false);
387            $this->refresh();
388        }
389        return $retval;
390    }
391
392    /** Add profile template to this user */
393    public function addProfile(Profile $profile) {
394        $db = AbstractDb::getObject();
395        $profile_id = $db->escapeString($profile->getId());
396        $sql = "INSERT INTO user_has_profiles (user_id, profile_id) VALUES ('$this->id','$profile_id')";
397        return $db->execSqlUpdate($sql, false);
398    }
399
400    /** Remove profile template from this user */
401    public function removeProfile(Profile $profile) {
402        $db = AbstractDb::getObject();
403        $profile_id = $db->escapeString($profile->getId());
404        $sql = "DELETE FROM user_has_profiles WHERE user_id='$this->id' AND profile_id='$profile_id'";
405        return $db->execSqlUpdate($sql, false);
406    }
407
408    /**Get an array of all Profiles linked to this user
409     * @return an array of Profile or an empty arrray */
410    public function getAllProfiles() {
411        $db = AbstractDb::getObject();
412        $retval = array ();
413        $profile_rows = null;
414        $sql = "SELECT profile_id FROM user_has_profiles NATURAL JOIN profiles WHERE user_id='$this->id' ORDER BY creation_date";
415        $db->execSql($sql, $profile_rows, false);
416        if ($profile_rows != null) {
417            foreach ($profile_rows as $profile_row) {
418                $retval[] = Profile :: getObject($profile_row['profile_id']);
419            }
420        }
421        return $retval;
422    }
423
424    public function getEmail() {
425        return $this->_row['email'];
426    }
427
428    public function setEmail($email) {
429        $email_str = $this->mDb->escapeString($email);
430        if (!($update = $this->mDb->execSqlUpdate("UPDATE users SET email='{$email_str}' WHERE user_id='{$this->id}'"))) {
431            throw new Exception(_("Could not update email address."));
432        }
433        $this->_row['email'] = $email; // unescaped
434    }
435
436    function setIsInvisible($value) {
437        $retval = true;
438        if ($value != $this->isAdvertised()) {
439            $db = AbstractDb::getObject();
440            $value ? $value = 'TRUE' : $value = 'FALSE';
441            $retval = $db->execSqlUpdate("UPDATE users SET is_invisible = {$value} WHERE user_id = '{$this->getId()}'", false);
442            $this->refresh();
443        }
444        return $retval;
445    }
446
447    public function isInvisible() {
448        return (($this->_row['is_invisible'] == 't') ? true : false);
449    }
450
451    /**What locale (language) does the user prefer? */
452    public function getPreferedLocale() {
453        $session = Session::getObject();
454        $locale = $this->_row['prefered_locale'];
455        if (empty ($locale) && !empty ($session))
456        $locale = $session->get(SESS_LANGUAGE_VAR);
457        if (empty ($locale))
458        $locale = DEFAULT_LANG;
459        return $locale;
460    }
461
462    public function setPreferedLocale($locale) {
463        $locale_str = $this->mDb->escapeString($locale);
464        if (!($update = $this->mDb->execSqlUpdate("UPDATE users SET prefered_locale='{$locale_str}' WHERE user_id='{$this->id}'"))) {
465            throw new Exception(_("Could not update username locale."));
466        }
467        $this->_row['prefered_locale'] = $locale;
468    }
469
470    /** get the hashed password stored in the database */
471    public function getPasswordHash() {
472        return $this->_row['pass'];
473    }
474
475    /** Get the account status.
476     * @return Possible values are listed in common.php
477     */
478    function getAccountStatus() {
479        return $this->_row['account_status'];
480    }
481
482    function setAccountStatus($status) {
483        $db = AbstractDb::getObject();
484        if($status != $this->getAccountStatus()) {
485            $status_str = $db->escapeString($status);
486            if (!($update = $db->execSqlUpdate("UPDATE users SET account_status='{$status_str}' WHERE user_id='{$this->id}'"))) {
487                throw new Exception(_("Could not update status."));
488            }
489            $this->_row['account_status'] = $status;
490        }
491    }
492
493    /** Is the user valid?  Valid means that the account is validated or hasn't exhausted it's validation period.
494     $errmsg: Returs the reason why the account is or isn't valid */
495    function isUserValid(& $errmsg = null) {
496        global $account_status_to_text;
497        $db = AbstractDb::getObject();
498        $retval = false;
499        $account_status = $this->getAccountStatus();
500        if ($account_status == ACCOUNT_STATUS_ALLOWED) {
501            $retval = true;
502        } else
503        if ($account_status == ACCOUNT_STATUS_VALIDATION) {
504            $sql = "SELECT CASE WHEN ((CURRENT_TIMESTAMP - reg_date) > networks.validation_grace_time) THEN true ELSE false END AS validation_grace_time_expired, EXTRACT(EPOCH FROM networks.validation_grace_time) as validation_grace_time FROM users  JOIN networks ON (users.account_origin = networks.network_id) WHERE (user_id='{$this->id}')";
505            $db->execSqlUniqueRes($sql, $user_info, false);
506
507            if ($user_info['validation_grace_time_expired'] == 't') {
508                $errmsg = sprintf(_("Sorry, your %.0f minutes grace period to retrieve your email and validate your account has now expired. You will have to connect to the internet and validate your account from another location."), $user_info['validation_grace_time']/60);
509                $retval = false;
510            } else {
511                $errmsg = _("Your account is currently valid.");
512                $retval = true;
513            }
514        } else {
515            $errmsg = _("Sorry, your account is not valid: ") . $account_status_to_text[$account_status];
516            $retval = false;
517        }
518        return $retval;
519    }
520
521    public function DEPRECATEDisSuperAdmin() {
522        $db = AbstractDb::getObject();
523        //$this->session->dump();
524
525        $db->execSqlUniqueRes("SELECT * FROM users JOIN server_stakeholders USING (user_id) WHERE (users.user_id='$this->id')", $user_info, false);
526        if (!empty ($user_info)) {
527            return true;
528        } else {
529            return false;
530        }
531
532    }
533
534    /** Is this user the Splash Only User() */
535    public function isSplashOnlyUser() {
536        if ($this->_row['username'] == "SPLASH_ONLY_USER") {
537            return true;
538        } else {
539            return false;
540        }
541    }
542
543    function getValidationToken() {
544        return $this->_row['validation_token'];
545    }
546
547    /** Retrieves the connection history necessary for abuse control
548
549    * @return false if abuse control is disabled */
550
551    static function getAbuseControlConnectionHistory($user = null, $mac = null, $node = null) {
552        if (!$user) {
553            $user = User::getCurrentUser();
554        }
555        if (!$node) {
556            $node = Node::getCurrentNode();//Maybe this should be getCurrentRealNode, but it would make debuging harder
557        }
558        $network = $node->getNetwork();
559
560        $db = AbstractDb::getObject();
561
562        if ($network->getConnectionLimitWindow()) {
563            //$sql =  " SELECT * from connections \n";//For debugging
564            $sql =  " SELECT \n";
565            $sql .= " SUM (incoming+outgoing) AS network_total_bytes, \n";
566            $sql .= " SUM (CASE WHEN node_id = '".$node->getId()."' THEN (incoming+outgoing) END) AS node_total_bytes, \n";
567            $sql .= " SUM (COALESCE(timestamp_out,last_updated) - timestamp_in) AS network_duration, \n";
568            $sql .= " SUM (CASE WHEN node_id = '".$node->getId()."' THEN (COALESCE(timestamp_out,last_updated) - timestamp_in) END) AS node_duration \n";//For real //The coalesce is to make sure the substraction returns a value for active conections, since active connections do not yet have a timestamp_out.  Do NOT coalesce with CURRENT_TIMESTAMP, it could cause real problems for users in case of gateway crash.
569            $sql .= " FROM connections \n";//For real
570            $sql .= " JOIN nodes USING (node_id) \n";
571            $sql .= " JOIN networks USING (network_id) \n";
572            $sql .= " JOIN tokens ON (tokens.token_id = connections.token_id) \n";
573            $sql .= " WHERE 1=1 \n";
574
575            if ($mac) {
576                //Catch some cheaters
577                $mac = $db->escapeString($mac);
578                $mac_sql_or = " OR connections.user_mac = '$mac' ";
579            }
580            else {
581                $mac_sql_or = null;
582            }
583            $sql .= " AND (connections.user_id = '".$user->getId()."' $mac_sql_or ) \n";
584
585            $sql .= " AND (timestamp_in > CURRENT_TIMESTAMP - networks.connection_limit_window OR tokens.token_status = '".TOKEN_INUSE."')";  //Get every connection within the window plus any still active connection, even if it started before the window
586
587            $subselect = $sql;
588            $sql =  " SELECT subselect.*, \n";
589            $sql .= " networks.connection_limit_window, \n";
590            $sql .= " networks.connection_limit_network_max_total_bytes, COALESCE(network_total_bytes>networks.connection_limit_network_max_total_bytes, false) AS network_total_bytes_exceeded_limit, \n";
591            $sql .= " networks.connection_limit_node_max_total_bytes, COALESCE(node_total_bytes>networks.connection_limit_node_max_total_bytes, false) AS node_total_bytes_exceeded_limit, \n";
592            $sql .= " networks.connection_limit_network_max_usage_duration, COALESCE(network_duration>networks.connection_limit_network_max_usage_duration, false) AS network_duration_exceeded_limit, \n";
593            $sql .= " networks.connection_limit_node_max_usage_duration, COALESCE(node_duration>networks.connection_limit_node_max_usage_duration, false) AS node_duration_exceeded_limit \n";
594
595            $sql .= " FROM ($subselect) AS subselect JOIN networks ON (network_id = '".$network->getId()."')";
596
597            $db->execSqlUniqueRes($sql, $connection_limits_report, false);
598            return $connection_limits_report;
599        }
600        else {
601            return false;
602        }
603    }
604
605    /** Takes the same paramaters as getAbuseControlConnectionHistory, and tells you if the abuse limits are busted
606
607    * @return false if abuse control respected, else a string containing the reason(s) for the bust  */
608
609    static function isAbuseControlViolated($user = null, $mac = null, $node = null) {
610        $retval = false;
611        $abuseControlReport = self::getAbuseControlConnectionHistory($user, $mac, $node);
612        if($abuseControlReport) {
613            if (!$user) {
614                $user = User::getCurrentUser();
615            }
616            //pretty_print_r($abuseControlReport);
617            if($node && Security::hasPermission(Permission::P('NODE_PERM_BYPASS_DYNAMIC_ABUSE_CONTROL'), $node, $user)) {
618                $retval = false;
619            }
620            else {
621                require_once('classes/Content/UIAllowedBandwidth/UIAllowedBandwidth.php');
622                if($abuseControlReport['network_total_bytes_exceeded_limit']=='t') {
623                    $retval .= sprintf(_("During the last %s period, you transfered %s bytes throughout the network, which exceeds the %s bytes limit for the entire network."), $abuseControlReport['connection_limit_window'], UIAllowedBandwidth::formatSize($abuseControlReport['network_total_bytes']), UIAllowedBandwidth::formatSize($abuseControlReport['connection_limit_network_max_total_bytes']));
624                }
625                if($abuseControlReport['node_total_bytes_exceeded_limit']=='t') {
626                    $retval .= sprintf(_("During the last %s period, you transfered %s bytes at this node, which exceeds the %s bytes limit for this node."), $abuseControlReport['connection_limit_window'], UIAllowedBandwidth::formatSize($abuseControlReport['node_total_bytes']), UIAllowedBandwidth::formatSize($abuseControlReport['connection_limit_node_max_total_bytes']));
627                }
628                if($abuseControlReport['network_duration_exceeded_limit']=='t') {
629                    $retval .= sprintf(_("During the last %s period, you were online for a duration of %s throughout the network, which exceeds the %s limit for the entire network."), $abuseControlReport['connection_limit_window'], $abuseControlReport['network_duration'], $abuseControlReport['connection_limit_network_max_usage_duration']);
630                }
631                if($abuseControlReport['node_duration_exceeded_limit']=='t') {
632                    $retval .= sprintf(_("During the last %s period, you were online for a duration of %s at this node, which exceeds the %s limit for this node."), $abuseControlReport['connection_limit_window'], $abuseControlReport['node_duration'], $abuseControlReport['connection_limit_node_max_usage_duration']);
633                }
634            }
635        }
636        return $retval;
637    }
638    /** Generate a token in the connection table so the user can actually use the internet
639    @return true on success, false on failure
640    */
641    function generateConnectionToken() {
642        if ($this->isUserValid()) {
643            $db = AbstractDb::getObject();
644            $session = Session::getObject();
645
646            $token = self :: generateToken();
647            if ($_SERVER['REMOTE_ADDR']) {
648                $node_ip = $db->escapeString($_SERVER['REMOTE_ADDR']);
649            }
650            if ($session && $node_ip && $session->get(SESS_NODE_ID_VAR)) {
651                //echo "$session && $node_ip && {$session->get(SESS_NODE_ID_VAR)}";
652                $node_id = $db->escapeString($session->get(SESS_NODE_ID_VAR));
653                $abuseControlFault = User::isAbuseControlViolated($this, null, Node::getObject($node_id));
654                if($abuseControlFault) {
655                    throw new Exception ($abuseControlFault);
656                }
657                /*
658                 * Delete all unused tokens for this user, so we don't fill the database
659                 * with them
660                 */
661                $sql = "DELETE FROM connections USING tokens "."WHERE tokens.token_id=connections.token_id AND token_status='".TOKEN_UNUSED."' AND user_id = '".$this->getId()."';\n";
662                // TODO:  Try to find a reusable token before creating a brand new one!
663
664                $sql .= "INSERT INTO tokens (token_owner, token_issuer, token_id, token_status) VALUES ('" . $this->getId() . "', '" . $this->getId() . "', '$token', '" . TOKEN_UNUSED . "');\n";
665                $sql .= "INSERT INTO connections (user_id, token_id, timestamp_in, node_id, node_ip, last_updated) VALUES ('" . $this->getId() . "', '$token', CURRENT_TIMESTAMP, '$node_id', '$node_ip', CURRENT_TIMESTAMP)";
666                $db->execSqlUpdate($sql, false);
667                $retval = $token;
668            }
669            else {
670                $retval = false;
671            }
672        }
673        else {
674            $retval = false;
675        }
676        return $retval;
677    }
678
679    function setPassword($password) {
680        $db = AbstractDb::getObject();
681
682        $new_password_hash = User :: passwordHash($password);
683        if (empty($password)) {
684            throw new Exception(_("Password cannot be empty."));
685        }
686
687        if (!($update = $db->execSqlUpdate("UPDATE users SET pass='$new_password_hash' WHERE user_id='{$this->id}'"))) {
688            throw new Exception(_("Could not change user's password."));
689        }
690        $this->_row['pass'] = $password;
691    }
692
693    function getAccountOrigin() {
694        return $this->_row['account_origin'];
695    }
696
697    /** Return all the users
698     */
699    static function getAllUsers() {
700        $db = AbstractDb::getObject();
701
702        $db->execSql("SELECT * FROM users", $objects, false);
703        if ($objects == null) {
704            throw new Exception(_("No users could not be found in the database"));
705        }
706        return $objects;
707    }
708
709    function sendLostUsername() {
710        $network = $this->getNetwork();
711        require_once ('classes/Mail.php');
712        $mail = new Mail();
713        $mail->setSenderName(_("Registration system"));
714        $mail->setSenderEmail($network->getValidationEmailFromAddress());
715        $mail->setRecipientEmail($this->getEmail());
716        $mail->setMessageSubject($network->getName() . _(" lost username request"));
717        $mail->setMessageBody(_("Hello,\nYou have requested that the authentication server send you your username:\nUsername: ") . $this->getUsername() . _("\n\nHave a nice day,\nThe Team"));
718        $mail->send();
719    }
720
721    function sendValidationEmail() {
722        if ($this->getAccountStatus() != ACCOUNT_STATUS_VALIDATION) {
723            throw new Exception(_("The user is not in validation period."));
724        } else {
725            if ($this->getValidationToken() == "") {
726                throw new Exception(_("The validation token is empty."));
727            } else {
728                $network = $this->getNetwork();
729                require_once ('classes/Mail.php');
730                $mail = new Mail();
731                $mail->setSenderName(_("Registration system"));
732                $mail->setSenderEmail($network->getValidationEmailFromAddress());
733                $mail->setRecipientEmail($this->getEmail());
734                $mail->setMessageSubject($network->getName() . _(" new user validation"));
735                $url = BASE_SSL_PATH . "validate.php?user_id=" . $this->getId() . "&token=" . $this->getValidationToken();
736                $mail->setMessageBody(_("Hello,\nPlease follow the link below to validate your account.\n") . $url . _("\n\nThank you,\nThe Team."));
737                $mail->send();
738            }
739        }
740    }
741
742    function sendLostPasswordEmail() {
743        $network = $this->getNetwork();
744        $new_password = $this->randomPass();
745        $this->setPassword($new_password);
746        require_once ('classes/Mail.php');
747        $mail = new Mail();
748        $mail->setSenderName(_("Registration system"));
749        $mail->setSenderEmail($network->getValidationEmailFromAddress());
750        $mail->setRecipientEmail($this->getEmail());
751        $mail->setMessageSubject($network->getName() . _(" new password request"));
752        $mail->setMessageBody(_("Hello,\nYou have requested that the authentication server send you a new password:\nUsername: ") . $this->getUsername() . _("\nPassword: ") . $new_password . _("\n\nHave a nice day,\nThe Team"));
753        $mail->send();
754    }
755
756    public static function emailExists($id) {
757        $db = AbstractDb::getObject();
758        $id_str = $db->escapeString($id);
759        $sql = "SELECT * FROM users WHERE email='{$id_str}'";
760        $db->execSqlUniqueRes($sql, $row, false);
761        return $row;
762    }
763
764    public static function randomPass() {
765        $rand_pass = ''; // makes sure the $pass var is empty.
766        for ($j = 0; $j < 3; $j++) {
767            $startnend = array (
768            'b',
769            'c',
770            'd',
771            'f',
772            'g',
773            'h',
774            'j',
775            'k',
776            'l',
777            'm',
778            'n',
779            'p',
780            'q',
781            'r',
782            's',
783            't',
784            'v',
785            'w',
786            'x',
787            'y',
788            'z',
789
790            );
791            $id = array (
792            'a',
793            'e',
794            'i',
795            'o',
796            'u',
797            'y',
798
799            );
800            $count1 = count($startnend) - 1;
801            $count2 = count($id) - 1;
802
803            for ($i = 0; $i < 3; $i++) {
804                if ($i != 1) {
805                    $rand_pass .= $startnend[rand(0, $count1)];
806                } else {
807                    $rand_pass .= $id[rand(0, $count2)];
808                }
809            }
810        }
811        return $rand_pass;
812    }
813
814    public static function generateToken() {
815        return md5(uniqid(rand(), 1));
816    }
817
818    /**
819     * Get an interface to add a user to a list
820     *
821     * @param string $user_prefix      A identifier provided by the programmer
822     *                                 to recognise it's generated HTML form
823     * @param string $add_button_name  Name of optional "add" button
824     * @param string $add_button_value Value of optional "add" button
825     *
826     * @return string HTML markup
827
828     */
829    public static function getSelectUserUI($user_prefix, $add_button_name = null, $add_button_value = null) {
830        $db = AbstractDb::getObject();
831        $networkSelector = Network :: getSelectUI($user_prefix);
832        // Check if we need to add an "add" button
833        if ($add_button_name && $add_button_value) {
834            $userSelector = _("Username") . ": " . InterfaceElements :: generateInputText("select_user_" . $user_prefix . "_username", "", "", "input_text", array (
835            "onkeypress" => "if ((event.which ? event.which : event.keyCode) == 13) {form.$add_button_name.click() }"
836            ));
837            $userSelector .= InterfaceElements :: generateInputSubmit($add_button_name, $add_button_value);
838        } else {
839            $userSelector = _("Search for Username or Email Address") . ": " . InterfaceElements :: generateInputText("select_user_" . $user_prefix . "_username");
840        }
841        $html = "<div class='user_select_user_ui_container'>".$networkSelector . "<br>" . $userSelector . "</div>\n";
842        return $html;
843    }
844
845    /** Get the selected user, IF one was selected and is valid
846     * @param $user_prefix A identifier provided by the programmer to recognise it's generated form
847     * @param &$errMsg An error message will be appended to this is the username is not empty, but the user doesn't exist.
848     * @return the User object, or null if the user is invalid or none was selected
849     */
850    static function processSelectUserUI($user_prefix, &$errMsg) {
851        $object = null;
852        try {
853            $network = Network :: processSelectUI($user_prefix);
854            $name = "select_user_{$user_prefix}_username";
855            if (!empty ($_REQUEST[$name])) {
856                $username = $_REQUEST[$name];
857                return self :: getUserByUsernameOrEmail($username, $errMsg);
858            } else
859            return null;
860        } catch (Exception $e) {
861            return null;
862        }
863    }
864
865    public function getAdminUI() {
866        $db = AbstractDb::getObject();
867        $currentUser = self :: getCurrentUser();
868        $userPreferencesItems = array();
869        $finalHtml = '';
870        if(Security::hasPermission(Permission::P('NETWORK_PERM_VIEW_STATISTICS'), $this->getNetwork())) {
871            /* Statistics */
872            $content = "<a href='".BASE_SSL_PATH."admin/stats.php?Statistics=".$this->getNetwork()->getId()."&distinguish_users_by=user_id&stats_selected_users=".$this->getUsername()."&UserReport=on&user_id=".$this->getId()."&action=generate'>"._("Get user statistics")."</a>\n";
873            $administrationItems[] = InterfaceElements::genSectionItem($content);
874
875            /* Account status */
876            $title = _("Account Status");
877            $help = _("Note that Error is for internal use only");
878            $name = "user_" . $this->getId() . "_accountstatus";
879            global $account_status_to_text;
880            $content = FormSelectGenerator::generateFromKeyLabelArray($account_status_to_text, $this->getAccountStatus(), $name, null, false);
881            $administrationItems[] = InterfaceElements::genSectionItem($content, $title, $help);
882
883            $finalHtml .= InterfaceElements::genSection($administrationItems, _("Administrative options"));
884        }
885
886        if (($this == $currentUser && !$this->isSplashOnlyUser() )|| Security::hasPermission(Permission::P('NETWORK_PERM_EDIT_ANY_USER'), $this->getNetwork())) {
887            /* Username */
888            $title = _("Username");
889            $name = "user_" . $this->getId() . "_username";
890            $content = "<input type='text' name='$name' value='" . htmlentities($this->getUsername()) . "' size=30><br/>\n";
891            $content .= _("Be careful when changing this: it's the username you use to log in!");
892            $userPreferencesItems[] = InterfaceElements::genSectionItem($content, $title);
893
894
895            /* Email */
896            $title = _("Email");
897            $name = "email_" . $this->getId() . "_email";
898            $content = "<input type='text' name='$name' disabled='disabled' value='" . htmlentities($this->getEmail()) . "' size=30><br/>\n";
899            $content .= _("If you wish to change this address, please Email Support!");
900            $userPreferencesItems[] = InterfaceElements::genSectionItem($content, $title);
901
902
903
904            /* Change password */
905            $changePasswordItems=array();
906            if($this == $currentUser) {//Don't enter the old password if changing password for another user
907                $title = _("Your current password");
908                $name = "user_" . $this->getId() . "_oldpassword";
909                $content = "<input type='password' name='$name' size='20'>\n";
910                $changePasswordItems[] = InterfaceElements::genSectionItem($content, $title);
911            }
912
913            $title = _("Your new password");
914            $name = "user_" . $this->getId() . "_newpassword";
915            $content = "<input type='password' name='$name' size='20'>\n";
916            $changePasswordItems[] = InterfaceElements::genSectionItem($content, $title);
917
918            $title = _("Your new password (again)");
919            $name = "user_" . $this->getId() . "_newpassword_again";
920            $content = "<input type='password' name='$name' size='20'>\n";
921            $changePasswordItems[] = InterfaceElements::genSectionItem($content, $title);
922
923            $userPreferencesItems[] = InterfaceElements::genSection($changePasswordItems, _("Change my password"));
924
925            $finalHtml .= InterfaceElements::genSection($userPreferencesItems, _("User preferences"), false, false, get_class($this));
926
927            //N.B: For now, let pretend we have only one profile per use...
928            $profiles = $this->getAllProfiles();
929            $current_profile = null;
930            if(!empty($profiles)) {
931                $current_profile = $profiles[0];
932            }
933
934            if($current_profile != null) {
935                $finalHtml .= $current_profile->getAdminUI();
936                $name = "user_" . $this->getId() . "_delete_profile_".$current_profile->getId();
937                $value = _("Completely delete my public profile");
938                $finalHtml .= "<div class='admin_element_tools'>";
939                $finalHtml .= '<input type="submit" class="submit" name="' . $name . '" value="' . $value . '">';
940                $finalHtml .= "</div>";
941            }
942            else {                    // Get the list of profile templates for the users' network
943                $profile_templates = ProfileTemplate::getAllProfileTemplates($this->getNetwork());
944                if(!empty($profile_templates)) {
945                    $name = "user_" . $this->getId() . "_add_profile";
946                    $value = _("Create my public profile");
947                    $finalHtml .= "<div class='admin_element_tools'>";
948                    $finalHtml .= '<input type="submit" class="submit" name="' . $name . '" value="' . $value . '">';
949                    $finalHtml .= "</div>";
950                }
951            }
952        }
953
954        return $finalHtml;
955    }
956
957    public function processAdminUI() {
958        $db = AbstractDb::getObject();
959        $currentUser = self :: getCurrentUser();
960        if (Security::hasPermission(Permission::P('NETWORK_PERM_EDIT_ANY_USER'), $this->getNetwork())) {
961            /* Account status */
962            $name = "user_" . $this->getId() . "_accountstatus";
963            $status = FormSelectGenerator::getResult($name, null);
964            $this->setAccountStatus($status);
965        }
966
967        if ($this == $currentUser || Security::requirePermission(Permission::P('NETWORK_PERM_EDIT_ANY_USER'), $this->getNetwork())) {
968            /* Username */
969            $name = "user_" . $this->getId() . "_username";
970            $this->setUsername($_REQUEST[$name]);
971
972            /* Change password */
973            $nameOldpassword = "user_" . $this->getId() . "_oldpassword";
974            $nameNewpassword = "user_" . $this->getId() . "_newpassword";
975            $nameNewpasswordAgain = "user_" . $this->getId() . "_newpassword_again";
976            if($_REQUEST[$nameNewpassword]!=null){
977                if ($this == $currentUser && $this->getPasswordHash() != User::passwordHash($_REQUEST[$nameOldpassword])) {
978                    throw new Exception(_("Wrong password."));
979                }
980                if ($_REQUEST[$nameNewpassword] != $_REQUEST[$nameNewpasswordAgain]){
981                    throw new Exception(_("Passwords do not match."));
982                }
983                $this->setPassword($_REQUEST[$nameNewpassword]);
984            }
985
986            // Pretend there is only one
987            $profiles = $this->getAllProfiles();
988            if(!empty($profiles)) {
989                $current_profile = $profiles[0];
990                if($current_profile != null) {
991                    $current_profile->processAdminUI();
992                    $name = "user_" . $this->getId() . "_delete_profile_".$current_profile->getId();
993                    if(!empty($_REQUEST[$name])) {
994                        $errmsg=null;
995                        $current_profile->delete($errmsg);
996                    }
997                }
998            }
999            else {
1000                $name = "user_" . $this->getId() . "_add_profile";
1001                if(!empty($_REQUEST[$name])) {
1002                    // Get the list of profile templates for the users' network
1003                    $profile_templates = ProfileTemplate::getAllProfileTemplates($this->getNetwork());
1004                    if(!empty($profile_templates)) {
1005                        // Create a blank profile and link it to the user
1006                        $current_profile = Profile::createNewObject(null, $profile_templates[0]);
1007                        $this->addProfile($current_profile);
1008                    }
1009                }
1010
1011            }
1012
1013        }
1014    }
1015
1016    public function delete(& $errmsg) {
1017    }
1018
1019    public function getUserUI() {
1020        $html = "";
1021        $html .= $this->getRealName();
1022
1023        return $html;
1024    }
1025
1026    /** Add content to this user ( subscription ) */
1027    public function addContent(Content $content) {
1028        $db = AbstractDb::getObject();
1029        $content_id = $db->escapeString($content->getId());
1030        $sql = "INSERT INTO user_has_content (user_id, content_id) VALUES ('$this->id','$content_id')";
1031        $db->execSqlUpdate($sql, false);
1032        return true;
1033    }
1034
1035    /** Remove content from this node */
1036    public function removeContent(Content $content) {
1037        $db = AbstractDb::getObject();
1038        $content_id = $db->escapeString($content->getId());
1039        $sql = "DELETE FROM user_has_content WHERE user_id='$this->id' AND content_id='$content_id'";
1040        $db->execSqlUpdate($sql, false);
1041        return true;
1042    }
1043
1044    /**Get an array of all Content linked to this node
1045     * @return an array of Content or an empty arrray */
1046    function getAllContent() {
1047        $db = AbstractDb::getObject();
1048        $retval = array ();
1049        $content_rows = null;
1050        $sql = "SELECT * FROM user_has_content WHERE user_id='$this->id' ORDER BY subscribe_timestamp";
1051        $db->execSql($sql, $content_rows, false);
1052        if ($content_rows != null) {
1053            foreach ($content_rows as $content_row) {
1054                $retval[] = Content :: getObject($content_row['content_id']);
1055            }
1056        }
1057        return $retval;
1058    }
1059
1060    /** Reloads the object from the database.  Should normally be called after a set operation */
1061    protected function refresh() {
1062        $this->__construct($this->id);
1063    }
1064    /** Menu hook function */
1065    static public function hookMenu() {
1066        $items = array();
1067        $network = Network::getCurrentNetwork();
1068        $server = Server::getServer();
1069        if(Security::hasAnyPermission(array(array(Permission::P('NETWORK_PERM_VIEW_ONLINE_USERS'), $network))))
1070        {
1071            $items[] = array('path' => 'users/online_users',
1072            'title' => _("Online Users"),
1073            'url' => BASE_URL_PATH."admin/online_users.php");
1074        }
1075        if(Security::hasPermission(Permission::P('SERVER_PERM_EDIT_SERVER_CONFIG'), $server))
1076        {
1077            $items[] = array('path' => 'users/import_nocat',
1078            'title' => _("Import NoCat user database"),
1079            'url' => BASE_URL_PATH."admin/import_user_database.php"
1080            );
1081        }
1082        if(Security::getObjectsWithPermission(Permission::P('NETWORK_PERM_EDIT_ANY_USER')))
1083        {
1084            $items[] = array('path' => 'users/user_manager',
1085            'title' => _("User manager"),
1086            'url' => BASE_URL_PATH."admin/user_log.php"
1087            );
1088        }
1089        if(Security::getObjectsWithPermission(Permission::P('NETWORK_PERM_VIEW_STATISTICS')))
1090        {
1091            $items[] = array('path' => 'users/statistics',
1092            'title' => _("Statistics"),
1093            'url' => BASE_URL_PATH."admin/stats.php"
1094            );
1095        }
1096        $items[] = array('path' => 'users',
1097        'title' => _('User administration'),
1098        'type' => MENU_ITEM_GROUPING);
1099        return $items;
1100    }
1101
1102    /** Set Smarty template values.  Standardization routine. */
1103    public static function assignSmartyValues($smarty, $user = null) {
1104        if (!$user)
1105        $user = User :: getCurrentUser();
1106        $session = Session :: getObject();
1107        $smarty->assign('userOriginallyRequestedURL', $session ? $session->get(SESS_ORIGINAL_URL_VAR) : '');
1108        $smarty->assign('userId', $user ? $user->getId() : '');
1109        $smarty->assign('userName', $user ? $user->getUsername() : '');
1110        /**
1111         * Define user security levels for the template
1112         *
1113         * These values are used in the default template of WiFoDog but could be
1114         * used in a customized template to restrict certain links to specific
1115         * user access levels.  Note however that they will all be deprecateb by the
1116         * new roles system.
1117         */
1118        $smarty->assign('userIsValid', $user && !$user->isSplashOnlyUser() ? true : false);
1119        $smarty->assign('userDEPRECATEDisSuperAdmin', $user && $user->DEPRECATEDisSuperAdmin());
1120
1121        if (isset ($_REQUEST['debug_request']) && ($user && $user->DEPRECATEDisSuperAdmin())) {
1122            // Tell Smarty everything it needs to know
1123            $smarty->assign('debugRequested', true);
1124            $smarty->assign('debugOutput', print_r($_REQUEST, true));
1125        }
1126    }
1127}
1128
1129/*
1130 * Local variables:
1131 * tab-width: 4
1132 * c-basic-offset: 4
1133 * c-hanging-comment-ender-p: nil
1134 * End:
1135 */
Note: See TracBrowser for help on using the browser.