Completed
Push — development ( 86ad30...7b6aa4 )
by Sebastian
05:00
created

include/classes/user.class.php (13 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
$defflip = (!cfip()) ? exit(header('HTTP/1.1 401 Unauthorized')) : 1;
3
4
class User extends Base {
5
  protected $table = 'accounts';
6
  private $userID = false;
7
  private $user = array();
8
9
  /**
10
   * We allow changing the database for shared accounts across pools
11
   * Load the config on construct so we can assign the DB name
12
   * @param config array MPOS configuration
13
   * @return none
14
   **/
15
  public function __construct($config) {
16
    $this->setConfig($config);
17
    $this->table = $this->config['db']['shared']['accounts'] . '.' . $this->table;
18
  }
19
20
  // get and set methods
21
  private function getHash($string, $version=0, $pepper='') {
22
    switch($version) {
23
    case 0:
24
      return hash('sha256', $string.$this->salt);
25
      break;
26
    case 1:
27
      return '$' . $version . '$' . $pepper . '$' . hash('sha256', $string.$this->salt.$pepper);
28
      break;
29
    }
30
  }
31
  public function getUserName($id) {
32
    return $this->getSingle($id, 'username', 'id');
33
  }
34
  public function getUserNameAnon($id) {
35
    return $this->getSingle($id, 'is_anonymous', 'id');
36
  }
37
  public function getUserNameByEmail($email) {
38
    return $this->getSingle($email, 'username', 'email', 's');
39
  }
40
  public function getUserId($username, $lower=false) {
41
    return $this->getSingle($username, 'id', 'username', 's', $lower);
42
  }
43
  public function getUserIdByEmail($email, $lower=false) {
44
    return $this->getSingle($email, 'id', 'email', 's', $lower);
45
  }
46
  public function getUserEmail($username, $lower=false) {
47
    return $this->getSingle($username, 'email', 'username', 's', $lower);
48
  }
49
  public function getUserEmailById($id) {
50
    return $this->getSingle($id, 'email', 'id', 'i');
51
  }
52
  public function getUserPasswordHashById($id) {
53
    return $this->getSingle($id, 'pass', 'id', 'i');
54
  }
55
  public function getUserPinHashById($id) {
56
    return $this->getSingle($id, 'pin', 'id', 'i');
57
  }
58
  public function getUserNoFee($id) {
59
    return $this->getSingle($id, 'no_fees', 'id');
60
  }
61
  public function getUserDonatePercent($id) {
62
    return $this->getDonatePercent($id);
63
  }
64
  public function getUserAdmin($id) {
65
    return $this->getSingle($id, 'is_admin', 'id');
66
  }
67
  public function getUserLocked($id) {
68
    return $this->getSingle($id, 'is_locked', 'id');
69
  }
70
  public function getUserIp($id) {
71
    return $this->getSingle($id, 'loggedIp', 'id');
72
  }
73
  public function getLastLogin($id) {
74
    return $this->getSingle($id, 'last_login', 'id');
75
  }
76
  public function getEmail($email) {
77
    return $this->getSingle($email, 'email', 'email', 's');
78
  }
79
  public function getUserFailed($id) {
80
   return $this->getSingle($id, 'failed_logins', 'id');
81
  }
82
  public function getUserPinFailed($id) {
83
   return $this->getSingle($id, 'failed_pins', 'id');
84
  }
85
  public function isNoFee($id) {
86
    return $this->getUserNoFee($id);
87
  }
88
  public function isLocked($id) {
89
    return $this->getUserLocked($id);
90
  }
91
  public function isAdmin($id) {
92
    return $this->getUserAdmin($id);
93
  }
94
  public function getSignupTime($id) {
95
    return $this->getSingle($id, 'signup_timestamp', 'id');
96
  }
97
  public function changeNoFee($id) {
98
    $field = array('name' => 'no_fees', 'type' => 'i', 'value' => !$this->isNoFee($id));
99
    $this->log->log("warn", $this->getUserName($id)." changed no_fees to ".$this->isNoFee($id));
100
    return $this->updateSingle($id, $field);
101
  }
102
  public function setLocked($id, $value) {
103
    $field = array('name' => 'is_locked', 'type' => 'i', 'value' => $value);
104
    $this->log->log("warn", $this->getUserName($id)." changed is_locked to $value");
105
    return $this->updateSingle($id, $field);
106
  }
107
  public function changeAdmin($id) {
108
    $field = array('name' => 'is_admin', 'type' => 'i', 'value' => !$this->isAdmin($id));
109
    $this->log->log("warn", $this->getUserName($id)." changed is_admin to ".$this->isAdmin($id));
110
    return $this->updateSingle($id, $field);
111
  }
112
  public function setUserFailed($id, $value) {
113
    $field = array( 'name' => 'failed_logins', 'type' => 'i', 'value' => $value);
114
    return $this->updateSingle($id, $field);
115
  }
116
  public function setUserPinFailed($id, $value) {
117
    $field = array( 'name' => 'failed_pins', 'type' => 'i', 'value' => $value);
118
    return $this->updateSingle($id, $field);
119
  }
120
  private function incUserFailed($id) {
121
    $field = array( 'name' => 'failed_logins', 'type' => 'i', 'value' => $this->getUserFailed($id) + 1);
122
    return $this->updateSingle($id, $field);
123
  }
124
  private function incUserPinFailed($id) {
125
    $field = array( 'name' => 'failed_pins', 'type' => 'i', 'value' => $this->getUserPinFailed($id) + 1);
126
    return $this->updateSingle($id, $field);
127
  }
128
  private function setUserIp($id, $ip) {
129
    $field = array( 'name' => 'loggedIp', 'type' => 's', 'value' => $ip );
130
    return $this->updateSingle($id, $field);
131
  }
132
133
  /**
134
   * Fetch all users for administrative tasks
135
   * @param none
136
   * @return data array All users with db columns as array fields
137
   **/
138
  public function getUsers($filter='%') {
139
    $stmt = $this->mysqli->prepare("SELECT * FROM " . $this->getTableName() . " WHERE username LIKE ?");
140
    if ($this->checkStmt($stmt) && $stmt->bind_param('s', $filter) && $stmt->execute() && $result = $stmt->get_result()) {
141
      return $result->fetch_all(MYSQLI_ASSOC);
142
    }
143
  }
144
145
  /**
146
   * Fetch last registered users for administrative tasks
147
   * @param none
148
   * @return data array All users with db columns as array fields
149
   **/
150 View Code Duplication
  public function getLastRegisteredUsers($limit=10,$start=0) {
0 ignored issues
show
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
151
    $this->debug->append("STA " . __METHOD__, 4);
152
    $invitation = new Invitation();
153
    $invitation->setMysql($this->mysqli);
154
    $invitation->setDebug($this->debug);
155
    $invitation->setLog($this->log);
156
    $stmt = $this->mysqli->prepare("
157
    	SELECT a.id,a.username as mposuser,a.email,a.signup_timestamp,u.username AS inviter FROM " . $this->getTableName() . " AS a
158
    	LEFT JOIN " . $invitation->getTableName() . " AS i
159
    	ON a.email = i.email
160
    	LEFT JOIN " . $this->getTableName() . " AS u
161
    	ON i.account_id = u.id
162
    	ORDER BY a.id DESC LIMIT ?,?");
163
    if ($this->checkStmt($stmt) && $stmt->bind_param("ii", $start, $limit) && $stmt->execute() && $result = $stmt->get_result()) {
164
      return $result->fetch_all(MYSQLI_ASSOC);
165
    }
166
  }
167
168
  /**
169
   * Fetch Top 10 Inviters
170
   * @param none
171
   * @return data array All users with db columns as array fields
172
   **/
173 View Code Duplication
  public function getTopInviters($limit=10,$start=0) {
0 ignored issues
show
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
174
    $this->debug->append("STA " . __METHOD__, 4);
175
    $invitation = new Invitation();
176
    $invitation->setMysql($this->mysqli);
177
    $invitation->setDebug($this->debug);
178
    $invitation->setLog($this->log);
179
    $stmt = $this->mysqli->prepare("
180
    	SELECT COUNT(i.account_id) AS invitationcount,a.id,a.username,a.email,
181
    	(SELECT COUNT(account_id) FROM " . $invitation->getTableName() . " WHERE account_id = i.account_id AND is_activated = 1 GROUP BY account_id) AS activated
182
    	FROM " . $invitation->getTableName() . " AS i
183
    	LEFT JOIN " . $this->getTableName() . " AS a
184
    	ON a.id = i.account_id
185
    	GROUP BY i.account_id
186
    	ORDER BY invitationcount ASC
187
    	LIMIT ?,?");
188
    if ($this->checkStmt($stmt) && $stmt->bind_param("ii", $start, $limit) && $stmt->execute() && $result = $stmt->get_result()) {
189
      return $result->fetch_all(MYSQLI_ASSOC);
190
    }
191
  }
192
193
  /**
194
   * Check user login
195
   * @param username string Username
196
   * @param password string Password
197
   * @return bool
198
   **/
199
  public function checkLogin($username, $password) {
0 ignored issues
show
checkLogin uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
200
    $this->debug->append("STA " . __METHOD__, 4);
201
    $this->debug->append("Checking login for $username with password $password", 2);
202
    if (empty($username) || empty($password)) {
203
      $this->setErrorMessage("Invalid username or password.");
204
      return false;
205
    }
206
    if (!filter_var($username, FILTER_VALIDATE_EMAIL)) {
207
      $this->debug->append("Not an e-mail address, rejecting login", 2);
208
      $this->setErrorMessage("Please login with your e-mail address");
209
      return false;
210
    } else {
211
      $this->debug->append("Username is an e-mail: $username", 2);
212
      if (!$username = $this->getUserNameByEmail($username)) {
213
        $this->setErrorMessage("Invalid username or password.");
214
        return false;
215
      }
216
    }
217
    if ($this->isLocked($this->getUserId($username))) {
218
      $this->setErrorMessage('Account locked. Please Check your Email for instructions to unlock.');
219
      return false;
220
    }
221
    if ($this->checkUserPassword($username, $password)) {
222
      // delete notification cookies
223
      setcookie("motd-box", "", time()-3600);
224
      setcookie("lastlogin-box", "", time()-3600);
225
      setcookie("backend-box", "", time()-3600);
226
      // rest of login process
227
      $uid = $this->getUserId($username);
228
      $lastLoginTime = $this->getLastLogin($uid);
229
      $this->updateLoginTimestamp($uid);
230
      $getIPAddress = $this->getUserIp($uid);
231
      if ($getIPAddress !== $this->getCurrentIP()) {
232
        $this->log->log("warn", "$username has logged in with a different IP, saved is [$getIPAddress]");
233
      }
234
      $setIPAddress = $this->setUserIp($uid, $_SERVER['REMOTE_ADDR']);
235
      $this->createSession($username, $getIPAddress, $lastLoginTime);
236
      if ($setIPAddress) {
237
        // send a notification if success_login is active
238
        $uid = $this->getUserId($username);
239
        $notifs = new Notification();
240
        $notifs->setDebug($this->debug);
241
        $notifs->setMysql($this->mysqli);
242
        $notifs->setSmarty($this->smarty);
243
        $notifs->setConfig($this->config);
244
        $notifs->setSetting($this->setting);
245
        $notifs->setErrorCodes($this->aErrorCodes);
246
        $ndata = $notifs->getNotificationSettings($uid);
247
        if ((array_key_exists('push_success_lo', $ndata) && $ndata['push_success_lo']) || (array_key_exists('success_login', $ndata) && $ndata['success_login'])){
248
          // seems to be active, let's send it
249
          $aDataN['username'] = $username;
250
          $aDataN['email'] = $this->getUserEmail($username);
251
          $aDataN['subject'] = 'Successful login notification';
252
          $aDataN['LOGINIP'] = $this->getCurrentIP();
253
          $aDataN['LOGINUSER'] = $username;
254
          $aDataN['LOGINTIME'] = date('m/d/y H:i:s');
255
          $notifs->sendNotification($uid, 'success_login', $aDataN);
256
        }
257
        return true;
258
      }
259
    }
260
    $this->setErrorMessage("Invalid username or password");
261
    $this->log->log('error', "Authentication failed for $username");
262 View Code Duplication
    if ($id = $this->getUserId($username)) {
263
      $this->incUserFailed($id);
264
      // Check if this account should be locked
265
      if (isset($this->config['maxfailed']['login']) && $this->getUserFailed($id) >= $this->config['maxfailed']['login']) {
266
        $this->setLocked($id, 1);
267
        $this->log->log("warn", "$username locked due to failed logins, saved is [".$this->getUserIp($this->getUserId($username))."]");
268
        if ($token = $this->token->createToken('account_unlock', $id)) {
269
          $aData['token'] = $token;
270
          $aData['username'] = $username;
271
          $aData['email'] = $this->getUserEmail($username);
272
          $aData['subject'] = 'Account auto-locked';
273
          $this->mail->sendMail('notifications/locked', $aData);
274
        }
275
      }
276
    }
277
278
    return false;
279
  }
280
281
  /**
282
   * Check the users PIN for confirmation
283
   * @param userID int User ID
284
   * @param pin int PIN to check
285
   * @return bool
286
   **/
287
  public function checkPin($userId, $pin='') {
288
    $this->debug->append("STA " . __METHOD__, 4);
289
    $this->debug->append("Confirming PIN for $userId and pin $pin", 2);
290
    $strPinHash = $this->getUserPinHashById($userId);
291
    $aPin = explode('$', $strPinHash);
292
    count($aPin) == 1 ? $pin_hash = $this->getHash($pin, 0) : $pin_hash = $this->getHash($pin, $aPin[1], $aPin[2]);
293
    $stmt = $this->mysqli->prepare("SELECT pin FROM $this->table WHERE id = ? AND pin = ? LIMIT 1");
294 View Code Duplication
    if ($stmt->bind_param('is', $userId, $pin_hash) && $stmt->execute() && $stmt->bind_result($row_pin) && $stmt->fetch()) {
0 ignored issues
show
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
295
      $stmt->close();
296
      $this->setUserPinFailed($userId, 0);
297
      return ($pin_hash === $row_pin);
298
    }
299
    $this->log->log('info', $this->getUserName($userId).' incorrect pin');
300
    $this->incUserPinFailed($userId);
301
    // Check if this account should be locked
302 View Code Duplication
    if (isset($this->config['maxfailed']['pin']) && $this->getUserPinFailed($userId) >= $this->config['maxfailed']['pin']) {
303
      $this->setLocked($userId, 1);
304
      $this->log->log("warn", $this->getUserName($userId)." was locked due to incorrect pins");
305
      if ($token = $this->token->createToken('account_unlock', $userId)) {
306
        $username = $this->getUserName($userId);
307
        $aData['token'] = $token;
308
        $aData['username'] = $username;
309
        $aData['email'] = $this->getUserEmail($username);
310
        $aData['subject'] = 'Account auto-locked';
311
        $this->mail->sendMail('notifications/locked', $aData);
312
      }
313
      $this->logoutUser();
314
    }
315
    return false;
316
  }
317
318
  public function generatePin($userID, $current) {
319
    $this->debug->append("STA " . __METHOD__, 4);
320
    $username = $this->getUserName($userID);
321
    $email = $this->getUserEmail($username);
322
    $strPasswordHash = $this->getUserPasswordHashById($userID);
323
    $aPassword = explode('$', $strPasswordHash);
324
    count($aPassword) == 1 ? $password_hash = $this->getHash($current, 0) : $password_hash = $this->getHash($current, $aPassword[1], $aPassword[2]);
325
    $newpin = intval( '0' . rand(1,9) . rand(0,9) . rand(0,9) . rand(0,9) );
326
    $aData['username'] = $username;
327
    $aData['email'] = $email;
328
    $aData['pin'] = $newpin;
329
    $newpin = $this->getHash($newpin, HASH_VERSION, bin2hex(openssl_random_pseudo_bytes(32)));
330
    $aData['subject'] = 'PIN Reset Request';
331
    $stmt = $this->mysqli->prepare("UPDATE $this->table SET pin = ? WHERE ( id = ? AND pass = ? )");
332
    if ($this->checkStmt($stmt) && $stmt->bind_param('sis', $newpin, $userID, $password_hash) && $stmt->execute()) {
333
      if ($stmt->errno == 0 && $stmt->affected_rows === 1) {
334
        if ($this->mail->sendMail('pin/reset', $aData)) {
335
          $this->log->log("info", "$username was sent a pin reset e-mail");
336
          return true;
337
        } else {
338
          $this->log->log("warn", "$username request a pin reset but failed to send mail");
339
          $this->setErrorMessage('Unable to send mail to your address');
340
          return false;
341
        }
342
      }
343
    }
344
    $this->log->log("warn", "$username incorrect pin reset attempt");
345
    $this->setErrorMessage( 'Unable to generate PIN, current password incorrect?' );
346
    return false;
347
}
348
349
  /**
350
   * Get all users that have auto payout setup
351
   * @param none
352
   * @return data array All users with payout setup
353
   **/
354
  public function getAllAutoPayout() {
355
    $this->debug->append("STA " . __METHOD__, 4);
356
    $stmt = $this->mysqli->prepare("
357
      SELECT
358
        a.id, a.username, ca.coin_address AS coin_address, ca.ap_threshold
359
      FROM " . $this->getTableName() . " AS a
360
      LEFT JOIN " . $this->coin_address->getTableName() . " AS ca
361
      ON a.id = ca.account_id
362
      WHERE ca.ap_threshold > 0 AND ca.currency = ?
363
      AND ca.coin_address IS NOT NULL
364
      ");
365 View Code Duplication
    if ( $this->checkStmt($stmt) && $stmt->bind_param('s', $this->config['currency']) && $stmt->execute() && $result = $stmt->get_result()) {
366
      return $result->fetch_all(MYSQLI_ASSOC);
367
    }
368
    $this->debug->append("Unable to fetch users with AP set");
369
    return false;
370
  }
371
372
  /**
373
   * Fetch users donation value 
374
   * @param userID int UserID
375
   * @return data string Coin Address
376
   **/
377
  public function getDonatePercent($userID) {
378
    $this->debug->append("STA " . __METHOD__, 4);
379
    $dPercent = $this->getSingle($userID, 'donate_percent', 'id');
380
    if ($dPercent > 100) $dPercent = 100;
381
    if ($dPercent < 0) $dPercent = 0;
382
    return $dPercent;
383
  }
384
385
  /**
386
   * Send e-mail to confirm a change for 2fa
387
   * @param strType string Token type name
388
   * @param userID int User ID
389
   * @return bool
390
   */
391
  public function sendChangeConfigEmail($strType, $userID) {
392
    $exists = $this->token->doesTokenExist($strType, $userID);
393
    if ($exists == 0) {
394
      $token = $this->token->createToken($strType, $userID);
395
      $aData['token'] = $token;
396
      $aData['username'] = $this->getUserName($userID);
397
      $aData['email'] = $this->getUserEmail($aData['username']);
398
      switch ($strType) {
399
      	case 'account_edit':
400
      	  $aData['subject'] = 'Account detail change confirmation';
401
      	  break;
402
      	case 'change_pw':
403
      	  $aData['subject'] = 'Account password change confirmation';
404
      	  break;
405
      	case 'withdraw_funds':
406
      	  $aData['subject'] = 'Manual payout request confirmation';
407
      	  break;
408
      	default:
409
      	  $aData['subject'] = '';
410
      }
411
      $this->log->log("info", $aData['username']." was sent a $strType token e-mail");
412
      if ($this->mail->sendMail('notifications/'.$strType, $aData)) {
413
        return true;
414
      } else {
415
        $this->setErrorMessage('Failed to send the notification');
416
        $this->log->log("warn", $aData['username']." requested a $strType token but sending mail failed");
417
        return false;
418
      }
419
    }
420
    $this->log->log("warn", $this->getUserName($userID)." attempted to request multiple $strType tokens");
421
    $this->setErrorMessage('A request has already been sent to your e-mail address. Please wait an hour for it to expire.');
422
    return false;
423
  }
424
425
  /**
426
   * Update the accounts password
427
   * @param userID int User ID
428
   * @param current string Current password
429
   * @param new1 string New password
430
   * @param new2 string New password confirmation
431
   * @param strToken string Token for confirmation
432
   * @return bool
433
   **/
434
  public function updatePassword($userID, $current, $new1, $new2, $strToken) {
435
    $this->debug->append("STA " . __METHOD__, 4);
436
    if ($new1 !== $new2) {
437
      $this->setErrorMessage( 'New passwords do not match' );
438
      return false;
439
    }
440
    if ( strlen($new1) < 8 ) {
441
      $this->setErrorMessage( 'New password is too short, please use more than 8 chars' );
442
      return false;
443
    }
444
    $strPasswordHash = $this->getUserPasswordHashById($userID);
445
    $aPassword = explode('$', $strPasswordHash);
446
    count($aPassword) == 1 ? $password_hash = $this->getHash($current, 0) : $password_hash = $this->getHash($current, $aPassword[1], $aPassword[2]);
447
    $new = $this->getHash($new1, HASH_VERSION, bin2hex(openssl_random_pseudo_bytes(32)));
448 View Code Duplication
    if ($this->config['twofactor']['enabled'] && $this->config['twofactor']['options']['changepw']) {
449
      $tValid = $this->token->isTokenValid($userID, $strToken, 6);
450
      if ($tValid) {
451
        if ($this->token->deleteToken($strToken)) {
452
          $this->log->log("info", $this->getUserName($userID)." deleted change password token");
453
          // token deleted, continue
454
        } else {
455
          $this->log->log("warn", $this->getUserName($userID)." failed to delete the change password token");
456
          $this->setErrorMessage('Token deletion failed');
457
          return false;
458
        }
459
      } else {
460
        $this->log->log("error", $this->getUserName($userID)." attempted to use an invalid change password token");
461
        $this->setErrorMessage('Invalid token');
462
        return false;
463
      }
464
    }
465
    $stmt = $this->mysqli->prepare("UPDATE $this->table SET pass = ? WHERE ( id = ? AND pass = ? )");
466
    if ($this->checkStmt($stmt)) {
467
      $stmt->bind_param('sis', $new, $userID, $password_hash);
468
      $stmt->execute();
469
      if ($stmt->errno == 0 && $stmt->affected_rows === 1) {
470
        $this->log->log("info", $this->getUserName($userID)." updated password");
471
        return true;
472
      }
473
      $stmt->close();
474
    }
475
    $this->log->log("warn", $this->getUserName($userID)." incorrect password update attempt");
476
    $this->setErrorMessage( 'Unable to update password, current password wrong?' );
477
    return false;
478
  }
479
480
  /**
481
   * Update account information from the edit account page
482
   * @param userID int User ID
483
   * @param address string new coin address
484
   * @param threshold float auto payout threshold
485
   * @param donat float donation % of income
486
   * @param strToken string Token for confirmation
487
   * @return bool
488
   **/
489
  public function updateAccount($userID, $address, $threshold, $donate, $email, $timezone, $is_anonymous, $strToken) {
490
    $this->debug->append("STA " . __METHOD__, 4);
491
    $bUser = false;
492
    $donate = round($donate, 2);
493
    // number validation checks
494
    if (!is_numeric($threshold)) {
495
      $this->setErrorMessage('Invalid input for auto-payout');
496
      return false;
497
    } else if ($threshold < $this->config['ap_threshold']['min'] && $threshold != 0) {
498
      $this->setErrorMessage('Threshold below configured minimum of ' . $this->config['ap_threshold']['min']);
499
      return false;
500
    } else if ($threshold > $this->config['ap_threshold']['max']) {
501
      $this->setErrorMessage('Threshold above configured maximum of ' . $this->config['ap_threshold']['max']);
502
      return false;
503
    }
504
    if (!is_numeric($donate)) {
505
      $this->setErrorMessage('Invalid input for donation');
506
      return false;
507
    } else if ($donate < $this->config['donate_threshold']['min'] && $donate != 0) {
508
      $this->setErrorMessage('Donation below allowed ' . $this->config['donate_threshold']['min'] . '% limit');
509
      return false;
510
    } else if ($donate > 100) {
511
      $this->setErrorMessage('Donation above allowed 100% limit');
512
      return false;
513
    }
514
    if ($email != 'hidden' && $email != NULL && !filter_var($email, FILTER_VALIDATE_EMAIL)) {
515
      $this->setErrorMessage('Invalid email address');
516
      return false;
517
    }
518
    if (!empty($address)) {
519 View Code Duplication
      if ($address != $this->coin_address->getCoinAddress($userID) && $this->coin_address->existsCoinAddress($address)) {
520
        $this->setErrorMessage('Address is already in use');
521
        return false;
522
      }
523
      if ($this->bitcoin->can_connect() === true) {
524
        if (!$this->bitcoin->validateaddress($address)) {
525
          $this->setErrorMessage('Invalid coin address');
526
          return false;
527
        }
528
      } else {
529
        $this->setErrorMessage('Unable to connect to RPC server for coin address validation');
530
        return false;
531
      }
532
    } else {
533
      $address = NULL;
534
    }
535
536
    // Number sanitizer, just in case we fall through above
537
    $threshold = min($this->config['ap_threshold']['max'], max(0, floatval($threshold)));
538
    $donate = min(100, max(0, floatval($donate)));
539
540
    // twofactor - consume the token if it is enabled and valid
541 View Code Duplication
    if ($this->config['twofactor']['enabled'] && $this->config['twofactor']['options']['details']) {
542
      $tValid = $this->token->isTokenValid($userID, $strToken, 5);
543
      if ($tValid) {
544
        if ($this->token->deleteToken($strToken)) {
545
          $this->log->log("info", $this->getUserName($userID)." deleted account update token");
546
        } else {
547
          $this->setErrorMessage('Token deletion failed');
548
          $this->log->log("warn", $this->getUserName($userID)." updated their account details but failed to delete token");
549
          return false;
550
        }
551
      } else {
552
        $this->setErrorMessage('Invalid token');
553
        $this->log->log("warn", $this->getUserName($userID)." attempted to use an invalid token account update token");
554
        return false;
555
      }
556
    }
557
558
    // If we hide our email or it's not set, fetch current one to update
559
    if ($email == 'hidden' || $email == NULL)
560
      $email = $this->getUserEmailById($userID);
561
    // We passed all validation checks so update the account
562
    $stmt = $this->mysqli->prepare("UPDATE $this->table SET donate_percent = ?, email = ?, timezone = ?, is_anonymous = ? WHERE id = ?");
563
    if ($this->checkStmt($stmt) && $stmt->bind_param('dssii', $donate, $email, $timezone, $is_anonymous, $userID) && $stmt->execute()) {
564
      $this->log->log("info", $this->getUserName($userID)." updated their account details");
565
      // Update coin address and ap_threshold if coin_address is set
566
      if ($address) {
567
        if ($this->coin_address->update($userID, $address, $threshold)) {
568
          return true;
569
        }
570
      } else {
571
        if ($this->coin_address->remove($userID, $address)) {
572
          return true;
573
        }
574
      }
575
    }
576
    // Catchall
577
    $this->setErrorMessage('Failed to update your account');
578
    $this->debug->append('Account update failed: ' . $this->mysqli->error);
579
    return false;
580
  }
581
582
  /**
583
   * Check API key for authentication
584
   * @param key string API key hash
585
   * @return bool
586
   **/
587
  public function checkApiKey($key) {
588
    $this->debug->append("STA " . __METHOD__, 4);
589
    if (!is_string($key)) return false;
590
    $stmt = $this->mysqli->prepare("SELECT api_key, id FROM $this->table WHERE api_key = ? LIMIT 1");
591 View Code Duplication
    if ($this->checkStmt($stmt) && $stmt->bind_param("s", $key) && $stmt->execute() && $stmt->bind_result($api_key, $id) && $stmt->fetch()) {
0 ignored issues
show
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
592
      if ($api_key === $key)
593
        return $id;
594
    }
595
    header("HTTP/1.1 401 Unauthorized");
596
    die('Access denied');
597
  }
598
599
  /**
600
   * Check a password for a user
601
   * @param username string Username
602
   * @param password string Password
603
   * @return bool
604
   **/
605
  private function checkUserPassword($username, $password) {
606
    $this->debug->append("STA " . __METHOD__, 4);
607
    $user = array();
608
    $stmt = $this->mysqli->prepare("SELECT username, pass, id, timezone, is_admin FROM $this->table WHERE LOWER(username) = LOWER(?) LIMIT 1");
609
    if ($this->checkStmt($stmt) && $stmt->bind_param('s', $username) && $stmt->execute() && $stmt->bind_result($row_username, $row_password, $row_id, $row_timezone, $row_admin)) {
610
      $stmt->fetch();
611
      $stmt->close();
612
      $aPassword = explode('$', $row_password);
613
      count($aPassword) == 1 ? $password_hash = $this->getHash($password, 0) : $password_hash = $this->getHash($password, $aPassword[1], $aPassword[2]);
614
      // Store the basic login information
615
      $this->user = array('username' => $row_username, 'id' => $row_id, 'timezone' => $row_timezone, 'is_admin' => $row_admin);
616
      return $password_hash === $row_password && strtolower($username) === strtolower($row_username);
617
    }
618
    return $this->sqlError();
619
  }
620
621
  /**
622
   * Create a PHP session for a user
623
   * @param username string Username to create session for
624
   * @return none
625
   **/
626
  private function createSession($username, $lastIP='', $lastLoginTime='') {
0 ignored issues
show
The parameter $username is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
createSession uses the super-global variable $_SESSION which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
createSession uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
627
    $this->debug->append("STA " . __METHOD__, 4);
628
    $this->debug->append("Log in user to _SESSION", 2);
629
    if (!empty($lastIP) && (!empty($lastLoginTime))) {
630
      $_SESSION['last_ip_pop'] = array($lastIP, $lastLoginTime);
631
    }
632
    session_regenerate_id(true);
633
    $_SESSION['AUTHENTICATED'] = '1';
634
    // $this->user from checkUserPassword
635
    $_SESSION['USERDATA'] = $this->user;
636
    if ($this->config['protect_session_state']) {
637
      $_SESSION['STATE'] = md5($_SESSION['USERDATA']['username'].$_SESSION['USERDATA']['id'].@$_SERVER['HTTP_USER_AGENT']);
638
    }
639
  }
640
641
  /**
642
   * Update users last_login timestamp
643
   * @param id int UserID
644
   * @return bool true of false
645
   **/
646
  private function updateLoginTimestamp($id) {
647
    $field = array('name' => 'last_login', 'type' => 'i', 'value' => time());
648
    return $this->updateSingle($id, $field);
649
  }
650
651
  /**
652
   * Log out current user, destroy the session
653
   * @param none
654
   * @return true
655
   **/
656
  public function logoutUser() {
0 ignored issues
show
logoutUser uses the super-global variable $_SESSION which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
logoutUser uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
657
    $this->debug->append("STA " . __METHOD__, 4);
658
    // Unset all of the session variables
659
    $_SESSION = array();
660
    // As we're killing the sesison, also kill the cookie!
661
    setcookie(session_name(), '', time() - 42000);
662
    // Destroy the session.
663
    session_destroy();
664
    // Enforce generation of a new Session ID and delete the old
665
    session_regenerate_id(true);
666
667
    // Enforce a page reload and point towards login with referrer included, if supplied
668
    $port = ($_SERVER["SERVER_PORT"] == "80" || $_SERVER["SERVER_PORT"] == "443") ? "" : (":".$_SERVER["SERVER_PORT"]);
669
    $pushto = $_SERVER['SCRIPT_NAME'].'?page=login';
670
    $location = (@$_SERVER['HTTPS'] == 'on') ? 'https://' . $_SERVER['SERVER_NAME'] . $port . $pushto : 'http://' . $_SERVER['SERVER_NAME'] . $port . $pushto;
671
    if (!headers_sent()) header('Location: ' . $location);
672
    exit('<meta http-equiv="refresh" content="0; url=' . $location . '"/>');
673
  }
674
675
  /**
676
   * Get all users for admin panel
677
   **/
678
  public function getAllUsers($filter='%') {
679
    $this->debug->append("STA " . __METHOD__, 4);
680
    $stmt = $this->mysqli->prepare("
681
      SELECT
682
      a.id AS id,
683
      a.username AS username
684
      FROM " . $this->getTableName() . " AS a
685
      WHERE a.username LIKE ?
686
      GROUP BY username");
687 View Code Duplication
    if ($this->checkStmt($stmt) && $stmt->bind_param('s', $filter) && $stmt->execute() && $result = $stmt->get_result()) {
688
      while ($row = $result->fetch_assoc()) {
689
        $aData[$row['id']] = $row['username'];
690
      }
691
      return $aData;
692
    }
693
    return false;
694
  }
695
696
  /**
697
   * Fetch this classes table name
698
   * @return table string This classes table name
699
   **/
700
  public function getTableName() {
701
    $this->debug->append("STA " . __METHOD__, 4);
702
    return $this->table;
703
  }
704
705
  /**
706
   * Fetch some basic user information to store for later user
707
   * @param userID int User ID
708
   * return data array Database fields as used in SELECT
709
   **/
710
  public function getUserData($userID) {
711
    $this->debug->append("STA " . __METHOD__, 4);
712
    $this->debug->append("Fetching user information for user id: $userID");
713
    $stmt = $this->mysqli->prepare("
714
      SELECT
715
      id AS id, username, pin, api_key, is_admin, is_anonymous, email, timezone, no_fees,
716
      IFNULL(donate_percent, '0') as donate_percent
717
      FROM " . $this->getTableName() . "
718
      WHERE id = ? LIMIT 0,1");
719
    if ($this->checkStmt($stmt) && $stmt->bind_param('i', $userID) && $stmt->execute() && $result = $stmt->get_result()) {
720
      $aData = $result->fetch_assoc();
721
      $aData['coin_address'] = $this->coin_address->getCoinAddress($userID);
722
      if (! $aData['ap_threshold'] = $this->coin_address->getAPThreshold($userID))
723
        $aData['ap_threshold'] = 0;
724
      $stmt->close();
725
      return $aData;
726
    }
727
    $this->debug->append("Failed to fetch user information for $userID");
728
    return $this->sqlError();
729
  }
730
731
  /**
732
   * Register a new user in the system
733
   * @param username string Username
734
   * @param password1 string Password
735
   * @param password2 string Password verification
736
   * @param pin int 4 digit PIN code
737
   * @param email1 string Email address
738
   * @param email2 string Email confirmation
739
   * @return bool
740
   **/
741
  public function register($username, $coinaddress, $password1, $password2, $pin, $email1='', $email2='', $tac='', $strToken='') {
742
    $this->debug->append("STA " . __METHOD__, 4);
743
    if ($tac != 1) {
744
      $this->setErrorMessage('You need to accept our <a href="'.$_SERVER['SCRIPT_NAME'].'?page=tac" target="_blank">Terms and Conditions</a>');
745
      return false;
746
    }
747
    if (strlen($username) > 40) {
748
      $this->setErrorMessage('Username exceeding character limit');
749
      return false;
750
    }
751
    if (!is_null($coinaddress)) {
752
      if ($this->coin_address->existsCoinAddress($coinaddress)) {
753
        $this->setErrorMessage('Coin address is already taken');
754
        return false;
755
      }
756
      if (!$this->bitcoin->validateaddress($coinaddress)) {
757
        $this->setErrorMessage('Coin address is not valid');
758
        return false;
759
      }
760
    }
761
    if (preg_match('/[^a-z_\-0-9]/i', $username)) {
762
      $this->setErrorMessage('Username may only contain alphanumeric characters');
763
      return false;
764
    }
765
    if ($this->getEmail($email1)) {
766
      $this->setErrorMessage( 'This e-mail address is already taken' );
767
      return false;
768
    }
769
    if (strlen($password1) < 8) {
770
      $this->setErrorMessage( 'Password is too short, minimum of 8 characters required' );
771
      return false;
772
    }
773
    if ($password1 !== $password2) {
774
      $this->setErrorMessage( 'Password do not match' );
775
      return false;
776
    }
777 View Code Duplication
    if (empty($email1) || !filter_var($email1, FILTER_VALIDATE_EMAIL)) {
778
      $this->setErrorMessage( 'Invalid e-mail address' );
779
      return false;
780
    }
781
    if ($email1 !== $email2) {
782
      $this->setErrorMessage( 'E-mail do not match' );
783
      return false;
784
    }
785
    if (!is_numeric($pin) || strlen($pin) > 4 || strlen($pin) < 4) {
786
      $this->setErrorMessage( 'Invalid PIN' );
787
      return false;
788
    }
789
    if (isset($strToken) && !empty($strToken)) {
790
      if ( ! $aToken = $this->token->getToken($strToken, 'invitation')) {
791
        $this->setErrorMessage('Unable to find token');
792
        return false;
793
      }
794
      // Circle dependency, so we create our own object here
795
      $invitation = new Invitation();
796
      $invitation->setMysql($this->mysqli);
797
      $invitation->setDebug($this->debug);
798
      $invitation->setLog($this->log);
799
      $invitation->setUser($this);
800
      $invitation->setConfig($this->config);
801
      if (!$invitation->setActivated($aToken['id'])) {
802
        $this->setErrorMessage('Unable to activate your invitation');
803
        return false;
804
      }
805
      if (!$this->token->deleteToken($strToken)) {
806
        $this->setErrorMessage('Unable to remove used token');
807
        $this->log->log("warn", "$username tried to register but failed to delete the invitation token");
808
        return false;
809
      }
810
    }
811
    if ($this->mysqli->query("SELECT id FROM $this->table LIMIT 1")->num_rows > 0) {
812
      ! $this->setting->getValue('accounts_confirm_email_disabled') ? $is_locked = 1 : $is_locked = 0;
813
      $is_admin = 0;
814
      $stmt = $this->mysqli->prepare("
815
        INSERT INTO $this->table (username, pass, email, signup_timestamp, pin, api_key, is_locked)
816
        VALUES (?, ?, ?, ?, ?, ?, ?)
817
        ");
818
    } else {
819
      $is_locked = 0;
820
      $is_admin = 1;
821
      $stmt = $this->mysqli->prepare("
822
        INSERT INTO $this->table (username, pass, email, signup_timestamp, pin, api_key, is_admin, is_locked)
823
        VALUES (?, ?, ?, ?, ?, ?, 1, ?)
824
        ");
825
    }
826
827
    // Create hashed strings using original string and salt
828
    $password_hash = $this->getHash($password1, HASH_VERSION, bin2hex(openssl_random_pseudo_bytes(32)));
829
    $pin_hash = $this->getHash($pin, HASH_VERSION, bin2hex(openssl_random_pseudo_bytes(32)));
830
    $apikey_hash = $this->getHash($username, 0);
831
    $username_clean = strip_tags($username);
832
    $signup_time = time();
833
834
    if ($this->checkStmt($stmt) && $stmt->bind_param('sssissi', $username_clean, $password_hash, $email1, $signup_time, $pin_hash, $apikey_hash, $is_locked) && $stmt->execute()) {
835
      $new_account_id = $this->mysqli->insert_id;
836
      if (!is_null($coinaddress)) $this->coin_address->add($new_account_id, $coinaddress);
837
      if (! $this->setting->getValue('accounts_confirm_email_disabled') && $is_admin != 1) {
838
        if ($token = $this->token->createToken('confirm_email', $stmt->insert_id)) {
839
          $aData['username'] = $username_clean;
840
          $aData['token'] = $token;
841
          $aData['email'] = $email1;
842
          $aData['subject'] = 'E-Mail verification';
843 View Code Duplication
          if (!$this->mail->sendMail('register/confirm_email', $aData)) {
844
            $this->setErrorMessage('Unable to request email confirmation: ' . $this->mail->getError());
845
            return false;
846
          }
847
          return true;
848
        } else {
849
          $this->setErrorMessage('Failed to create confirmation token');
850
          $this->debug->append('Unable to create confirm_email token: ' . $this->token->getError());
851
          return false;
852
        }
853
      } else {
854
        return true;
855
      }
856
    } else {
857
      $this->setErrorMessage( 'Unable to register' );
858
      $this->debug->append('Failed to insert user into DB: ' . $this->mysqli->error);
859
      echo $this->mysqli->error;
860
      if ($stmt->sqlstate == '23000') $this->setErrorMessage( 'Username or email already registered' );
861
      return false;
862
    }
863
    return false;
864
  }
865
866
  /**
867
   * User a one time token to reset a password
868
   * @param token string one time token
869
   * @param new1 string New password
870
   * @param new2 string New password verification
871
   * @return bool
872
   **/
873
  public function resetPassword($token, $new1, $new2) {
874
    $this->debug->append("STA " . __METHOD__, 4);
875
    if ($aToken = $this->token->getToken($token, 'password_reset')) {
876
      if ($new1 !== $new2) {
877
        $this->setErrorMessage( 'New passwords do not match' );
878
        return false;
879
      }
880
      if ( strlen($new1) < 8 ) { 
881
        $this->setErrorMessage( 'New password is too short, please use more than 8 chars' );
882
        return false;
883
      }
884
      $new_hash = $this->getHash($new1, HASH_VERSION, bin2hex(openssl_random_pseudo_bytes(32)));
885
      $stmt = $this->mysqli->prepare("UPDATE $this->table SET pass = ? WHERE id = ?");
886
      if ($this->checkStmt($stmt) && $stmt->bind_param('si', $new_hash, $aToken['account_id']) && $stmt->execute() && $stmt->affected_rows === 1) {
887
        if ($this->token->deleteToken($aToken['token'])) {
888
          return true;
889
        } else {
890
          $this->setErrorMessage('Unable to invalidate used token');
891
        }
892
      } else {
893
        $this->setErrorMessage('Unable to set new password or you chose the same password. Please use a different one.');
894
      }
895
    } else {
896
      $this->setErrorMessage('Invalid token: ' . $this->token->getError());
897
    }
898
    $this->debug->append('Failed to update password:' . $this->mysqli->error);
899
    return false;
900
  }
901
902
  /**
903
   * Reset a password by sending a password reset mail
904
   * @param username string Username to reset password for
905
   * @return bool
906
   **/
907
  public function initResetPassword($username) {
0 ignored issues
show
initResetPassword uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
908
    $this->debug->append("STA " . __METHOD__, 4);
909
    // Fetch the users mail address
910
    if (empty($username)) {
911
      $this->setErrorMessage("Username must not be empty");
912
      return false;
913
    }
914
    if (filter_var($username, FILTER_VALIDATE_EMAIL)) {
915
      $this->debug->append("Username is an e-mail: $username", 2);
916
      if (!$username = $this->getUserNameByEmail($username)) {
917
        $this->setErrorMessage("Invalid username or password.");
918
        return false;
919
      }
920
    }
921
    if (!$aData['email'] = $this->getUserEmail($username, true)) {
922
      $this->setErrorMessage("Please check your mail account to finish your password reset");
923
      return false;
924
    }
925 View Code Duplication
    if (!$aData['token'] = $this->token->createToken('password_reset', $this->getUserId($username, true))) {
926
      $this->setErrorMessage('Unable to setup token for password reset');
927
      return false;
928
    }
929
    $aData['username'] = $this->getUserName($this->getUserId($username, true));
930
    $aData['subject'] = 'Password Reset Request';
931
    if ($_SERVER['REMOTE_ADDR'] !== $this->getUserIp($this->getUserId($username, true))) {
932
      $this->log->log("warn", "$username requested password reset, saved IP is [".$this->getUserIp($this->getUserId($username, true))."]");
933
    } else {
934
      $this->log->log("info", "$username requested password reset, saved IP is [".$this->getUserIp($this->getUserId($username, true))."]");
935
    }
936 View Code Duplication
    if ($this->mail->sendMail('password/reset', $aData)) {
937
        return true;
938
      } else {
939
        $this->setErrorMessage('Unable to send mail to your address');
940
        return false;
941
      }
942
    return false;
943
  }
944
945
  /**
946
   * Check if a user is authenticated and allowed to login
947
   * Checks the $_SESSION for existing data
948
   * Destroys the session if account is now locked
949
   * @param none
950
   * @return bool
951
   **/
952
public function isAuthenticated($logout=true) {
0 ignored issues
show
isAuthenticated uses the super-global variable $_SESSION which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
isAuthenticated uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
953
    $this->debug->append("STA " . __METHOD__, 4);
954
    if ( @$_SESSION['AUTHENTICATED'] == true &&
955
         !$this->isLocked($_SESSION['USERDATA']['id']) &&
956
         $this->getUserIp($_SESSION['USERDATA']['id']) == $_SERVER['REMOTE_ADDR'] &&
957
         ( ! $this->config['protect_session_state'] ||
958
           (
959
             $this->config['protect_session_state'] && $_SESSION['STATE'] == md5($_SESSION['USERDATA']['username'].$_SESSION['USERDATA']['id'].@$_SERVER['HTTP_USER_AGENT'])
960
           )
961
         )
962
    ) return true;
963
    // Catchall
964
    $this->log->log('warn', 'Forcing logout, user is locked or IP changed mid session [hijack attempt?]');
965
    if ($logout == true) $this->logoutUser();
966
    return false;
967
  }
968
969
  /**
970
   * Convenience function to get IP address, no params is the same as REMOTE_ADDR
971
   * @param trustremote bool must be FALSE to checkcloudflare, checkclient or checkforwarded
972
   * @param checkcloudflare bool check HTTP_CF_CONNECTING_IP for a valid ip first
973
   * @param checkclient bool check HTTP_CLIENT_IP for a valid ip first
974
   * @param checkforwarded bool check HTTP_X_FORWARDED_FOR for a valid ip first
975
   * @return string IP address
976
   */
977
  public function getCurrentIP($trustremote=false, $checkcloudflare=true, $checkclient=false, $checkforwarded=true) {
978
    $cf = (isset($_SERVER['HTTP_CF_CONNECTING_IP'])) ? $_SERVER['HTTP_CF_CONNECTING_IP'] : false;
979
    $client = (isset($_SERVER['HTTP_CLIENT_IP'])) ? $_SERVER['HTTP_CLIENT_IP'] : false;
980
    $fwd = (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) ? $_SERVER['HTTP_X_FORWARDED_FOR'] : false;
981
    $remote = (isset($_SERVER['REMOTE_ADDR'])) ? $_SERVER['REMOTE_ADDR'] : @$_SERVER['REMOTE_ADDR'];
982
    // shared internet
983
    if (!$trustremote && $checkcloudflare && filter_var($cf, FILTER_VALIDATE_IP)) {
984
      // cloudflare
985
      return $cf;
986
    } else if (!$trustremote && $checkclient && filter_var($client, FILTER_VALIDATE_IP)) {
987
      return $client;
988
    } else if (!$trustremote && $checkforwarded && strpos($fwd, ',') !== false) {
989
      // multiple proxies
990
      $ips = explode(',', $fwd);
991
      return $ips[0];
992
    } else if (!$trustremote && $checkforwarded && filter_var($fwd, FILTER_VALIDATE_IP)) {
993
      // single
994
      return $fwd;
995
    } else {
996
      // as usual
997
      return $remote;
998
    }
999
  }
1000
}
1001
1002
// Make our class available automatically
1003
$user = new User($config);
1004
$user->setDebug($debug);
1005
$user->setLog($log);
1006
$user->setMysql($mysqli);
1007
$user->setSalt($config['SALT']);
1008
$user->setSmarty($smarty);
1009
$user->setMail($mail);
1010
$user->setToken($oToken);
1011
$user->setBitcoin($bitcoin);
1012
$user->setSetting($setting);
1013
$user->setCoinAddress($coin_address);
1014
$user->setErrorCodes($aErrorCodes);
1015