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

include/classes/user.class.php (11 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
    if ($stmt->bind_param('is', $userId, $pin_hash) && $stmt->execute() && $stmt->bind_result($row_pin) && $stmt->fetch()) {
295
      $this->setUserPinFailed($userId, 0);
296
      return ($pin_hash === $row_pin);
297
    }
298
    $this->log->log('info', $this->getUserName($userId).' incorrect pin');
299
    $this->incUserPinFailed($userId);
300
    // Check if this account should be locked
301 View Code Duplication
    if (isset($this->config['maxfailed']['pin']) && $this->getUserPinFailed($userId) >= $this->config['maxfailed']['pin']) {
302
      $this->setLocked($userId, 1);
303
      $this->log->log("warn", $this->getUserName($userId)." was locked due to incorrect pins");
304
      if ($token = $this->token->createToken('account_unlock', $userId)) {
305
        $username = $this->getUserName($userId);
306
        $aData['token'] = $token;
307
        $aData['username'] = $username;
308
        $aData['email'] = $this->getUserEmail($username);
309
        $aData['subject'] = 'Account auto-locked';
310
        $this->mail->sendMail('notifications/locked', $aData);
311
      }
312
      $this->logoutUser();
313
    }
314
    return false;
315
  }
316
317
  public function generatePin($userID, $current) {
318
    $this->debug->append("STA " . __METHOD__, 4);
319
    $username = $this->getUserName($userID);
320
    $email = $this->getUserEmail($username);
321
    $strPasswordHash = $this->getUserPasswordHashById($userID);
322
    $aPassword = explode('$', $strPasswordHash);
323
    count($aPassword) == 1 ? $password_hash = $this->getHash($current, 0) : $password_hash = $this->getHash($current, $aPassword[1], $aPassword[2]);
324
    $newpin = intval( '0' . rand(1,9) . rand(0,9) . rand(0,9) . rand(0,9) );
325
    $aData['username'] = $username;
326
    $aData['email'] = $email;
327
    $aData['pin'] = $newpin;
328
    $newpin = $this->getHash($newpin, HASH_VERSION, bin2hex(openssl_random_pseudo_bytes(32)));
329
    $aData['subject'] = 'PIN Reset Request';
330
    $stmt = $this->mysqli->prepare("UPDATE $this->table SET pin = ? WHERE ( id = ? AND pass = ? )");
331
    if ($this->checkStmt($stmt) && $stmt->bind_param('sis', $newpin, $userID, $password_hash) && $stmt->execute()) {
332
      if ($stmt->errno == 0 && $stmt->affected_rows === 1) {
333
        if ($this->mail->sendMail('pin/reset', $aData)) {
334
          $this->log->log("info", "$username was sent a pin reset e-mail");
335
          return true;
336
        } else {
337
          $this->log->log("warn", "$username request a pin reset but failed to send mail");
338
          $this->setErrorMessage('Unable to send mail to your address');
339
          return false;
340
        }
341
      }
342
    }
343
    $this->log->log("warn", "$username incorrect pin reset attempt");
344
    $this->setErrorMessage( 'Unable to generate PIN, current password incorrect?' );
345
    return false;
346
}
347
348
  /**
349
   * Get all users that have auto payout setup
350
   * @param none
351
   * @return data array All users with payout setup
352
   **/
353
  public function getAllAutoPayout() {
354
    $this->debug->append("STA " . __METHOD__, 4);
355
    $stmt = $this->mysqli->prepare("
356
      SELECT
357
        a.id, a.username, ca.coin_address AS coin_address, ca.ap_threshold
358
      FROM " . $this->getTableName() . " AS a
359
      LEFT JOIN " . $this->coin_address->getTableName() . " AS ca
360
      ON a.id = ca.account_id
361
      WHERE ca.ap_threshold > 0 AND ca.currency = ?
362
      AND ca.coin_address IS NOT NULL
363
      ");
364 View Code Duplication
    if ( $this->checkStmt($stmt) && $stmt->bind_param('s', $this->config['currency']) && $stmt->execute() && $result = $stmt->get_result()) {
365
      return $result->fetch_all(MYSQLI_ASSOC);
366
    }
367
    $this->debug->append("Unable to fetch users with AP set");
368
    return false;
369
  }
370
371
  /**
372
   * Fetch users donation value 
373
   * @param userID int UserID
374
   * @return data string Coin Address
375
   **/
376
  public function getDonatePercent($userID) {
377
    $this->debug->append("STA " . __METHOD__, 4);
378
    $dPercent = $this->getSingle($userID, 'donate_percent', 'id');
379
    if ($dPercent > 100) $dPercent = 100;
380
    if ($dPercent < 0) $dPercent = 0;
381
    return $dPercent;
382
  }
383
384
  /**
385
   * Send e-mail to confirm a change for 2fa
386
   * @param strType string Token type name
387
   * @param userID int User ID
388
   * @return bool
389
   */
390
  public function sendChangeConfigEmail($strType, $userID) {
391
    $exists = $this->token->doesTokenExist($strType, $userID);
392
    if ($exists == 0) {
393
      $token = $this->token->createToken($strType, $userID);
394
      $aData['token'] = $token;
395
      $aData['username'] = $this->getUserName($userID);
396
      $aData['email'] = $this->getUserEmail($aData['username']);
397
      switch ($strType) {
398
      	case 'account_edit':
399
      	  $aData['subject'] = 'Account detail change confirmation';
400
      	  break;
401
      	case 'change_pw':
402
      	  $aData['subject'] = 'Account password change confirmation';
403
      	  break;
404
      	case 'withdraw_funds':
405
      	  $aData['subject'] = 'Manual payout request confirmation';
406
      	  break;
407
      	default:
408
      	  $aData['subject'] = '';
409
      }
410
      $this->log->log("info", $aData['username']." was sent a $strType token e-mail");
411
      if ($this->mail->sendMail('notifications/'.$strType, $aData)) {
412
        return true;
413
      } else {
414
        $this->setErrorMessage('Failed to send the notification');
415
        $this->log->log("warn", $aData['username']." requested a $strType token but sending mail failed");
416
        return false;
417
      }
418
    }
419
    $this->log->log("warn", $this->getUserName($userID)." attempted to request multiple $strType tokens");
420
    $this->setErrorMessage('A request has already been sent to your e-mail address. Please wait an hour for it to expire.');
421
    return false;
422
  }
423
424
  /**
425
   * Update the accounts password
426
   * @param userID int User ID
427
   * @param current string Current password
428
   * @param new1 string New password
429
   * @param new2 string New password confirmation
430
   * @param strToken string Token for confirmation
431
   * @return bool
432
   **/
433
  public function updatePassword($userID, $current, $new1, $new2, $strToken) {
434
    $this->debug->append("STA " . __METHOD__, 4);
435
    if ($new1 !== $new2) {
436
      $this->setErrorMessage( 'New passwords do not match' );
437
      return false;
438
    }
439
    if ( strlen($new1) < 8 ) {
440
      $this->setErrorMessage( 'New password is too short, please use more than 8 chars' );
441
      return false;
442
    }
443
    $strPasswordHash = $this->getUserPasswordHashById($userID);
444
    $aPassword = explode('$', $strPasswordHash);
445
    count($aPassword) == 1 ? $password_hash = $this->getHash($current, 0) : $password_hash = $this->getHash($current, $aPassword[1], $aPassword[2]);
446
    $new = $this->getHash($new1, HASH_VERSION, bin2hex(openssl_random_pseudo_bytes(32)));
447 View Code Duplication
    if ($this->config['twofactor']['enabled'] && $this->config['twofactor']['options']['changepw']) {
448
      $tValid = $this->token->isTokenValid($userID, $strToken, 6);
449
      if ($tValid) {
450
        if ($this->token->deleteToken($strToken)) {
451
          $this->log->log("info", $this->getUserName($userID)." deleted change password token");
452
          // token deleted, continue
453
        } else {
454
          $this->log->log("warn", $this->getUserName($userID)." failed to delete the change password token");
455
          $this->setErrorMessage('Token deletion failed');
456
          return false;
457
        }
458
      } else {
459
        $this->log->log("error", $this->getUserName($userID)." attempted to use an invalid change password token");
460
        $this->setErrorMessage('Invalid token');
461
        return false;
462
      }
463
    }
464
    $stmt = $this->mysqli->prepare("UPDATE $this->table SET pass = ? WHERE ( id = ? AND pass = ? )");
465
    if ($this->checkStmt($stmt)) {
466
      $stmt->bind_param('sis', $new, $userID, $password_hash);
467
      $stmt->execute();
468
      if ($stmt->errno == 0 && $stmt->affected_rows === 1) {
469
        $this->log->log("info", $this->getUserName($userID)." updated password");
470
        return true;
471
      }
472
      $stmt->close();
473
    }
474
    $this->log->log("warn", $this->getUserName($userID)." incorrect password update attempt");
475
    $this->setErrorMessage( 'Unable to update password, current password wrong?' );
476
    return false;
477
  }
478
479
  /**
480
   * Update account information from the edit account page
481
   * @param userID int User ID
482
   * @param address string new coin address
483
   * @param threshold float auto payout threshold
484
   * @param donat float donation % of income
485
   * @param strToken string Token for confirmation
486
   * @return bool
487
   **/
488
  public function updateAccount($userID, $address, $threshold, $donate, $email, $timezone, $is_anonymous, $strToken) {
489
    $this->debug->append("STA " . __METHOD__, 4);
490
    $bUser = false;
491
    $donate = round($donate, 2);
492
    // number validation checks
493
    if (!is_numeric($threshold)) {
494
      $this->setErrorMessage('Invalid input for auto-payout');
495
      return false;
496
    } else if ($threshold < $this->config['ap_threshold']['min'] && $threshold != 0) {
497
      $this->setErrorMessage('Threshold below configured minimum of ' . $this->config['ap_threshold']['min']);
498
      return false;
499
    } else if ($threshold > $this->config['ap_threshold']['max']) {
500
      $this->setErrorMessage('Threshold above configured maximum of ' . $this->config['ap_threshold']['max']);
501
      return false;
502
    }
503
    if (!is_numeric($donate)) {
504
      $this->setErrorMessage('Invalid input for donation');
505
      return false;
506
    } else if ($donate < $this->config['donate_threshold']['min'] && $donate != 0) {
507
      $this->setErrorMessage('Donation below allowed ' . $this->config['donate_threshold']['min'] . '% limit');
508
      return false;
509
    } else if ($donate > 100) {
510
      $this->setErrorMessage('Donation above allowed 100% limit');
511
      return false;
512
    }
513
    if ($email != 'hidden' && $email != NULL && !filter_var($email, FILTER_VALIDATE_EMAIL)) {
514
      $this->setErrorMessage('Invalid email address');
515
      return false;
516
    }
517
    if (!empty($address)) {
518 View Code Duplication
      if ($address != $this->coin_address->getCoinAddress($userID) && $this->coin_address->existsCoinAddress($address)) {
519
        $this->setErrorMessage('Address is already in use');
520
        return false;
521
      }
522
      if ($this->bitcoin->can_connect() === true) {
523
        if (!$this->bitcoin->validateaddress($address)) {
524
          $this->setErrorMessage('Invalid coin address');
525
          return false;
526
        }
527
      } else {
528
        $this->setErrorMessage('Unable to connect to RPC server for coin address validation');
529
        return false;
530
      }
531
    } else {
532
      $address = NULL;
533
    }
534
535
    // Number sanitizer, just in case we fall through above
536
    $threshold = min($this->config['ap_threshold']['max'], max(0, floatval($threshold)));
537
    $donate = min(100, max(0, floatval($donate)));
538
539
    // twofactor - consume the token if it is enabled and valid
540 View Code Duplication
    if ($this->config['twofactor']['enabled'] && $this->config['twofactor']['options']['details']) {
541
      $tValid = $this->token->isTokenValid($userID, $strToken, 5);
542
      if ($tValid) {
543
        if ($this->token->deleteToken($strToken)) {
544
          $this->log->log("info", $this->getUserName($userID)." deleted account update token");
545
        } else {
546
          $this->setErrorMessage('Token deletion failed');
547
          $this->log->log("warn", $this->getUserName($userID)." updated their account details but failed to delete token");
548
          return false;
549
        }
550
      } else {
551
        $this->setErrorMessage('Invalid token');
552
        $this->log->log("warn", $this->getUserName($userID)." attempted to use an invalid token account update token");
553
        return false;
554
      }
555
    }
556
557
    // If we hide our email or it's not set, fetch current one to update
558
    if ($email == 'hidden' || $email == NULL)
559
      $email = $this->getUserEmailById($userID);
560
    // We passed all validation checks so update the account
561
    $stmt = $this->mysqli->prepare("UPDATE $this->table SET donate_percent = ?, email = ?, timezone = ?, is_anonymous = ? WHERE id = ?");
562
    if ($this->checkStmt($stmt) && $stmt->bind_param('dssii', $donate, $email, $timezone, $is_anonymous, $userID) && $stmt->execute()) {
563
      $this->log->log("info", $this->getUserName($userID)." updated their account details");
564
      // Update coin address and ap_threshold if coin_address is set
565
      if ($address) {
566
        if ($this->coin_address->update($userID, $address, $threshold)) {
567
          return true;
568
        }
569
      } else {
570
        if ($this->coin_address->remove($userID, $address)) {
571
          return true;
572
        }
573
      }
574
    }
575
    // Catchall
576
    $this->setErrorMessage('Failed to update your account');
577
    $this->debug->append('Account update failed: ' . $this->mysqli->error);
578
    return false;
579
  }
580
581
  /**
582
   * Check API key for authentication
583
   * @param key string API key hash
584
   * @return bool
585
   **/
586
  public function checkApiKey($key) {
587
    $this->debug->append("STA " . __METHOD__, 4);
588
    if (!is_string($key)) return false;
589
    $stmt = $this->mysqli->prepare("SELECT api_key, id FROM $this->table WHERE api_key = ? LIMIT 1");
590
    if ($this->checkStmt($stmt) && $stmt->bind_param("s", $key) && $stmt->execute() && $stmt->bind_result($api_key, $id) && $stmt->fetch()) {
591
      if ($api_key === $key)
592
        return $id;
593
    }
594
    header("HTTP/1.1 401 Unauthorized");
595
    die('Access denied');
596
  }
597
598
  /**
599
   * Check a password for a user
600
   * @param username string Username
601
   * @param password string Password
602
   * @return bool
603
   **/
604
  private function checkUserPassword($username, $password) {
605
    $this->debug->append("STA " . __METHOD__, 4);
606
    $user = array();
607
    $stmt = $this->mysqli->prepare("SELECT username, pass, id, timezone, is_admin FROM $this->table WHERE LOWER(username) = LOWER(?) LIMIT 1");
608
    if ($this->checkStmt($stmt) && $stmt->bind_param('s', $username) && $stmt->execute() && $stmt->bind_result($row_username, $row_password, $row_id, $row_timezone, $row_admin)) {
609
      $stmt->fetch();
610
      $stmt->close();
611
      $aPassword = explode('$', $row_password);
612
      count($aPassword) == 1 ? $password_hash = $this->getHash($password, 0) : $password_hash = $this->getHash($password, $aPassword[1], $aPassword[2]);
613
      // Store the basic login information
614
      $this->user = array('username' => $row_username, 'id' => $row_id, 'timezone' => $row_timezone, 'is_admin' => $row_admin);
615
      return $password_hash === $row_password && strtolower($username) === strtolower($row_username);
616
    }
617
    return $this->sqlError();
618
  }
619
620
  /**
621
   * Create a PHP session for a user
622
   * @param username string Username to create session for
623
   * @return none
624
   **/
625
  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...
626
    $this->debug->append("STA " . __METHOD__, 4);
627
    $this->debug->append("Log in user to _SESSION", 2);
628
    if (!empty($lastIP) && (!empty($lastLoginTime))) {
629
      $_SESSION['last_ip_pop'] = array($lastIP, $lastLoginTime);
630
    }
631
    session_regenerate_id(true);
632
    $_SESSION['AUTHENTICATED'] = '1';
633
    // $this->user from checkUserPassword
634
    $_SESSION['USERDATA'] = $this->user;
635
    if ($this->config['protect_session_state']) {
636
      $_SESSION['STATE'] = md5($_SESSION['USERDATA']['username'].$_SESSION['USERDATA']['id'].@$_SERVER['HTTP_USER_AGENT']);
637
    }
638
  }
639
640
  /**
641
   * Update users last_login timestamp
642
   * @param id int UserID
643
   * @return bool true of false
644
   **/
645
  private function updateLoginTimestamp($id) {
646
    $field = array('name' => 'last_login', 'type' => 'i', 'value' => time());
647
    return $this->updateSingle($id, $field);
648
  }
649
650
  /**
651
   * Log out current user, destroy the session
652
   * @param none
653
   * @return true
654
   **/
655
  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...
656
    $this->debug->append("STA " . __METHOD__, 4);
657
    // Unset all of the session variables
658
    $_SESSION = array();
659
    // As we're killing the sesison, also kill the cookie!
660
    setcookie(session_name(), '', time() - 42000);
661
    // Destroy the session.
662
    session_destroy();
663
    // Enforce generation of a new Session ID and delete the old
664
    session_regenerate_id(true);
665
666
    // Enforce a page reload and point towards login with referrer included, if supplied
667
    $port = ($_SERVER["SERVER_PORT"] == "80" || $_SERVER["SERVER_PORT"] == "443") ? "" : (":".$_SERVER["SERVER_PORT"]);
668
    $pushto = $_SERVER['SCRIPT_NAME'].'?page=login';
669
    $location = (@$_SERVER['HTTPS'] == 'on') ? 'https://' . $_SERVER['SERVER_NAME'] . $port . $pushto : 'http://' . $_SERVER['SERVER_NAME'] . $port . $pushto;
670
    if (!headers_sent()) header('Location: ' . $location);
671
    exit('<meta http-equiv="refresh" content="0; url=' . $location . '"/>');
672
  }
673
674
  /**
675
   * Get all users for admin panel
676
   **/
677
  public function getAllUsers($filter='%') {
678
    $this->debug->append("STA " . __METHOD__, 4);
679
    $stmt = $this->mysqli->prepare("
680
      SELECT
681
      a.id AS id,
682
      a.username AS username
683
      FROM " . $this->getTableName() . " AS a
684
      WHERE a.username LIKE ?
685
      GROUP BY username");
686 View Code Duplication
    if ($this->checkStmt($stmt) && $stmt->bind_param('s', $filter) && $stmt->execute() && $result = $stmt->get_result()) {
687
      while ($row = $result->fetch_assoc()) {
688
        $aData[$row['id']] = $row['username'];
689
      }
690
      return $aData;
691
    }
692
    return false;
693
  }
694
695
  /**
696
   * Fetch this classes table name
697
   * @return table string This classes table name
698
   **/
699
  public function getTableName() {
700
    $this->debug->append("STA " . __METHOD__, 4);
701
    return $this->table;
702
  }
703
704
  /**
705
   * Fetch some basic user information to store for later user
706
   * @param userID int User ID
707
   * return data array Database fields as used in SELECT
708
   **/
709
  public function getUserData($userID) {
710
    $this->debug->append("STA " . __METHOD__, 4);
711
    $this->debug->append("Fetching user information for user id: $userID");
712
    $stmt = $this->mysqli->prepare("
713
      SELECT
714
      id AS id, username, pin, api_key, is_admin, is_anonymous, email, timezone, no_fees,
715
      IFNULL(donate_percent, '0') as donate_percent
716
      FROM " . $this->getTableName() . "
717
      WHERE id = ? LIMIT 0,1");
718
    if ($this->checkStmt($stmt) && $stmt->bind_param('i', $userID) && $stmt->execute() && $result = $stmt->get_result()) {
719
      $aData = $result->fetch_assoc();
720
      $aData['coin_address'] = $this->coin_address->getCoinAddress($userID);
721
      if (! $aData['ap_threshold'] = $this->coin_address->getAPThreshold($userID))
722
        $aData['ap_threshold'] = 0;
723
      $stmt->close();
724
      return $aData;
725
    }
726
    $this->debug->append("Failed to fetch user information for $userID");
727
    return $this->sqlError();
728
  }
729
730
  /**
731
   * Register a new user in the system
732
   * @param username string Username
733
   * @param password1 string Password
734
   * @param password2 string Password verification
735
   * @param pin int 4 digit PIN code
736
   * @param email1 string Email address
737
   * @param email2 string Email confirmation
738
   * @return bool
739
   **/
740
  public function register($username, $coinaddress, $password1, $password2, $pin, $email1='', $email2='', $tac='', $strToken='') {
741
    $this->debug->append("STA " . __METHOD__, 4);
742
    if ($tac != 1) {
743
      $this->setErrorMessage('You need to accept our <a href="'.$_SERVER['SCRIPT_NAME'].'?page=tac" target="_blank">Terms and Conditions</a>');
744
      return false;
745
    }
746
    if (strlen($username) > 40) {
747
      $this->setErrorMessage('Username exceeding character limit');
748
      return false;
749
    }
750
    if (!is_null($coinaddress)) {
751
      if ($this->coin_address->existsCoinAddress($coinaddress)) {
752
        $this->setErrorMessage('Coin address is already taken');
753
        return false;
754
      }
755
      if (!$this->bitcoin->validateaddress($coinaddress)) {
756
        $this->setErrorMessage('Coin address is not valid');
757
        return false;
758
      }
759
    }
760
    if (preg_match('/[^a-z_\-0-9]/i', $username)) {
761
      $this->setErrorMessage('Username may only contain alphanumeric characters');
762
      return false;
763
    }
764
    if ($this->getEmail($email1)) {
765
      $this->setErrorMessage( 'This e-mail address is already taken' );
766
      return false;
767
    }
768
    if (strlen($password1) < 8) {
769
      $this->setErrorMessage( 'Password is too short, minimum of 8 characters required' );
770
      return false;
771
    }
772
    if ($password1 !== $password2) {
773
      $this->setErrorMessage( 'Password do not match' );
774
      return false;
775
    }
776 View Code Duplication
    if (empty($email1) || !filter_var($email1, FILTER_VALIDATE_EMAIL)) {
777
      $this->setErrorMessage( 'Invalid e-mail address' );
778
      return false;
779
    }
780
    if ($email1 !== $email2) {
781
      $this->setErrorMessage( 'E-mail do not match' );
782
      return false;
783
    }
784
    if (!is_numeric($pin) || strlen($pin) > 4 || strlen($pin) < 4) {
785
      $this->setErrorMessage( 'Invalid PIN' );
786
      return false;
787
    }
788
    if (isset($strToken) && !empty($strToken)) {
789
      if ( ! $aToken = $this->token->getToken($strToken, 'invitation')) {
790
        $this->setErrorMessage('Unable to find token');
791
        return false;
792
      }
793
      // Circle dependency, so we create our own object here
794
      $invitation = new Invitation();
795
      $invitation->setMysql($this->mysqli);
796
      $invitation->setDebug($this->debug);
797
      $invitation->setLog($this->log);
798
      $invitation->setUser($this);
799
      $invitation->setConfig($this->config);
800
      if (!$invitation->setActivated($aToken['id'])) {
801
        $this->setErrorMessage('Unable to activate your invitation');
802
        return false;
803
      }
804
      if (!$this->token->deleteToken($strToken)) {
805
        $this->setErrorMessage('Unable to remove used token');
806
        $this->log->log("warn", "$username tried to register but failed to delete the invitation token");
807
        return false;
808
      }
809
    }
810
    if ($this->mysqli->query("SELECT id FROM $this->table LIMIT 1")->num_rows > 0) {
811
      ! $this->setting->getValue('accounts_confirm_email_disabled') ? $is_locked = 1 : $is_locked = 0;
812
      $is_admin = 0;
813
      $stmt = $this->mysqli->prepare("
814
        INSERT INTO $this->table (username, pass, email, signup_timestamp, pin, api_key, is_locked)
815
        VALUES (?, ?, ?, ?, ?, ?, ?)
816
        ");
817
    } else {
818
      $is_locked = 0;
819
      $is_admin = 1;
820
      $stmt = $this->mysqli->prepare("
821
        INSERT INTO $this->table (username, pass, email, signup_timestamp, pin, api_key, is_admin, is_locked)
822
        VALUES (?, ?, ?, ?, ?, ?, 1, ?)
823
        ");
824
    }
825
826
    // Create hashed strings using original string and salt
827
    $password_hash = $this->getHash($password1, HASH_VERSION, bin2hex(openssl_random_pseudo_bytes(32)));
828
    $pin_hash = $this->getHash($pin, HASH_VERSION, bin2hex(openssl_random_pseudo_bytes(32)));
829
    $apikey_hash = $this->getHash($username, 0);
830
    $username_clean = strip_tags($username);
831
    $signup_time = time();
832
833
    if ($this->checkStmt($stmt) && $stmt->bind_param('sssissi', $username_clean, $password_hash, $email1, $signup_time, $pin_hash, $apikey_hash, $is_locked) && $stmt->execute()) {
834
      $new_account_id = $this->mysqli->insert_id;
835
      if (!is_null($coinaddress)) $this->coin_address->add($new_account_id, $coinaddress);
836
      if (! $this->setting->getValue('accounts_confirm_email_disabled') && $is_admin != 1) {
837
        if ($token = $this->token->createToken('confirm_email', $stmt->insert_id)) {
838
          $aData['username'] = $username_clean;
839
          $aData['token'] = $token;
840
          $aData['email'] = $email1;
841
          $aData['subject'] = 'E-Mail verification';
842 View Code Duplication
          if (!$this->mail->sendMail('register/confirm_email', $aData)) {
843
            $this->setErrorMessage('Unable to request email confirmation: ' . $this->mail->getError());
844
            return false;
845
          }
846
          return true;
847
        } else {
848
          $this->setErrorMessage('Failed to create confirmation token');
849
          $this->debug->append('Unable to create confirm_email token: ' . $this->token->getError());
850
          return false;
851
        }
852
      } else {
853
        return true;
854
      }
855
    } else {
856
      $this->setErrorMessage( 'Unable to register' );
857
      $this->debug->append('Failed to insert user into DB: ' . $this->mysqli->error);
858
      echo $this->mysqli->error;
859
      if ($stmt->sqlstate == '23000') $this->setErrorMessage( 'Username or email already registered' );
860
      return false;
861
    }
862
    return false;
863
  }
864
865
  /**
866
   * User a one time token to reset a password
867
   * @param token string one time token
868
   * @param new1 string New password
869
   * @param new2 string New password verification
870
   * @return bool
871
   **/
872
  public function resetPassword($token, $new1, $new2) {
873
    $this->debug->append("STA " . __METHOD__, 4);
874
    if ($aToken = $this->token->getToken($token, 'password_reset')) {
875
      if ($new1 !== $new2) {
876
        $this->setErrorMessage( 'New passwords do not match' );
877
        return false;
878
      }
879
      if ( strlen($new1) < 8 ) { 
880
        $this->setErrorMessage( 'New password is too short, please use more than 8 chars' );
881
        return false;
882
      }
883
      $new_hash = $this->getHash($new1, HASH_VERSION, bin2hex(openssl_random_pseudo_bytes(32)));
884
      $stmt = $this->mysqli->prepare("UPDATE $this->table SET pass = ? WHERE id = ?");
885
      if ($this->checkStmt($stmt) && $stmt->bind_param('si', $new_hash, $aToken['account_id']) && $stmt->execute() && $stmt->affected_rows === 1) {
886
        if ($this->token->deleteToken($aToken['token'])) {
887
          return true;
888
        } else {
889
          $this->setErrorMessage('Unable to invalidate used token');
890
        }
891
      } else {
892
        $this->setErrorMessage('Unable to set new password or you chose the same password. Please use a different one.');
893
      }
894
    } else {
895
      $this->setErrorMessage('Invalid token: ' . $this->token->getError());
896
    }
897
    $this->debug->append('Failed to update password:' . $this->mysqli->error);
898
    return false;
899
  }
900
901
  /**
902
   * Reset a password by sending a password reset mail
903
   * @param username string Username to reset password for
904
   * @return bool
905
   **/
906
  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...
907
    $this->debug->append("STA " . __METHOD__, 4);
908
    // Fetch the users mail address
909
    if (empty($username)) {
910
      $this->setErrorMessage("Username must not be empty");
911
      return false;
912
    }
913
    if (filter_var($username, FILTER_VALIDATE_EMAIL)) {
914
      $this->debug->append("Username is an e-mail: $username", 2);
915
      if (!$username = $this->getUserNameByEmail($username)) {
916
        $this->setErrorMessage("Invalid username or password.");
917
        return false;
918
      }
919
    }
920
    if (!$aData['email'] = $this->getUserEmail($username, true)) {
921
      $this->setErrorMessage("Please check your mail account to finish your password reset");
922
      return false;
923
    }
924 View Code Duplication
    if (!$aData['token'] = $this->token->createToken('password_reset', $this->getUserId($username, true))) {
925
      $this->setErrorMessage('Unable to setup token for password reset');
926
      return false;
927
    }
928
    $aData['username'] = $this->getUserName($this->getUserId($username, true));
929
    $aData['subject'] = 'Password Reset Request';
930
    if ($_SERVER['REMOTE_ADDR'] !== $this->getUserIp($this->getUserId($username, true))) {
931
      $this->log->log("warn", "$username requested password reset, saved IP is [".$this->getUserIp($this->getUserId($username, true))."]");
932
    } else {
933
      $this->log->log("info", "$username requested password reset, saved IP is [".$this->getUserIp($this->getUserId($username, true))."]");
934
    }
935 View Code Duplication
    if ($this->mail->sendMail('password/reset', $aData)) {
936
        return true;
937
      } else {
938
        $this->setErrorMessage('Unable to send mail to your address');
939
        return false;
940
      }
941
    return false;
942
  }
943
944
  /**
945
   * Check if a user is authenticated and allowed to login
946
   * Checks the $_SESSION for existing data
947
   * Destroys the session if account is now locked
948
   * @param none
949
   * @return bool
950
   **/
951
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...
952
    $this->debug->append("STA " . __METHOD__, 4);
953
    if ( @$_SESSION['AUTHENTICATED'] == true &&
954
         !$this->isLocked($_SESSION['USERDATA']['id']) &&
955
         $this->getUserIp($_SESSION['USERDATA']['id']) == $_SERVER['REMOTE_ADDR'] &&
956
         ( ! $this->config['protect_session_state'] ||
957
           (
958
             $this->config['protect_session_state'] && $_SESSION['STATE'] == md5($_SESSION['USERDATA']['username'].$_SESSION['USERDATA']['id'].@$_SERVER['HTTP_USER_AGENT'])
959
           )
960
         )
961
    ) return true;
962
    // Catchall
963
    $this->log->log('warn', 'Forcing logout, user is locked or IP changed mid session [hijack attempt?]');
964
    if ($logout == true) $this->logoutUser();
965
    return false;
966
  }
967
968
  /**
969
   * Convenience function to get IP address, no params is the same as REMOTE_ADDR
970
   * @param trustremote bool must be FALSE to checkcloudflare, checkclient or checkforwarded
971
   * @param checkcloudflare bool check HTTP_CF_CONNECTING_IP for a valid ip first
972
   * @param checkclient bool check HTTP_CLIENT_IP for a valid ip first
973
   * @param checkforwarded bool check HTTP_X_FORWARDED_FOR for a valid ip first
974
   * @return string IP address
975
   */
976
  public function getCurrentIP($trustremote=false, $checkcloudflare=true, $checkclient=false, $checkforwarded=true) {
977
    $cf = (isset($_SERVER['HTTP_CF_CONNECTING_IP'])) ? $_SERVER['HTTP_CF_CONNECTING_IP'] : false;
978
    $client = (isset($_SERVER['HTTP_CLIENT_IP'])) ? $_SERVER['HTTP_CLIENT_IP'] : false;
979
    $fwd = (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) ? $_SERVER['HTTP_X_FORWARDED_FOR'] : false;
980
    $remote = (isset($_SERVER['REMOTE_ADDR'])) ? $_SERVER['REMOTE_ADDR'] : @$_SERVER['REMOTE_ADDR'];
981
    // shared internet
982
    if (!$trustremote && $checkcloudflare && filter_var($cf, FILTER_VALIDATE_IP)) {
983
      // cloudflare
984
      return $cf;
985
    } else if (!$trustremote && $checkclient && filter_var($client, FILTER_VALIDATE_IP)) {
986
      return $client;
987
    } else if (!$trustremote && $checkforwarded && strpos($fwd, ',') !== false) {
988
      // multiple proxies
989
      $ips = explode(',', $fwd);
990
      return $ips[0];
991
    } else if (!$trustremote && $checkforwarded && filter_var($fwd, FILTER_VALIDATE_IP)) {
992
      // single
993
      return $fwd;
994
    } else {
995
      // as usual
996
      return $remote;
997
    }
998
  }
999
}
1000
1001
// Make our class available automatically
1002
$user = new User($config);
1003
$user->setDebug($debug);
1004
$user->setLog($log);
1005
$user->setMysql($mysqli);
1006
$user->setSalt($config['SALT']);
1007
$user->setSmarty($smarty);
1008
$user->setMail($mail);
1009
$user->setToken($oToken);
1010
$user->setBitcoin($bitcoin);
1011
$user->setSetting($setting);
1012
$user->setCoinAddress($coin_address);
1013
$user->setErrorCodes($aErrorCodes);
1014