Completed
Push — rbac ( 1ec5d5...5c33ef )
by Simon
04:39
created

User::getOAuthRequestToken()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 2
c 0
b 0
f 0
nc 1
nop 0
dl 0
loc 4
rs 10
ccs 0
cts 2
cp 0
crap 2
1
<?php
2
/******************************************************************************
3
 * Wikipedia Account Creation Assistance tool                                 *
4
 *                                                                            *
5
 * All code in this file is released into the public domain by the ACC        *
6
 * Development Team. Please see team.json for a list of contributors.         *
7
 ******************************************************************************/
8
9
namespace Waca\DataObjects;
10
11
use DateTime;
12
use Exception;
13
use JWT;
14
use PDO;
15
use UnexpectedValueException;
16
use Waca\AuthUtility;
17
use Waca\DataObject;
18
use Waca\Exceptions\OptimisticLockFailedException;
19
use Waca\Helpers\Interfaces\IOAuthHelper;
20
use Waca\IdentificationVerifier;
21
use Waca\PdoDatabase;
22
use Waca\SessionAlert;
23
use Waca\WebRequest;
24
25
/**
26
 * User data object
27
 */
28
class User extends DataObject
29
{
30
    const STATUS_ACTIVE = 'Active';
31
    const STATUS_SUSPENDED = 'Suspended';
32
    const STATUS_DECLINED = 'Declined';
33
    const STATUS_NEW = 'New';
34
    private $username;
35
    private $email;
36
    private $password;
37
    private $status = self::STATUS_NEW;
38
    private $onwikiname = "##OAUTH##";
39
    private $welcome_sig = "";
40
    private $lastactive = "0000-00-00 00:00:00";
41
    private $forcelogout = 0;
42
    private $forceidentified = null;
43
    private $welcome_template = 0;
44
    private $abortpref = 0;
45
    private $confirmationdiff = 0;
46
    private $emailsig = "";
47
    /** @var null|string */
48
    private $oauthrequesttoken = null;
49
    /** @var null|string */
50
    private $oauthrequestsecret = null;
51
    /** @var null|string */
52
    private $oauthaccesstoken = null;
53
    /** @var null|string */
54
    private $oauthaccesssecret = null;
55
    private $oauthidentitycache = null;
56
    /** @var User Cache variable of the current user - it's never going to change in the middle of a request. */
57
    private static $currentUser;
58
    /** @var null|JWT The identity cache */
59
    private $identityCache = null;
60
    #region Object load methods
61
62
    /**
63
     * Gets the currently logged in user
64
     *
65
     * @param PdoDatabase $database
66
     *
67
     * @return User|CommunityUser
68
     */
69
    public static function getCurrent(PdoDatabase $database)
70
    {
71
        if (self::$currentUser === null) {
72
            $sessionId = WebRequest::getSessionUserId();
73
74
            if ($sessionId !== null) {
75
                /** @var User $user */
76
                $user = self::getById($sessionId, $database);
77
78
                if ($user === false) {
79
                    self::$currentUser = new CommunityUser();
80
                }
81
                else {
82
                    self::$currentUser = $user;
83
                }
84
            }
85
            else {
86
                $anonymousCoward = new CommunityUser();
87
88
                self::$currentUser = $anonymousCoward;
89
            }
90
        }
91
92
        return self::$currentUser;
93
    }
94
95
    /**
96
     * Gets a user by their user ID
97
     *
98
     * Pass -1 to get the community user.
99
     *
100
     * @param int|null    $id
101
     * @param PdoDatabase $database
102
     *
103
     * @return User|false
104
     */
105
    public static function getById($id, PdoDatabase $database)
106
    {
107
        if ($id === null || $id == -1) {
108
            return new CommunityUser();
109
        }
110
111
        /** @var User|false $user */
112
        $user = parent::getById($id, $database);
113
114
        return $user;
115
    }
116
117
    /**
118
     * @return CommunityUser
119
     */
120 2
    public static function getCommunity()
121
    {
122 2
        return new CommunityUser();
123
    }
124
125
    /**
126
     * Gets a user by their username
127
     *
128
     * @param  string      $username
129
     * @param  PdoDatabase $database
130
     *
131
     * @return CommunityUser|User|false
132
     */
133
    public static function getByUsername($username, PdoDatabase $database)
134
    {
135
        global $communityUsername;
136
        if ($username == $communityUsername) {
137
            return new CommunityUser();
138
        }
139
140
        $statement = $database->prepare("SELECT * FROM user WHERE username = :id LIMIT 1;");
141
        $statement->bindValue(":id", $username);
142
143
        $statement->execute();
144
145
        $resultObject = $statement->fetchObject(get_called_class());
146
147
        if ($resultObject != false) {
148
            $resultObject->setDatabase($database);
149
        }
150
151
        return $resultObject;
152
    }
153
154
    /**
155
     * Gets a user by their on-wiki username.
156
     *
157
     * Don't use without asking me first. It's really inefficient in it's current implementation.
158
     * We need to restructure the user table again to make this more efficient.
159
     * We don't actually store the on-wiki name in the table any more, instead we
160
     * are storing JSON in a column (!!). Yep, my fault. Code review is an awesome thing.
161
     *            -- stw 2015-10-20
162
     *
163
     * @param string      $username
164
     * @param PdoDatabase $database
165
     *
166
     * @return User|false
167
     */
168
    public static function getByOnWikiUsername($username, PdoDatabase $database)
169
    {
170
        // Firstly, try to search by the efficient database lookup.
171
        $statement = $database->prepare("SELECT * FROM user WHERE onwikiname = :id LIMIT 1;");
172
        $statement->bindValue(":id", $username);
173
        $statement->execute();
174
175
        $resultObject = $statement->fetchObject(get_called_class());
176
177
        if ($resultObject != false) {
178
            $resultObject->setDatabase($database);
179
180
            return $resultObject;
181
        }
182
183
        // For active users, the above has failed. Let's do it the hard way.
184
        $sqlStatement = "SELECT * FROM user WHERE onwikiname = '##OAUTH##' AND oauthaccesstoken IS NOT NULL;";
185
        $statement = $database->prepare($sqlStatement);
186
        $statement->execute();
187
        $resultSet = $statement->fetchAll(PDO::FETCH_CLASS, get_called_class());
188
189
        /** @var User $user */
190
        foreach ($resultSet as $user) {
191
            // We have to set this before doing OAuth queries. :(
192
            $user->setDatabase($database);
193
194
            // Using cached data here!
195
            if ($user->getOAuthOnWikiName(true) == $username) {
196
                // Success.
197
                return $user;
198
            }
199
        }
200
201
        // Cached data failed. Let's do it the *REALLY* hard way.
202
        foreach ($resultSet as $user) {
203
            // We have to set this before doing OAuth queries. :(
204
            $user->setDatabase($database);
205
206
            // Don't use the cached data, but instead query the API.
207
            if ($user->getOAuthOnWikiName(false) == $username) {
208
                // Success.
209
                return $user;
210
            }
211
        }
212
213
        // Nope. Sorry.
214
        return false;
215
    }
216
217
    /**
218
     * Gets a user by their OAuth request token
219
     *
220
     * @param string      $requestToken
221
     * @param PdoDatabase $database
222
     *
223
     * @return User|false
224
     */
225
    public static function getByRequestToken($requestToken, PdoDatabase $database)
226
    {
227
        $statement = $database->prepare("SELECT * FROM user WHERE oauthrequesttoken = :id LIMIT 1;");
228
        $statement->bindValue(":id", $requestToken);
229
230
        $statement->execute();
231
232
        $resultObject = $statement->fetchObject(get_called_class());
233
234
        if ($resultObject != false) {
235
            $resultObject->setDatabase($database);
236
        }
237
238
        return $resultObject;
239
    }
240
241
    #endregion
242
243
    /**
244
     * Saves the current object
245
     *
246
     * @throws Exception
247
     */
248
    public function save()
249
    {
250
        if ($this->isNew()) {
251
            // insert
252
            $statement = $this->dbObject->prepare(<<<SQL
253
				INSERT INTO `user` ( 
254
					username, email, password, status, onwikiname, welcome_sig, 
255
					lastactive, forcelogout, forceidentified,
256
					welcome_template, abortpref, confirmationdiff, emailsig, 
257
					oauthrequesttoken, oauthrequestsecret, 
258
					oauthaccesstoken, oauthaccesssecret
259
				) VALUES (
260
					:username, :email, :password, :status, :onwikiname, :welcome_sig,
261
					:lastactive, :forcelogout, :forceidentified,
262
					:welcome_template, :abortpref, :confirmationdiff, :emailsig, 
263
					:ort, :ors, :oat, :oas
264
				);
265
SQL
266
            );
267
            $statement->bindValue(":username", $this->username);
268
            $statement->bindValue(":email", $this->email);
269
            $statement->bindValue(":password", $this->password);
270
            $statement->bindValue(":status", $this->status);
271
            $statement->bindValue(":onwikiname", $this->onwikiname);
272
            $statement->bindValue(":welcome_sig", $this->welcome_sig);
273
            $statement->bindValue(":lastactive", $this->lastactive);
274
            $statement->bindValue(":forcelogout", $this->forcelogout);
275
            $statement->bindValue(":forceidentified", $this->forceidentified);
276
            $statement->bindValue(":welcome_template", $this->welcome_template);
277
            $statement->bindValue(":abortpref", $this->abortpref);
278
            $statement->bindValue(":confirmationdiff", $this->confirmationdiff);
279
            $statement->bindValue(":emailsig", $this->emailsig);
280
            $statement->bindValue(":ort", $this->oauthrequesttoken);
281
            $statement->bindValue(":ors", $this->oauthrequestsecret);
282
            $statement->bindValue(":oat", $this->oauthaccesstoken);
283
            $statement->bindValue(":oas", $this->oauthaccesssecret);
284
285
            if ($statement->execute()) {
286
                $this->id = (int)$this->dbObject->lastInsertId();
287
            }
288
            else {
289
                throw new Exception($statement->errorInfo());
290
            }
291
        }
292
        else {
293
            // update
294
            $statement = $this->dbObject->prepare(<<<SQL
295
				UPDATE `user` SET 
296
					username = :username, email = :email, 
297
					password = :password, status = :status,
298
					onwikiname = :onwikiname, welcome_sig = :welcome_sig, 
299
					lastactive = :lastactive, forcelogout = :forcelogout, 
300
					forceidentified = :forceidentified,
301
					welcome_template = :welcome_template, abortpref = :abortpref, 
302
					confirmationdiff = :confirmationdiff, emailsig = :emailsig, 
303
					oauthrequesttoken = :ort, oauthrequestsecret = :ors, 
304
					oauthaccesstoken = :oat, oauthaccesssecret = :oas,
305
					updateversion = updateversion + 1
306
				WHERE id = :id AND updateversion = :updateversion
307
				LIMIT 1;
308
SQL
309
            );
310
            $statement->bindValue(":forceidentified", $this->forceidentified);
311
312
            $statement->bindValue(':id', $this->id);
313
            $statement->bindValue(':updateversion', $this->updateversion);
314
315
            $statement->bindValue(':username', $this->username);
316
            $statement->bindValue(':email', $this->email);
317
            $statement->bindValue(':password', $this->password);
318
            $statement->bindValue(':status', $this->status);
319
            $statement->bindValue(':onwikiname', $this->onwikiname);
320
            $statement->bindValue(':welcome_sig', $this->welcome_sig);
321
            $statement->bindValue(':lastactive', $this->lastactive);
322
            $statement->bindValue(':forcelogout', $this->forcelogout);
323
            $statement->bindValue(':forceidentified', $this->forceidentified);
324
            $statement->bindValue(':welcome_template', $this->welcome_template);
325
            $statement->bindValue(':abortpref', $this->abortpref);
326
            $statement->bindValue(':confirmationdiff', $this->confirmationdiff);
327
            $statement->bindValue(':emailsig', $this->emailsig);
328
            $statement->bindValue(':ort', $this->oauthrequesttoken);
329
            $statement->bindValue(':ors', $this->oauthrequestsecret);
330
            $statement->bindValue(':oat', $this->oauthaccesstoken);
331
            $statement->bindValue(':oas', $this->oauthaccesssecret);
332
333
            if (!$statement->execute()) {
334
                throw new Exception($statement->errorInfo());
335
            }
336
337
            if ($statement->rowCount() !== 1) {
338
                throw new OptimisticLockFailedException();
339
            }
340
341
            $this->updateversion++;
342
        }
343
    }
344
345
    /**
346
     * Authenticates the user with the supplied password
347
     *
348
     * @param string $password
349
     *
350
     * @return bool
351
     * @throws Exception
352
     * @category Security-Critical
353
     */
354
    public function authenticate($password)
355
    {
356
        $result = AuthUtility::testCredentials($password, $this->password);
357
358
        if ($result === true) {
359
            // password version is out of date, update it.
360
            if (!AuthUtility::isCredentialVersionLatest($this->password)) {
361
                $this->password = AuthUtility::encryptPassword($password);
362
                $this->save();
363
            }
364
        }
365
366
        return $result;
367
    }
368
369
    #region properties
370
371
    /**
372
     * Gets the tool username
373
     * @return string
374
     */
375
    public function getUsername()
376
    {
377
        return $this->username;
378
    }
379
380
    /**
381
     * Sets the tool username
382
     *
383
     * @param string $username
384
     */
385
    public function setUsername($username)
386
    {
387
        $this->username = $username;
388
389
        // If this isn't a brand new user, then it's a rename, force the logout
390
        if (!$this->isNew()) {
391
            $this->forcelogout = 1;
392
        }
393
    }
394
395
    /**
396
     * Gets the user's email address
397
     * @return string
398
     */
399
    public function getEmail()
400
    {
401
        return $this->email;
402
    }
403
404
    /**
405
     * Sets the user's email address
406
     *
407
     * @param string $email
408
     */
409
    public function setEmail($email)
410
    {
411
        $this->email = $email;
412
    }
413
414
    /**
415
     * Sets the user's password
416
     *
417
     * @param string $password the plaintext password
418
     *
419
     * @category Security-Critical
420
     */
421
    public function setPassword($password)
422
    {
423
        $this->password = AuthUtility::encryptPassword($password);
424
    }
425
426
    /**
427
     * Gets the status (User, Admin, Suspended, etc - excludes checkuser) of the user.
428
     * @return string
429
     */
430
    public function getStatus()
431
    {
432
        return $this->status;
433
    }
434
435
    /**
436
     * @param string $status
437
     */
438
    public function setStatus($status)
439
    {
440
        $this->status = $status;
441
    }
442
443
    /**
444
     * Gets the user's on-wiki name
445
     * @return string
446
     */
447
    public function getOnWikiName()
448
    {
449
        if ($this->oauthaccesstoken !== null) {
450
            try {
451
                return $this->getOAuthOnWikiName();
452
            }
453
            catch (Exception $ex) {
454
                // urm.. log this?
455
                return $this->onwikiname;
456
            }
457
        }
458
459
        return $this->onwikiname;
460
    }
461
462
    /**
463
     * This is probably NOT the function you want!
464
     *
465
     * Take a look at getOnWikiName() instead.
466
     * @return string
467
     */
468
    public function getStoredOnWikiName()
469
    {
470
        return $this->onwikiname;
471
    }
472
473
    /**
474
     * Sets the user's on-wiki name
475
     *
476
     * This can have interesting side-effects with OAuth.
477
     *
478
     * @param string $onWikiName
479
     */
480
    public function setOnWikiName($onWikiName)
481
    {
482
        $this->onwikiname = $onWikiName;
483
    }
484
485
    /**
486
     * Gets the welcome signature
487
     * @return string
488
     */
489
    public function getWelcomeSig()
490
    {
491
        return $this->welcome_sig;
492
    }
493
494
    /**
495
     * Sets the welcome signature
496
     *
497
     * @param string $welcomeSig
498
     */
499
    public function setWelcomeSig($welcomeSig)
500
    {
501
        $this->welcome_sig = $welcomeSig;
502
    }
503
504
    /**
505
     * Gets the last activity date for the user
506
     *
507
     * @return string
508
     * @todo This should probably return an instance of DateTime
0 ignored issues
show
Coding Style introduced by
Comment refers to a TODO task

This check looks TODO comments that have been left in the code.

``TODO``s show that something is left unfinished and should be attended to.

Loading history...
509
     */
510
    public function getLastActive()
511
    {
512
        return $this->lastactive;
513
    }
514
515
    /**
516
     * Gets the user's forced logout status
517
     *
518
     * @return bool
519
     */
520
    public function getForceLogout()
521
    {
522
        return $this->forcelogout == 1;
523
    }
524
525
    /**
526
     * Sets the user's forced logout status
527
     *
528
     * @param bool $forceLogout
529
     */
530
    public function setForceLogout($forceLogout)
531
    {
532
        $this->forcelogout = $forceLogout ? 1 : 0;
533
    }
534
535
    /**
536
     * Returns the ID of the welcome template used.
537
     * @return int
538
     */
539
    public function getWelcomeTemplate()
540
    {
541
        return $this->welcome_template;
542
    }
543
544
    /**
545
     * Sets the ID of the welcome template used.
546
     *
547
     * @param int $welcomeTemplate
548
     */
549
    public function setWelcomeTemplate($welcomeTemplate)
550
    {
551
        $this->welcome_template = $welcomeTemplate;
552
    }
553
554
    /**
555
     * Gets the user's abort preference
556
     * @todo this is badly named too! Also a bool that's actually an int.
0 ignored issues
show
Coding Style introduced by
Comment refers to a TODO task

This check looks TODO comments that have been left in the code.

``TODO``s show that something is left unfinished and should be attended to.

Loading history...
557
     * @return int
558
     */
559
    public function getAbortPref()
560
    {
561
        return $this->abortpref;
562
    }
563
564
    /**
565
     * Sets the user's abort preference
566
     * @todo rename, retype, and re-comment.
0 ignored issues
show
Coding Style introduced by
Comment refers to a TODO task

This check looks TODO comments that have been left in the code.

``TODO``s show that something is left unfinished and should be attended to.

Loading history...
567
     *
568
     * @param int $abortPreference
569
     */
570
    public function setAbortPref($abortPreference)
571
    {
572
        $this->abortpref = $abortPreference;
573
    }
574
575
    /**
576
     * Gets the user's confirmation diff. Unused if OAuth is in use.
577
     * @return int the diff ID
578
     */
579
    public function getConfirmationDiff()
580
    {
581
        return $this->confirmationdiff;
582
    }
583
584
    /**
585
     * Sets the user's confirmation diff.
586
     *
587
     * @param int $confirmationDiff
588
     */
589
    public function setConfirmationDiff($confirmationDiff)
590
    {
591
        $this->confirmationdiff = $confirmationDiff;
592
    }
593
594
    /**
595
     * Gets the users' email signature used on outbound mail.
596
     * @todo rename me!
0 ignored issues
show
Coding Style introduced by
Comment refers to a TODO task

This check looks TODO comments that have been left in the code.

``TODO``s show that something is left unfinished and should be attended to.

Loading history...
597
     * @return string
598
     */
599
    public function getEmailSig()
600
    {
601
        return $this->emailsig;
602
    }
603
604
    /**
605
     * Sets the user's email signature for outbound mail.
606
     *
607
     * @param string $emailSignature
608
     */
609
    public function setEmailSig($emailSignature)
610
    {
611
        $this->emailsig = $emailSignature;
612
    }
613
614
    /**
615
     * Gets the user's OAuth request token.
616
     *
617
     * @todo move me to a collaborator.
0 ignored issues
show
Coding Style introduced by
Comment refers to a TODO task

This check looks TODO comments that have been left in the code.

``TODO``s show that something is left unfinished and should be attended to.

Loading history...
618
     * @return null|string
619
     */
620
    public function getOAuthRequestToken()
621
    {
622
        return $this->oauthrequesttoken;
623
    }
624
625
    /**
626
     * Sets the user's OAuth request token
627
     * @todo move me to a collaborator
0 ignored issues
show
Coding Style introduced by
Comment refers to a TODO task

This check looks TODO comments that have been left in the code.

``TODO``s show that something is left unfinished and should be attended to.

Loading history...
628
     *
629
     * @param string $oAuthRequestToken
630
     */
631
    public function setOAuthRequestToken($oAuthRequestToken)
632
    {
633
        $this->oauthrequesttoken = $oAuthRequestToken;
634
    }
635
636
    /**
637
     * Gets the users OAuth request secret
638
     * @category Security-Critical
639
     * @todo     move me to a collaborator
0 ignored issues
show
Coding Style introduced by
Comment refers to a TODO task

This check looks TODO comments that have been left in the code.

``TODO``s show that something is left unfinished and should be attended to.

Loading history...
640
     * @return null|string
641
     */
642
    public function getOAuthRequestSecret()
643
    {
644
        return $this->oauthrequestsecret;
645
    }
646
647
    /**
648
     * Sets the user's OAuth request secret
649
     * @todo move me to a collaborator
0 ignored issues
show
Coding Style introduced by
Comment refers to a TODO task

This check looks TODO comments that have been left in the code.

``TODO``s show that something is left unfinished and should be attended to.

Loading history...
650
     *
651
     * @param string $oAuthRequestSecret
652
     */
653
    public function setOAuthRequestSecret($oAuthRequestSecret)
654
    {
655
        $this->oauthrequestsecret = $oAuthRequestSecret;
656
    }
657
658
    /**
659
     * Gets the user's access token
660
     * @category Security-Critical
661
     * @todo     move me to a collaborator
0 ignored issues
show
Coding Style introduced by
Comment refers to a TODO task

This check looks TODO comments that have been left in the code.

``TODO``s show that something is left unfinished and should be attended to.

Loading history...
662
     * @return null|string
663
     */
664
    public function getOAuthAccessToken()
665
    {
666
        return $this->oauthaccesstoken;
667
    }
668
669
    /**
670
     * Sets the user's access token
671
     * @todo move me to a collaborator
0 ignored issues
show
Coding Style introduced by
Comment refers to a TODO task

This check looks TODO comments that have been left in the code.

``TODO``s show that something is left unfinished and should be attended to.

Loading history...
672
     *
673
     * @param string $oAuthAccessToken
674
     */
675
    public function setOAuthAccessToken($oAuthAccessToken)
676
    {
677
        $this->oauthaccesstoken = $oAuthAccessToken;
678
    }
679
680
    /**
681
     * Gets the user's OAuth access secret
682
     * @category Security-Critical
683
     * @todo     move me to a collaborator
0 ignored issues
show
Coding Style introduced by
Comment refers to a TODO task

This check looks TODO comments that have been left in the code.

``TODO``s show that something is left unfinished and should be attended to.

Loading history...
684
     * @return null|string
685
     */
686
    public function getOAuthAccessSecret()
687
    {
688
        return $this->oauthaccesssecret;
689
    }
690
691
    /**
692
     * Sets the user's OAuth access secret
693
     * @todo move me to a collaborator
0 ignored issues
show
Coding Style introduced by
Comment refers to a TODO task

This check looks TODO comments that have been left in the code.

``TODO``s show that something is left unfinished and should be attended to.

Loading history...
694
     *
695
     * @param string $oAuthAccessSecret
696
     */
697
    public function setOAuthAccessSecret($oAuthAccessSecret)
698
    {
699
        $this->oauthaccesssecret = $oAuthAccessSecret;
700
    }
701
702
    #endregion
703
704
    #region user access checks
705
706
    public function isActive()
707
    {
708
        return $this->status == self::STATUS_ACTIVE;
709
    }
710
711
    /**
712
     * Tests if the user is identified
713
     *
714
     * @param IdentificationVerifier $iv
715
     *
716
     * @return bool
717
     * @todo     Figure out what on earth is going on with PDO's typecasting here.  Apparently, it returns string("0") for
0 ignored issues
show
Coding Style introduced by
Comment refers to a TODO task

This check looks TODO comments that have been left in the code.

``TODO``s show that something is left unfinished and should be attended to.

Loading history...
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 122 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
718
     *       the force-unidentified case, and int(1) for the identified case?!  This is quite ugly, but probably needed
719
     *       to play it safe for now.
720
     * @category Security-Critical
721
     */
722
    public function isIdentified(IdentificationVerifier $iv)
723
    {
724
        if ($this->forceidentified === 0 || $this->forceidentified === "0") {
725
            // User forced to unidentified in the database.
726
            return false;
727
        }
728
        elseif ($this->forceidentified === 1 || $this->forceidentified === "1") {
729
            // User forced to identified in the database.
730
            return true;
731
        }
732
        else {
733
            // User not forced to any particular identified status; consult IdentificationVerifier
734
            return $iv->isUserIdentified($this->getOnWikiName());
735
        }
736
    }
737
738
    /**
739
     * Tests if the user is suspended
740
     * @return bool
741
     * @category Security-Critical
742
     */
743
    public function isSuspended()
744
    {
745
        return $this->status == self::STATUS_SUSPENDED;
746
    }
747
748
    /**
749
     * Tests if the user is new
750
     * @return bool
751
     * @category Security-Critical
752
     */
753
    public function isNewUser()
754
    {
755
        return $this->status == self::STATUS_NEW;
756
    }
757
758
    /**
759
     * Tests if the user has been declined access to the tool
760
     * @return bool
761
     * @category Security-Critical
762
     */
763
    public function isDeclined()
764
    {
765
        return $this->status == self::STATUS_DECLINED;
766
    }
767
768
    /**
769
     * Tests if the user is the community user
770
     *
771
     * @todo     decide if this means logged out. I think it usually does.
0 ignored issues
show
Coding Style introduced by
Comment refers to a TODO task

This check looks TODO comments that have been left in the code.

``TODO``s show that something is left unfinished and should be attended to.

Loading history...
772
     * @return bool
773
     * @category Security-Critical
774
     */
775
    public function isCommunityUser()
776
    {
777
        return false;
778
    }
779
780
    #endregion 
781
782
    #region OAuth
783
784
    /**
785
     * @todo     move me to a collaborator
0 ignored issues
show
Coding Style introduced by
Comment refers to a TODO task

This check looks TODO comments that have been left in the code.

``TODO``s show that something is left unfinished and should be attended to.

Loading history...
786
     *
787
     * @param bool $useCached
788
     *
789
     * @return mixed|null
790
     * @category Security-Critical
791
     */
792
    public function getOAuthIdentity($useCached = false)
793
    {
794
        if ($this->oauthaccesstoken === null) {
795
            $this->clearOAuthData();
796
        }
797
798
        global $oauthConsumerToken, $oauthMediaWikiCanonicalServer;
799
800
        if ($this->oauthidentitycache == null) {
801
            $this->identityCache = null;
802
        }
803
        else {
804
            $this->identityCache = unserialize($this->oauthidentitycache);
805
        }
806
807
        // check the cache
808
        if (
809
            $this->identityCache != null &&
810
            $this->identityCache->aud == $oauthConsumerToken &&
811
            $this->identityCache->iss == $oauthMediaWikiCanonicalServer
812
        ) {
813
            if (
814
                $useCached || (
815
                    DateTime::createFromFormat("U", $this->identityCache->iat) < new DateTime() &&
816
                    DateTime::createFromFormat("U", $this->identityCache->exp) > new DateTime()
817
                )
818
            ) {
819
                // Use cached value - it's either valid or we don't care.
820
                return $this->identityCache;
821
            }
822
            else {
823
                // Cache expired and not forcing use of cached value
824
                $this->getIdentityCache();
825
826
                return $this->identityCache;
827
            }
828
        }
829
        else {
830
            // Cache isn't ours or doesn't exist
831
            $this->getIdentityCache();
832
833
            return $this->identityCache;
834
        }
835
    }
836
837
    /**
838
     * @todo     move me to a collaborator
0 ignored issues
show
Coding Style introduced by
Comment refers to a TODO task

This check looks TODO comments that have been left in the code.

``TODO``s show that something is left unfinished and should be attended to.

Loading history...
839
     *
840
     * @param mixed $useCached Set to false for everything where up-to-date data is important.
841
     *
842
     * @return mixed
843
     * @category Security-Critical
844
     */
845
    private function getOAuthOnWikiName($useCached = false)
846
    {
847
        $identity = $this->getOAuthIdentity($useCached);
848
        if ($identity !== null) {
849
            return $identity->username;
850
        }
851
852
        return false;
853
    }
854
855
    /**
856
     * @return bool
857
     * @todo move me to a collaborator
0 ignored issues
show
Coding Style introduced by
Comment refers to a TODO task

This check looks TODO comments that have been left in the code.

``TODO``s show that something is left unfinished and should be attended to.

Loading history...
858
     */
859
    public function isOAuthLinked()
860
    {
861
        if ($this->onwikiname === "##OAUTH##") {
862
            return true; // special value. If an account must be oauth linked, this is true.
863
        }
864
865
        return $this->oauthaccesstoken !== null;
866
    }
867
868
    /**
869
     * @return null
870
     * @todo move me to a collaborator
0 ignored issues
show
Coding Style introduced by
Comment refers to a TODO task

This check looks TODO comments that have been left in the code.

``TODO``s show that something is left unfinished and should be attended to.

Loading history...
871
     */
872
    public function clearOAuthData()
873
    {
874
        $this->identityCache = null;
875
        $this->oauthidentitycache = null;
876
        $clearCacheQuery = "UPDATE user SET oauthidentitycache = NULL WHERE id = :id;";
877
        $this->dbObject->prepare($clearCacheQuery)->execute(array(":id" => $this->id));
878
879
        return null;
880
    }
881
882
    /**
883
     * @throws Exception
884
     * @todo     move me to a collaborator
0 ignored issues
show
Coding Style introduced by
Comment refers to a TODO task

This check looks TODO comments that have been left in the code.

``TODO``s show that something is left unfinished and should be attended to.

Loading history...
885
     * @category Security-Critical
886
     */
887
    private function getIdentityCache()
888
    {
889
        /** @var IOAuthHelper $oauthHelper */
890
        global $oauthHelper;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
891
892
        try {
893
            $this->identityCache = $oauthHelper->getIdentityTicket($this->oauthaccesstoken, $this->oauthaccesssecret);
894
895
            $this->oauthidentitycache = serialize($this->identityCache);
896
            $this->dbObject->prepare("UPDATE user SET oauthidentitycache = :identity WHERE id = :id;")
897
                ->execute(array(":id" => $this->id, ":identity" => $this->oauthidentitycache));
898
        }
899
        catch (UnexpectedValueException $ex) {
900
            $this->identityCache = null;
901
            $this->oauthidentitycache = null;
902
            $this->dbObject->prepare("UPDATE user SET oauthidentitycache = NULL WHERE id = :id;")
903
                ->execute(array(":id" => $this->id));
904
905
            SessionAlert::warning("OAuth error getting identity from MediaWiki: " . $ex->getMessage());
906
        }
907
    }
908
909
    /**
910
     * @return bool
911
     * @todo move me to a collaborator
0 ignored issues
show
Coding Style introduced by
Comment refers to a TODO task

This check looks TODO comments that have been left in the code.

``TODO``s show that something is left unfinished and should be attended to.

Loading history...
912
     */
913
    public function oauthCanUse()
914
    {
915
        try {
916
            return in_array('useoauth', $this->getOAuthIdentity()->grants);
917
        }
918
        catch (Exception $ex) {
919
            return false;
920
        }
921
    }
922
923
    /**
924
     * @return bool
925
     * @todo move me to a collaborator
0 ignored issues
show
Coding Style introduced by
Comment refers to a TODO task

This check looks TODO comments that have been left in the code.

``TODO``s show that something is left unfinished and should be attended to.

Loading history...
926
     */
927 View Code Duplication
    public function oauthCanEdit()
928
    {
929
        try {
930
            return in_array('useoauth', $this->getOAuthIdentity()->grants)
931
            && in_array('createeditmovepage', $this->getOAuthIdentity()->grants)
932
            && in_array('createtalk', $this->getOAuthIdentity()->rights)
933
            && in_array('edit', $this->getOAuthIdentity()->rights)
934
            && in_array('writeapi', $this->getOAuthIdentity()->rights);
935
        }
936
        catch (Exception $ex) {
937
            return false;
938
        }
939
    }
940
941
    /**
942
     * @return bool
943
     * @todo move me to a collaborator
0 ignored issues
show
Coding Style introduced by
Comment refers to a TODO task

This check looks TODO comments that have been left in the code.

``TODO``s show that something is left unfinished and should be attended to.

Loading history...
944
     */
945 View Code Duplication
    public function oauthCanCreateAccount()
946
    {
947
        try {
948
            return in_array('useoauth', $this->getOAuthIdentity()->grants)
949
            && in_array('createaccount', $this->getOAuthIdentity()->grants)
950
            && in_array('createaccount', $this->getOAuthIdentity()->rights)
951
            && in_array('writeapi', $this->getOAuthIdentity()->rights);
952
        }
953
        catch (Exception $ex) {
954
            return false;
955
        }
956
    }
957
958
    /**
959
     * @return bool
960
     * @todo     move me to a collaborator
0 ignored issues
show
Coding Style introduced by
Comment refers to a TODO task

This check looks TODO comments that have been left in the code.

``TODO``s show that something is left unfinished and should be attended to.

Loading history...
961
     * @category Security-Critical
962
     */
963
    protected function oauthCanCheckUser()
964
    {
965
        if (!$this->isOAuthLinked()) {
966
            return false;
967
        }
968
969
        try {
970
            $identity = $this->getOAuthIdentity();
971
972
            return in_array('checkuser', $identity->rights);
973
        }
974
        catch (Exception $ex) {
975
            return false;
976
        }
977
    }
978
979
    #endregion
980
981
    /**
982
     * Gets a hash of data for the user to reset their password with.
983
     * @category Security-Critical
984
     * @return string
985
     */
986
    public function getForgottenPasswordHash()
987
    {
988
        return md5($this->username . $this->email . $this->welcome_template . $this->id . $this->password);
989
    }
990
991
    /**
992
     * Gets the approval date of the user
993
     * @return DateTime|false
994
     */
995
    public function getApprovalDate()
996
    {
997
        $query = $this->dbObject->prepare(<<<SQL
998
			SELECT timestamp 
999
			FROM log 
1000
			WHERE objectid = :userid
1001
				AND objecttype = 'User'
1002
				AND action = 'Approved' 
1003
			ORDER BY id DESC 
1004
			LIMIT 1;
1005
SQL
1006
        );
1007
        $query->execute(array(":userid" => $this->id));
1008
1009
        $data = DateTime::createFromFormat("Y-m-d H:i:s", $query->fetchColumn());
1010
        $query->closeCursor();
1011
1012
        return $data;
1013
    }
1014
}
1015