GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.

Cookie::isCookieValid()   C
last analyzed

Complexity

Conditions 8
Paths 8

Size

Total Lines 43
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 43
rs 5.3846
c 0
b 0
f 0
cc 8
eloc 23
nc 8
nop 0
1
<?php
2
3
/**
4
 * Cookie Class
5
 *
6
 * @license    http://opensource.org/licenses/MIT The MIT License (MIT)
7
 * @author     Omar El Gabry <[email protected]>
8
 */
9
10
class Cookie{
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
11
12
    /**
13
     * @access public
14
     * @var string User ID
15
     */
16
    private static $userId = null;
17
18
    /**
19
     * @access public
20
     * @var string Cookie Token
21
     */
22
    private static $token = null;
23
24
    /**
25
     * @access public
26
     * @var string Hashed Token = hash(User ID . ":" . Token . Cookie Secret)
27
     */
28
    private static $hashedCookie = null;
29
30
    /**
31
     * This is the constructor for Cookie object.
32
     *
33
     * @access private
34
     */
35
    private function __construct() {}
36
37
    /**
38
     * Getters for $userId
39
     *
40
     * @access public
41
     * @static static method
42
     * @return string   User ID
43
     */
44
    public static function getUserId(){
45
        return (int)self::$userId;
46
    }
47
48
    /**
49
     * Extract and validate cookie
50
     *
51
     * @access public
52
     * @static static method
53
     * @return bool
54
     */
55
    public static function isCookieValid(){
0 ignored issues
show
Coding Style introduced by
isCookieValid uses the super-global variable $_COOKIE 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...
56
57
        // "auth" or "remember me" cookie
0 ignored issues
show
Unused Code Comprehensibility introduced by
38% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
58
        if(empty($_COOKIE['auth'])) {
59
            return false;
60
        }
61
62
        // check the count before using explode
63
        $cookie_auth = explode(':', $_COOKIE['auth']);
64
        if(count ($cookie_auth) !== 3){
65
            self::remove();
66
            return false;
67
        }
68
69
        list ($encryptedUserId, self::$token, self::$hashedCookie) = $cookie_auth;
70
71
        // Remember? $hashedCookie was generated from the original user Id, NOT from the encrypted one.
72
        self::$userId = Encryption::decrypt($encryptedUserId);
0 ignored issues
show
Documentation Bug introduced by
It seems like \Encryption::decrypt($encryptedUserId) can also be of type false. However, the property $userId is declared as type string. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

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

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
73
74
        if (self::$hashedCookie === hash('sha256', self::$userId . ':' . self::$token . Config::get('COOKIE_SECRET_KEY')) && !empty(self::$token) && !empty(self::$userId)) {
75
76
            $database = Database::openConnection();
77
            $query    = "SELECT id, cookie_token FROM users WHERE id = :id AND cookie_token = :cookie_token LIMIT 1";
78
            $database->prepare($query);
79
            $database->bindValue(':id', self::$userId);
80
            $database->bindValue(':cookie_token', self::$token);
81
            $database->execute();
82
83
            $isValid = $database->countRows() === 1? true: false;
84
85
        }else{
86
87
           $isValid = false;
88
        }
89
90
        if(!$isValid){
91
92
            Logger::log("COOKIE", self::$userId . " is trying to login using invalid cookie: " . self::$token, __FILE__, __LINE__);
93
            self::remove(self::$userId);
0 ignored issues
show
Security Bug introduced by
It seems like self::$userId can also be of type false; however, Cookie::remove() does only seem to accept string|null, did you maybe forget to handle an error condition?
Loading history...
94
        }
95
96
        return  $isValid;
97
    }
98
99
    /**
100
     * Remove cookie from the database of a user(if exists),
101
     * and also from the browser.
102
     *
103
     * @static static  method
104
     * @param  string  $userId
105
     *
106
     */
107
    public static function remove($userId = null){
108
109
        if(!empty($userId)){
110
111
            $database = Database::openConnection();
112
            $query    = "UPDATE users SET cookie_token = NULL WHERE id = :id";
113
            $database->prepare($query);
114
            $database->bindValue(":id", $userId);
115
            $result = $database->execute();
116
117
            if(!$result)  {
118
                Logger::log("COOKIE", "Couldn't remove cookie from the database for user ID: " . $userId, __FILE__, __LINE__);
119
            }
120
        }
121
122
        self::$userId = self::$token = self::$hashedCookie = null;
123
124
        // How to kill/delete a cookie in a browser?
125
        setcookie('auth', false, time() - (3600 * 3650), Config::get('COOKIE_PATH'), Config::get('COOKIE_DOMAIN'), Config::get('COOKIE_SECURE'), Config::get('COOKIE_HTTP'));
126
    }
127
128
    /**
129
     * Reset Cookie,
130
     * resetting is done by updating the database,
131
     * and resetting the "auth" cookie in the browser
132
     *
133
     * @static  static method
134
     * @param   string $userId
135
     */
136
    public static function reset($userId){
137
138
        self::$userId = $userId;
139
        self::$token = hash('sha256', mt_rand());
140
        $database = Database::openConnection();
141
142
        $query = "UPDATE users SET cookie_token = :cookie_token WHERE id = :id";
143
        $database->prepare($query);
144
145
        // generate random hash for cookie token (64 char string)
146
        $database->bindValue(":cookie_token", self::$token);
147
        $database->bindValue(":id", self::$userId);
148
        $result = $database->execute();
149
150
        if(!$result) {
151
            Logger::log("COOKIE", "Couldn't remove cookie from the database for user ID: " . $userId, __FILE__, __LINE__);
152
        }
153
154
        // generate cookie string(remember me)
155
        // Don't expose the original user id in the cookie, Encrypt It!
156
        $cookieFirstPart = Encryption::encrypt(self::$userId) . ':' . self::$token ;
157
158
        // $hashedCookie generated from the original user Id, NOT from the encrypted one.
159
        self::$hashedCookie = hash('sha256', self::$userId . ':' . self::$token  . Config::get('COOKIE_SECRET_KEY'));
160
        $authCookie = $cookieFirstPart . ':' . self::$hashedCookie;
161
162
        setcookie('auth', $authCookie, time() + Config::get('COOKIE_EXPIRY'), Config::get('COOKIE_PATH'), Config::get('COOKIE_DOMAIN'), Config::get('COOKIE_SECURE'), Config::get('COOKIE_HTTP'));
163
    }
164
165
}
166