Issues (164)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

src/Auth/Component.php (20 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
3
namespace BootPress\Auth;
4
5
use BootPress\Page\Component as Page;
6
use BootPress\SQLite\Component as SQLite;
7
use Symfony\Component\Yaml\Yaml;
8
9
/*
10
http://stackoverflow.com/questions/549/the-definitive-guide-to-form-based-website-authentication#477579
11
http://stackoverflow.com/questions/244882/what-is-the-best-way-to-implement-remember-me-for-a-website?lq=1
12
http://security.stackexchange.com/questions/63435/why-use-an-authentication-token-instead-of-the-username-password-per-request/63438#63438
13
    - Store token hash (a weak one is fine) in database, and original in cookie
14
https://paragonie.com/blog/2015/04/secure-authentication-php-with-long-term-persistence
15
    - Query database on a unique id (and not the token) for speed, and to thwart a timing attack
16
Charles Miller - http://fishbowl.pastiche.org/2004/01/19/persistent_login_cookie_best_practice/)
17
    - Change token with each new session to give attacker a smaller window of opportunity to exploit a stolen cookie
18
    - If an id (username) and token do not match, then remove all of the users persistent logins
19
    - Do not allow access to sensitive actions on a cookie-based login, but only through typing a valid password
20
Barry Jaspan - http://jaspan.com/improved_persistent_login_cookie_best_practice)
21
    - If attacker steals cookie, he becomes the legitimate user (with an updated token), whilst the victim must login again
22
    - Deleting all sessions for a user submitting an invalid token (only) makes it too easy for an attacker to log everyone out
23
    - If a series (constant) token is submitted with an invalid (variable) token, then a theft has occurred
24
    - If anything is missing from the cookie, then just ignore it
25
http://security.stackexchange.com/questions/19676/token-based-authentication-securing-the-token#19677
26
    - In the Barry Jaspan improved plan, an attacker can fly under the radar if they delete the original cookie
27
*/
28
29
class Component
30
{
31
    
32
    /** @var object  This property gives you access to the SQLite3 (or custom) users database. */
33
    public $db;
34
    
35
    /** @var object  BootPress\Page\Component instance. */
36
    private $page;
37
    
38
    /** @var null|string  The HTTP Authenticated username or null. */
39
    private $basic;
40
    
41
    /** @var array  Password hashing options. */
42
    private $password;
43
    
44
    /** $var array  Information we user to sanity check with a cookie.  Has '**id**', '**series**', '**token**', '**time**', and '**user_agent**' keys. */
45
    private $session = array();
46
    
47
    /** var array  Stored information about a logged in user.  Has '**id**', '**name**', '**email**', '**admin**', and '**login**' keys. */
48
    private $user = array();
49
50
    /**
51
     * Establishes the authentication settings, and runs all of our security checks.  Authentication is either session-based, or via HTTP Basic Auth.  Session-based authentication relies on the user database.  HTTP Basic Auth can use either the database, an array, or a YAML file of usernames and passwords.  A user can be both HTTP and session authenticated as long as Basic Auth is not using the database.  This allows an administrator to be logged in as a regular user, yet still retain super admin privileges.
52
     * 
53
     * @param array $options  Allows you to customize the authorization settings.  You can set the:
54
     * 
55
     * - '**db**' - A custom BootPress\Database\Component instance.  The default is an SQLite Users.db we will automatically create.
56
     * - '**basic**' - Either ``null`` (the default) to use the users database, an ``array('username'=>'password', ...)`` of users, or a YAML file of username's and password's.
57
     * - '**password**' - '**algo**' and '**options**' for hashing passwords.
58
     */
59 9
    public function __construct(array $options = array()) {
60 9
        extract(array_merge(array(
0 ignored issues
show
array_merge(array('db' =... => array()), $options) cannot be passed to extract() as the parameter $var_array expects a reference.
Loading history...
61 9
            'db' => null,
62 9
            'basic' => null,
63 9
            'password' => array(),
64 9
        ), $options));
65 9
        $this->password = array_merge(array(
66 9
            'algo' => \PASSWORD_DEFAULT,
67 9
            'options' => array(),
68 9
        ), (array) $password);
69 9
        $this->page = Page::html();
70 9
        $this->db = ($db instanceof \BootPress\Database\Component) ? $db : null;
71 9
        if (is_null($this->db)) {
72 9
            $this->db = new SQLite($this->page->dir['page'].'Users.db');
73 9
            if ($this->db->created) {
74 1
                $this->db->create('user_group_names', array(
75 1
                    'id' => 'INTEGER PRIMARY KEY',
76 1
                    'name' => 'TEXT UNIQUE COLLATE NOCASE',
77 1
                ));
78 1
                $this->db->create('user_groups', array(
79 1
                    'user_id' => 'INTEGER NOT NULL DEFAULT 0',
80 1
                    'group_id' => 'INTEGER NOT NULL DEFAULT 0',
81 1
                ), array('unique' => 'user_id, group_id'));
82 1
                $this->db->create('user_sessions', array(
83 1
                    'id' => 'INTEGER PRIMARY KEY',
84 1
                    'user_id' => 'INTEGER NOT NULL DEFAULT 0',
85 1
                    'adjourn' => 'INTEGER NOT NULL DEFAULT 0', // 'last_activity' + 'relapse'
86 1
                    'relapse' => 'INTEGER NOT NULL DEFAULT 0', // unsigned integer
87 1
                    'last_activity' => 'INTEGER NOT NULL DEFAULT 0', // time()
88 1
                    'ip_address' => 'TEXT NOT NULL DEFAULT ""', // of last updated session
89 1
                    'user_agent' => 'TEXT NOT NULL DEFAULT ""', // up to 255 varchar constant
90 1
                    'series' => 'TEXT NOT NULL DEFAULT ""', // sha1(salt) constant
91 1
                    'token' => 'TEXT NOT NULL DEFAULT ""', // sha1(salt) updated
92 1
                    'login' => 'TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP', // original date('Y-m-d H:i:s')
93 1
                ), 'user_id, adjourn');
0 ignored issues
show
'user_id, adjourn' is of type string, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
94 1
                $this->db->create('users', array(
95 1
                    'id' => 'INTEGER PRIMARY KEY',
96 1
                    'name' => 'TEXT NOT NULL DEFAULT ""',
97 1
                    'email' => 'TEXT UNIQUE COLLATE NOCASE',
98 1
                    'admin' => 'INTEGER NOT NULL DEFAULT 0', // unsigned integer
99 1
                    'password' => 'TEXT NOT NULL DEFAULT ""', // up to 255 varchar hash
100 1
                    'approved' => 'TEXT NOT NULL DEFAULT "Y"', // 'Y' or 'N' char
101 1
                    'registered' => 'TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP', // date('Y-m-d H:i:s')
102 1
                    'last_activity' => 'INTEGER NOT NULL DEFAULT 0', // time()
103 1
                ));
104 1
            }
105 9
        }
106 9
        $username = $this->page->request->getUser();
107 9
        $password = $this->page->request->getPassword();
108 9
        if (!empty($username) && !empty($password)) {
109 1
            if (is_null($basic)) { // use the user database (default)
110 1
                if ($user_id = $this->check($username, $password, 'approved = "Y"')) {
111 1
                    $this->user = $this->db->row('SELECT id, name, email, admin FROM users WHERE id = ?', $user_id, 'assoc');
112 1
                    $this->user['login'] = 0;
113 1
                }
114 1
            } elseif (is_array($basic)) { // an array of users
115 1
                if (isset($basic[$username]) && $password == $basic[$username]) {
116 1
                    $this->basic = $username;
117 1
                }
118 1
            } elseif (substr($basic, -4) == '.yml' && is_file($basic)) { // a YAML file of users
119 1
                $users = array();
120 1
                $current = (array) Yaml::parse(file_get_contents($basic));
121 1
                foreach ($current as $name => $pw) {
122 1
                    if (!is_numeric($name) && !empty($name) && !empty($pw)) {
123 1
                        $users[$name] = (substr($pw, 0, 4) == '$2y$') ? $pw : password_hash($pw, $this->password['algo'], $this->password['options']);
124 1
                    }
125 1
                }
126 1
                if (isset($users[$username]) && password_verify($password, $users[$username])) {
127 1
                    if (password_needs_rehash($users[$username], $this->password['algo'], $this->password['options'])) {
128 1
                        $users[$username] = password_hash($password, $this->password['algo'], $this->password['options']);
129 1
                    }
130 1
                    $this->basic = $username;
131 1
                }
132 1
                if ($users != $current) {
133 1
                    file_put_contents($basic, Yaml::dump($users));
134 1
                }
135 1
            }
136
            
137 1
            return; // HTTP Basic Authentication takes precedence
138
        }
139 8
        $user = false;
0 ignored issues
show
$user is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
140 8
        $session = $this->page->session->get('bootpress/cookie'); // vs. bootpress/verified
141 8
        if ($cookie = $this->page->request->cookies->get('bootpress')) {
142 5
            $cookie = base64_decode($cookie);
143 5
        }
144 8
        if ($session && $session != $cookie) { // These two must match if they both exist
145
            // We set the $session so we know that's good, but ...
146
            // if $session doesn't equal $cookie then it has been compromised
147
            // if $session and no $cookie, then it's assumed to have been stolen and deleted
148 3
            $this->logout($this->db->value('SELECT user_id FROM user_sessions WHERE id = ?', strstr($session.' ', ' ', true)));
149
            
150 3
            return $this->setCookie();
0 ignored issues
show
Constructors do not have meaningful return values, anything that is returned from here is discarded. Are you sure this is correct?
Loading history...
151
        }
152 6
        if ($cookie) { // We can have a $cookie and no $session (just log them in again)
153 3
            list($id, $series, $token) = explode(' ', $cookie.'   ');
154 3
            if (!$user = $this->db->row(array(
155 3
                'SELECT u.id, u.name, u.email, u.admin, strftime("%s", s.login) AS login, s.user_agent, s.last_activity, s.relapse, s.adjourn, s.token, u.approved',
156 3
                'FROM user_sessions AS s',
157 3
                'INNER JOIN users AS u ON s.user_id = u.id',
158 3
                'WHERE s.id = ? AND s.series = ?',
159 3
            ), array($id, sha1($series)), 'assoc')) {
160 1
                return $this->setCookie(); // ie. unset the cookie - it is defective, but not necessarily malicious
0 ignored issues
show
Constructors do not have meaningful return values, anything that is returned from here is discarded. Are you sure this is correct?
Loading history...
161
            }
162
            $session = array(
163 3
                'id' => $id,
164 3
                'series' => $series,
165 3
                'token' => $token,
166 3
                'time' => time(),
167 3
                'user_agent' => trim(substr($this->page->request->headers->get('User-Agent'), 0, 255)),
168 3
            );
169 3
            if ($user['user_agent'] != $session['user_agent'] ||
170 3
                $user['token'] != sha1($session['token']) ||
171 1
                $user['adjourn'] <= $session['time'] ||
172 3
                $user['approved'] != 'Y') {
173
                // Something we take seriously has been changed
174 3
                $this->logout($user['id']);
175
                
176 3
                return $this->setCookie();
0 ignored issues
show
Constructors do not have meaningful return values, anything that is returned from here is discarded. Are you sure this is correct?
Loading history...
177
            }
178
            // Update records every 5 minutes
179 1
            if (($user['last_activity'] + 300) <= $session['time'] && !$this->page->request->isXmlHttpRequest()) {
180 1
                $session['token'] = $this->salt();
181 1
                $this->db->update('user_sessions', 'id', array($session['id'] => array(
182 1
                    'ip_address' => $this->page->request->getClientIp(),
183 1
                    'last_activity' => $session['time'],
184 1
                    'adjourn' => $session['time'] + $user['relapse'],
185 1
                    'token' => sha1($session['token']),
186 1
                )));
187 1
                $this->db->update('users', 'id', array($user['id'] => array('last_activity' => $session['time'])));
188 1
                $this->setCookie(implode(' ', array($session['id'], $session['series'], $session['token'])), $user['relapse']);
189 1
            }
190 1
            $this->session = $session; // 'id', 'series', 'token', 'time', 'user_agent'
191 1
            $this->user = array_slice($user, 0, 5, true); // 'id', 'name', 'email', 'admin', 'login'
192 1
            $this->user['login'] = time() - $this->user['login'];
193 1
        }
194 4
    }
195
    
196
    /**
197
     * @return null|string  The basic username if HTTP authenticated, or null if not.
198
     */
199 1
    public function http()
200
    {
201 1
        return $this->basic;
202
    }
203
    
204
    /**
205
     * HTTP Authenticate a user for current directory and all subdirectories whether they are signed in or not.
206
     * 
207
     * @param string $name  Identifies the set of resources to which the username and password will apply.
208
     *
209
     * @see http://stackoverflow.com/questions/12701085/what-is-the-realm-in-basic-authentication
210
     *
211
     * ```php
212
     * if (!$auth->http()) {
213
     *     $auth->realm('Website');
214
     * }
215
     * ```
216
     */
217 1
    public function realm($name, $message = null)
218
    {
219 1
        $content = $message ?: '<h1>401 Unauthorized</h1><p>Access Denied</p>';
220 1
        $this->page->send($content, 401, array(
221 1
            'WWW-Authenticate' => 'Basic realm="'.htmlspecialchars($name).'"',
222 1
        ));
223 1
    }
224
225
    /**
226
     * Retrieve some information about the signed in user.
227
     * 
228
     * @param string $param  Can be any of the following:
229
     * - '**id**' - From the database's user table.
230
     * - '**name**' - Of the user.
231
     * - '**email**' - Of the user.
232
     * - '**admin**' level - The default is 0 meaning they have no admin privileges.
233
     * - '**login**' - The number of seconds ago that they actually signed in with a username and password for the current session.  If using HTTP Basic Authentication, this will always be 0.
234
     * 
235
     * @return array|string|null  An array if you don't set $param, a string if the user is logged in and the $param exists, or null if not.
236
     * 
237
     * ```php
238
     * echo 'Hello '.$auth->user('name');
239
     * ```
240
     */
241 7
    public function user($param = null)
242
    {
243 7
        if (is_null($param)) {
244 1
            return $this->user;
245
        }
246
247 7
        return (isset($this->user[$param])) ? $this->user[$param] : null;
248
    }
249
250
    /**
251
     * Gives you the following information about your user(s):
252
     *
253
     * - '**id**'
254
     * - '**name**'
255
     * - '**email**'
256
     * - '**admin**' - Integer.
257
     * - '**approved**' - Y (yes) or N (no).
258
     * - '**registered**' - A GMT timestamp.
259
     * - '**last_activity**' - A GMT timestamp (updated at 5 minute intervals) or 0 if we don't know.
260
     * - A '**groups**' ``array($group, ...)`` of groups they are in.
261
     * 
262
     * @param int|int[] $user_id  An integer, or an ``array($user_id, ...)``` of users you would like to return information for.
263
     * 
264
     * @return array  An associative array of information about your user(s).  If $user_id is an array, then this will be a multidimensional ``array($user_id => $info, ...)``` for every user in the order given.  If there was no record found for a given $user_id, then it will be an empty array.
265
     */
266 2
    public function info($user_id)
267
    {
268 2
        $single = (is_array($user_id)) ? false : true;
269 2
        $ids = ($single) ? array($user_id) : $user_id;
270 2
        $users = array();
271 2
        foreach ($ids as $id) {
0 ignored issues
show
The expression $ids of type array|integer is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
272 2
            $users[$id] = array();
273 2
        }
274 2
        $groups = $this->getUsersGroups($ids);
0 ignored issues
show
It seems like $ids defined by $single ? array($user_id) : $user_id on line 269 can also be of type array; however, BootPress\Auth\Component::getUsersGroups() does only seem to accept integer, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
275 2
        foreach ($this->db->all('SELECT * FROM users WHERE id IN('.implode(', ', $ids).')', '', 'assoc') as $row) {
276 2
            unset($row['password']);
277 2
            $users[$row['id']] = $row;
278 2
            $users[$row['id']]['registered'] = strtotime($row['registered']);
279 2
            $users[$row['id']]['groups'] = $groups[$row['id']];
280 2
        }
281
282 2
        return ($single) ? array_shift($users) : $users;
283
    }
284
285
    /**
286
     * This takes the submitted parameters and check to see if they exist in the users database.
287
     * 
288
     * @param string $email     This is the only required argument.  If you stop here then we are only checking to see if the email already exists.
289
     * @param string $password  The submitted password to check if the user is who they are claiming to be.  Encryption is handled in vitro.
290
     * @param string $and       Additional qualifier's to check against.
291
     * 
292
     * @return int|false  (bool) false if the record does not exist, or the id (integer) of the user if we have a match.
293
     *
294
     * ```php
295
     * if ($user_id = $auth->check('[email protected]', 'password', 'approved = "Y"')) {
296
     *     // Then you may proceed with $user_id
297
     * }
298
     * ```
299
     */
300 5
    public function check($email, $password = null, $and = null)
0 ignored issues
show
The parameter $email 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...
The parameter $password 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...
The parameter $and 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...
301
    {
302 5
        $check = func_get_args();
303 5
        $email = array_shift($check);
304 5
        if (empty($check)) {
305 2
            return $this->db->value('SELECT id FROM users WHERE email = ?', $email);
306
        }
307 4
        $password = array_shift($check);
308 4
        $and = (!empty($check)) ? ' AND '.array_shift($check) : '';
309 4
        if ($user = $this->db->row('SELECT id, password AS hash FROM users WHERE email = ?'.$and, $email, 'assoc')) {
310 4
            if (password_verify($password, $user['hash'])) {
311 4
                if (password_needs_rehash($user['hash'], $this->password['algo'], $this->password['options'])) {
312 2
                    $this->db->update('users', 'id', array($user['id'] => array(
313 2
                        'password' => password_hash($password, $this->password['algo'], $this->password['options']),
314 2
                    )));
315 2
                }
316 4
                $this->page->session->set('bootpress/verified', $user['id']);
317
318 4
                return $user['id'];
319
            }
320 1
        }
321
322 1
        return false;
323
    }
324
325
    /**
326
     * Verifies whether or not an email address looks valid.
327
     * 
328
     * @param string $address  The email you would like to verify the looks of.
329
     * 
330
     * @return bool  True or False.  Whether the $address looks like a real email or not.
331
     *
332
     * ```php
333
     * if ($auth->isEmail('[email protected]')) {
334
     *     // Then you may proceed
335
     * }
336
     * ```
337
     */
338 1
    public function isEmail($address)
339
    {
340 1
        return (bool) preg_match('/^[a-zA-Z0-9.!#$%&\'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/', $address);
341
    }
342
343
    /**
344
     * This creates a random password that you can suggest to a user, or use to reset and email them the new password, or to just create a random alpha-numeric string for yourself.
345
     * 
346
     * @param int $length  The desired length of your password
347
     * 
348
     * @return string  The random password.
349
     */
350 1
    public function randomPassword($length = 8)
351
    {
352 1
        $password = '';
353 1
        for ($i = 0; $i < $length; ++$i) {
354 1
            switch (mt_rand(0, 2)) {
355 1
                case 0:
356 1
                    $password .= mt_rand(0, 9);
357 1
                    break; // 0-9
358 1
                case 1:
359 1
                    $password .= chr(mt_rand(65, 90));
360 1
                    break; // A-Z
361 1
                case 2:
362 1
                    $password .= chr(mt_rand(97, 122));
363 1
                    break; // a-z
364 1
            }
365 1
        }
366
367 1
        return $password;
368
    }
369
370
    /**
371
     * This will ensure that a user is registered at the site.  If you get someone registering twice for whatever reason, then this will make sure they are in, and you can advise them whether or not they already hold an account with you.  If they are not a $new_user, then the name and password will not be saved (the email of course will remain the same).  You don't want somebody registering themselves access into someone else's account.
372
     * 
373
     * @param string $name      The user's name.
374
     * @param string $email     The user's email.
375
     * @param string $password  The user's password.  Do not encrypt!  We do that for you.
376
     * 
377
     * @return array  An ``array((bool) $new_user, (int) $user_id)`` where $new_user is either true or false, and $user_id is either the new or the old id depending.
378
     *
379
     * ```php
380
     * list($new_user, $user_id) = $auth->register('Joe Blow', '[email protected]', 'sekrit');
381
     * ```
382
     */
383 1
    public function register($name, $email, $password)
384
    {
385 1
        $new_user = ($user_id = $this->check($email)) ? false : true;
386 1
        if ($new_user) {
387 1
            $user_id = $this->db->insert('users', array(
388 1
                'name' => $name,
389 1
                'email' => $email,
390 1
                'password' => password_hash($password, $this->password['algo'], $this->password['options']),
391 1
                'admin' => 0,
392 1
                'approved' => 'Y',
393 1
                'registered' => date('Y-m-d H:i:s'),
394 1
                'last_activity' => 0,
395 1
            ));
396 1
        }
397
398 1
        return array($new_user, $user_id);
399
    }
400
401
    /**
402
     * Allows you to update a users account.
403
     * 
404
     * @param int   $user_id  The id of the user you would like to update.
405
     * @param array $user     An associative array of fields you would like to update.  The available fields are:
406
     *
407
     * - '**name**' => The users name.
408
     * - '**email**' => The users email.  If you are changing this, then make sure it is not already in use beforehand.
409
     * - '**password**' => The new password.  No encryption needed.
410
     * - '**admin**' => An integer >= 0.
411
     * - '**approved**' => '**Y**' (yes) or '**N**' (no).  If you set this to '**N**' (no), they will be logged out immediately wherever they may be.
412
     * - '**registered**' => 
413
     * - '**last_activity**' => 
414
     *
415
     * ... and any other fields that may exist if you have customized the database to suit your needs.
416
     * 
417
     * ```php
418
     * if ($auth->update($user_id, array(
419
     *     'approved' => 'N', // This will log them out and prevent them from ever signing in again.
420
     * ));
421
     * ```
422
     */
423 1
    public function update($user_id, array $user = array())
424
    {
425 1
        $update = array();
426 1
        foreach ($user as $key => $value) {
427
            switch ($key) {
428 1
                case 'name':
429 1
                    $update[$key] = $value;
430 1
                    break;
431 1
                case 'email':
432 1
                    if ($this->isEmail($value)) {
433 1
                        $update[$key] = $value;
434 1
                    }
435 1
                    break;
436 1
                case 'password':
437 1
                    if (!empty($value)) {
438 1
                        $update[$key] = password_hash($value, $this->password['algo'], $this->password['options']);
439 1
                    }
440 1
                    break;
441 1
                case 'admin':
442 1
                    $update[$key] = (is_numeric($value) && $value > 0) ? (int) $value : 0;
443 1
                    break;
444 1
                case 'approved':
445 1
                    $update[$key] = (empty($value) || strtoupper($value) == 'N') ? 'N' : 'Y';
446 1
                    break;
447 1
                case 'registered':
448 1
                    if (is_numeric($value)) {
449 1
                        $update[$key] = date('Y-m-d H:i:s', (int) $value);
450 1
                    }
451 1
                    break;
452 1
                case 'last_activity':
453 1
                    if (is_numeric($value)) {
454 1
                        $update[$key] = (int) $value;
455 1
                    }
456 1
                    break;
457 1
                default:
458 1
                    $update[$key] = $value;
459 1
                    break;
460 1
            }
461 1
        }
462 1
        if (!empty($update)) {
463 1
            $this->db->update('users', 'id', array($user_id => $update));
464 1
            if ($this->isUser($user_id)) {
465 1
                foreach (array('name', 'email', 'admin') as $value) {
466 1
                    if (isset($update[$value])) {
467 1
                        $this->user[$value] = $update[$value];
468 1
                    }
469 1
                }
470 1
            }
471 1
            if (isset($update['approved']) && $update['approved'] == 'N') {
472 1
                $this->logout($user_id);
473 1
            }
474 1
        }
475 1
    }
476
    
477
    /**
478
     * This will login a user to your site for a specified amount of time (of inactivity), and optionally log them out everywhere else.  Session and cookie based.
479
     * 
480
     * @param integer     $user_id  Of the user you want to login.
481
     * @param integer     $expires  The number of days (if less than or equal to 730) or seconds (if greater than 730) of inactivity after which you would like to require the user to sign back into your site.
482
     * @param false|mixed $single   If you set this to true (or to anything besides false), then they will be logged out of every other session that may be currently active.  Meaning if you sign in on a public computer, then realize you forgot to sign out, you can sign in again on any other computer and be signed out from all previous sessions if you use this feature.
483
     * 
484
     * ```php
485
     * $auth->login($user_id, 30, 'single'); // Sign in $user_id for 30 days here, and log them out everywhere else.
486
     * ```
487
     */
488 4
    public function login($user_id, $expires = 7200, $single = false)
489
    {
490 4
        if (empty($user_id)) {
491 1
            return;
492
        }
493 4
        $this->logout(($single !== false) ? $user_id : null);
494 4
        if ($user = $this->db->row('SELECT id, name, email, admin FROM users WHERE id = ? AND approved = ?', array($user_id, 'Y'), 'assoc')) {
495 4
            $this->session = array(
496 4
                'id' => '',
497 4
                'series' => $this->salt(),
498 4
                'token' => $this->salt(),
499 4
                'time' => time(),
500 4
                'user_agent' => trim(substr($this->page->request->headers->get('User-Agent'), 0, 255)),
501
            );
502 4
            $relapse = ($expires <= 730) ? $expires * 24 * 60 * 60 : $expires;
503 4
            $this->session['id'] = $this->db->insert('user_sessions', array(
504 4
                'user_id' => $user['id'],
505 4
                'adjourn' => $this->session['time'] + $relapse,
506 4
                'relapse' => $relapse,
507 4
                'last_activity' => $this->session['time'],
508 4
                'ip_address' => $this->page->request->getClientIp(),
509 4
                'user_agent' => $this->session['user_agent'],
510 4
                'series' => sha1($this->session['series']),
511 4
                'token' => sha1($this->session['token']),
512 4
                'login' => date('Y-m-d H:i:s', $this->session['time']),
513 4
            ));
514 4
            $this->db->update('users', 'id', array($user['id'] => array('last_activity' => $this->session['time'])));
515 4
            $this->setCookie(implode(' ', array($this->session['id'], $this->session['series'], $this->session['token'])), $relapse);
516 4
            $this->user = $user;
517 4
        }
518 4
    }
519
520
    /**
521
     * This will log a session authenticated user out of your site.
522
     * 
523
     * @param integer $user_id  If this is an integer, then $user_id will be logged out of all their sessions, everywhere.  If null (or not given), then the current user will be logged out of the current session.
524
     * 
525
     * ```php
526
     * $auth->logout(); // Log out the current user.
527
     * ```
528
     */
529 6
    public function logout($user_id = null)
530
    {
531 6
        if ($user_id) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $user_id of type integer|null is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
532 5
            $this->db->exec('DELETE FROM user_sessions WHERE user_id = ? AND adjourn <= ?', array($user_id, time()));
533 5
            if ($user_id == $this->isUser()) {
534 1
                $this->logout();
535 1
            }
536 6
        } elseif (isset($this->session['id'])) {
537 2
            $this->setCookie();
538 2
            $this->db->exec('DELETE FROM user_sessions WHERE id = ? AND adjourn <= ?', array($this->session['id'], time()));
539 2
            $this->session = array();
540 2
            $this->user = array();
541 2
        }
542 6
    }
543
544
    /**
545
     * Get the number of active, session authenticated users at your site within the $duration given.
546
     * 
547
     * @param integer $duration  How far back (in seconds) you would like to go.  The default is 600 seconds ie. 10 minutes.
548
     * 
549
     * @return integer  The total number of active users.  If this is 1, then that's you!
550
     * 
551
     * ```php
552
     * $auth->count(86400); // within the last 24 hours
553
     * ```
554
     */
555 1
    public function online($duration = 600)
556
    {
557 1
        return $this->db->value('SELECT COUNT(*) FROM users WHERE last_activity >= ?', array(time() - $duration));
558
    }
559
560
    /**
561
     * This will tell you if the person viewing the current page is a specific (optional) user, or not.  This does not apply if the user is HTTP Basic Authenticated from an array or YAML file of users.
562
     * 
563
     * @param integer $user_id  If you want to verify that this is a specific user, then you may indicate the user's id here.
564
     * 
565
     * @return false|integer  False if they are not a signed in user, and if you included a $user_id then false if they are not that specific user.  Otherwise this returns the id of the signed in user.
566
     * 
567
     * ```php
568
     * if ($user_id = $auth->isUser()) {
569
     *     // Now we may do something specifically for $user_id
570
     * }
571
     * 
572
     * if (!$auth->isUser($seller_id)) {
573
     *     $page->eject(); // not the real seller, get them out of here
574
     * }
575
     * 
576
     * if ($auth->isUser()) {
577
     *     // Display a logout link
578
     * }
579
     * ```
580
     */
581 7
    public function isUser($user_id = null)
582
    {
583 7
        if (!$user = $this->user('id')) {
584 7
            return false;
585
        }
586 5
        if (empty($user_id)) {
587 5
            return (!empty($user)) ? $user : false; // an id > 0 or false
588
        }
589
590 2
        return (!empty($user) && $user_id == $user) ? $user : false;
591
    }
592
593
    /**
594
     * Allows you to determine if ``$this->isUser()`` submitted a password during the current session, or if we're relying on a remember-me cookie.
595
     * 
596
     * @return false|integer  The current user's id if they submitted a password during the current session, or false if not.
597
     * 
598
     * ```php
599
     * if ($user_id = $auth->isVerified()) {
600
     *     // Allow them to change their password, charge their credit card, etc.
601
     * }
602
     * ```
603
     */
604 1
    public function isVerified()
605
    {
606 1
        return (($user_id = $this->isUser()) && $user_id == $this->page->session->get('bootpress/verified')) ? $user_id : false;
607
    }
608
609
    /**
610
     * This will tell you if the person viewing the current page has admin access greater than or equal to $level, or not.  There is no need to check if ``$this->isUser()`` first when using this function, unless you also want the $user_id, or to make sure this is a specific admin user.  HTTP Basic Authenticated users will always pass this test and returns (integer) 1, giving them super-admin privileges.
611
     * 
612
     * @param integer $level  The admin user must be greater than or equal to the level you indicate here.  This method manages admin permissions as follows:
613
     * 
614
     * 1. Is the end all and be all of admins, and can access anything.
615
     * 2. Does not have level 1 access, but can access 2, 3, 4, 5, etc.
616
     * 3. Cannot access level 1 or 2 admin pages, but can access 3, 4, 5, 6 ... you get the picture.
617
     * 
618
     * @return bool  False if they are not even a user in the first place, and false again if they don't have the level of access you desire.  Otherwise it returns the level of access they have.
619
     * 
620
     * ```php
621
     * if ($auth->isAdmin(1) || $auth->isUser($seller)) {
622
     *     // Now you and the seller can edit their info
623
     * }
624
     * 
625
     * if ($level = $auth->isAdmin(2)) {
626
     *     // Level 1 and level 2 admins can access this
627
     *     if ($level == 1) {
628
     *         // Now we are tightening down the hatches
629
     *     }
630
     * }
631
     * ```
632
     */
633 3
    public function isAdmin($level = 1)
634
    {
635 3
        if ($this->basic) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->basic of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
636 1
            return 1;
637 3
        } elseif (!$admin = $this->user('admin')) {
638 1
            return false;
639
        }
640
641 2
        return (!empty($admin) && $admin <= $level) ? $admin : false;
642
    }
643
644
    /**
645
     * Checks to see if the signed in user is included in any or all of the group(s) you specify.
646
     * 
647
     * @param string|array $group  The name of the group.  To verify membership for the current user against 
648
     * @param string $check  If '**all**' (default) then they must be in every group specified.  If '**any**' then they must be in at least one of the groups specified.
649
     * 
650
     * @return bool  True or False.
651
     * 
652
     * ```php
653
     * if ($auth->inGroup(array('heaven', 'hell'), 'any')) {
654
     *     // Then this signed in user is dead.
655
     *     $auth->logout();
656
     * }
657
     * ```
658
     */
659 2
    public function inGroup($group, $check = 'all')
660
    {
661 2
        if ($this->basic) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->basic of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
662 1
            return true;
663
        }
664 2
        return ($user_id = $this->isUser()) ? $this->userInGroup($user_id, $group, $check) : false;
0 ignored issues
show
$user_id is of type array|string, but the function expects a integer.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
It seems like $group defined by parameter $group on line 659 can also be of type array; however, BootPress\Auth\Component::userInGroup() does only seem to accept string, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
665
    }
666
667
    /**
668
     * This will add $user_id(s) to the $group(s) you want to include them in.
669
     * 
670
     * @param integer $user_id  Of the user.  To add multiple users, make this an ``array($user_id, ...)`` of users to add to the $group(s).
671
     * @param string  $group    The name of the group you would like to include them in.  To include in multiple groups, make this an ``array($group, ...)`` of groups to add the $user_id(s) to.
672
     * 
673
     * ```php
674
     * $auth->addToGroup($user_id, 'rothschild');
675
     * ```
676
     */
677 1
    public function addToGroup($user_id, $group)
678
    {
679 1
        $users = (array) $user_id;
680 1
        $groups = (array) $this->groupId($group);
681 1
        $this->removeFromGroup($users, $groups);
0 ignored issues
show
$users is of type array, but the function expects a integer.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
$groups is of type array, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
682 1
        $stmt = $this->db->insert('user_groups', array('user_id', 'group_id'));
683 1
        foreach ($users as $user_id) {
684 1
            foreach ($groups as $group_id) {
685 1
                $this->db->insert($stmt, array($user_id, $group_id));
686 1
            }
687 1
        }
688 1
        $this->db->close($stmt);
689 1
    }
690
691
    /**
692
     * This will remove $user_id(s) from the $group(s) they no longer belong in.
693
     * 
694
     * @param integer $user_id  Of the user.  To remove multiple users, make this an ``array($user_id, ...)`` of users to remove from the $group(s).
695
     * @param string  $group    The name of the group you would like to remove them from.  To remove from multiple groups, make this an ``array($group, ...)`` of groups to remove the $user_id(s) from.
696
     * 
697
     * ```php
698
     * $auth->removeFromGroup($user_id, 'rothschild'); // $user_id should not be involved with them anyways
699
     * ```
700
     */
701 1
    public function removeFromGroup($user_id, $group)
702
    {
703 1
        $this->db->exec(array(
704 1
            'DELETE FROM user_groups',
705 1
            'WHERE user_id IN ('.implode(', ', (array) $user_id).')',
706 1
            'AND group_id IN('.implode(', ', (array) $this->groupId($group)).')',
707 1
        ));
708 1
    }
709
710
    /**
711
     * This will retrieve all of the groups that $user_id(s) belong to.
712
     * 
713
     * @param integer $user_id  Of the user.  If you want to retrieve the groups of multiple users at once, then make this an ``array($user_id, ...)`` of users.
714
     * 
715
     * @return array  A single ``array($group, ...)`` of groups if $user_id is an integer (single), or a multidimensional ``array($user_id => $groups, ...)`` for every $user_id in the order given.
716
     * 
717
     * ```php
718
     * $groups = $auth->getUsersGroups($user_id);
719
     * ```
720
     */
721 4
    public function getUsersGroups($user_id)
722
    {
723 4
        $single = (is_array($user_id)) ? false : true;
724 4
        $ids = ($single) ? array($user_id) : $user_id;
725 4
        $users = array();
726 4
        foreach ($ids as $id) {
0 ignored issues
show
The expression $ids of type array|integer is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
727 4
            $users[$id] = array();
728 4
        }
729 4
        foreach ($this->db->all(array(
730 4
            'SELECT u.user_id, g.name AS group_name',
731 4
            'FROM user_groups AS u',
732 4
            'INNER JOIN user_group_names AS g ON u.group_id = g.id',
733 4
            'WHERE u.user_id IN('.implode(', ', $ids).')',
734 4
            'ORDER BY g.name ASC',
735 4
        ), '', 'assoc') as $row) {
736 2
            $users[$row['user_id']][] = $row['group_name'];
737 4
        }
738
739 4
        return ($single) ? array_shift($users) : $users;
740
    }
741
742
    /**
743
     * This will retrieve all of the user_id's that belong to the $group(s).
744
     * 
745
     * @param string $group  The name of the group.  If you want to retrieve the user_id's of multiple groups at once, then make this an ``array($group, ...)`` of groups.
746
     * 
747
     * @return array  A single ``array($user_id, ...)`` of users if $groups is a string (single), or a multidimensional ``array($group => (array) $user_ids, ...)`` for every $group in the order given.
748
     * 
749
     * ```php
750
     * $users = $auth->getGroupsUsers('rothschild');
751
     * ```
752
     */
753 1
    public function getGroupsUsers($group)
754
    {
755 1
        $single = (is_array($group)) ? false : true;
756 1
        $ids = ($single) ? array($group) : $group;
757 1
        $groups = array();
758 1
        foreach ($ids as $id) {
0 ignored issues
show
The expression $ids of type array|string is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
759 1
            $groups[$id] = array();
760 1
        }
761 1
        foreach ($this->db->all(array(
762 1
            'SELECT u.user_id, u.group_id, g.name AS group_name',
763 1
            'FROM user_groups AS u',
764 1
            'INNER JOIN user_group_names AS g ON u.group_id = g.id',
765 1
            'WHERE u.user_id > 0 AND u.group_id IN('.implode(', ', $this->groupId($ids)).')',
766 1
            'ORDER BY u.user_id, u.group_id ASC',
767 1
        ), '', 'assoc') as $row) {
768 1
            $key = (isset($groups[$row['group_id']])) ? $row['group_id'] : $row['group_name'];
769 1
            $groups[$key][] = $row['user_id'];
770 1
        }
771
772 1
        return ($single) ? array_shift($groups) : $groups;
773
    }
774
775
    /**
776
     * This will verify if $user_id is in '**any**' or '**all**' of the $group(s) specified.  If you are checking for the current user of your page, then do not use this method.  Use ``$this->inGroup()`` instead.
777
     * 
778
     * @param integer $user_id  Of the user whose $group(s) membership you want to verify.  This cannot be an array.  Sorry.
779
     * @param string  $group    The name of the group.  To verify $user_id against multiple groups then make this an ``array($group, ...)`` of groups.
780
     * @param string  $check    If '**all**' (default) then they must be in every group specified.  If '**any**' then they must be in at least one of the groups specified.
781
     * 
782
     * @return array  True or False, whether or not your conditions were met.
783
     * 
784
     * ```php
785
     * if ($auth->userInGroup($user_id, array('rothschild', 'white house', 'al-Qaeda'), 'any') {
786
     *     // Well then we've got quite the user on our hands.
787
     * }
788
     * ```
789
     */
790 2
    public function userInGroup($user_id, $group, $check = 'all') // or 'any'
791
    {
792 2
        $groups = implode(',', $this->getUsersGroups($user_id));
793 2
        if (empty($groups)) {
794 1
            return false;
795
        }
796 2
        $check = (in_array(strtolower($check), array('all', 'and', '&&'))) ? 'all' : 'any';
797 2
        foreach ((array) $group as $in) {
798 2
            if (mb_stripos(",{$groups},", ",{$in},") !== false) {
799 2
                if ($check == 'any') {
800 2
                    return true; // there is no sense in checking any more beyond this
801
                }
802 2
            } elseif ($check == 'all') {
803 2
                return false; // they are not in this group, so that is all we need to know
804
            }
805 2
        }
806
807 1
        return ($check == 'all') ? true : false;
808
    }
809
810
    /**
811
     * Retrieves a group's id.
812
     * 
813
     * @param string|array $name  Of the group.
814
     * 
815
     * @return integer|array  The group(s) id(s).
816
     */
817 1
    private function groupId($name)
818
    {
819 1
        $single = (is_array($name)) ? false : true;
820 1
        $groups = array();
821 1
        foreach ((array) $name as $group) {
822 1
            if (empty($group)) {
823 1
                $group_id = 0;
824 1
            } elseif (is_numeric($group)) {
825 1
                $group_id = $group;
826 1
            } else {
827 1
                $group_id = $this->db->value('SELECT id FROM user_group_names WHERE name = ?', $group);
828 1
                if (empty($group_id)) {
829 1
                    $group_id = $this->db->insert('user_group_names', array('name' => $group));
830 1
                }
831
            }
832 1
            $groups[$group] = $group_id;
833 1
        }
834
835 1
        return ($single) ? array_shift($groups) : $groups;
836
    }
837
838
    /**
839
     * Sets (or removes if the $value is empty) a cookie to verify the validity of a session.
840
     * 
841
     * @param string  $value    Of the cookie.
842
     * @param integer $expires  How long (in seconds) the cookie should exist.
843
     */
844 6
    private function setCookie($value = '', $expires = 0)
845
    {
846 6
        if (empty($value)) {
847 6
            $this->page->session->remove('bootpress/cookie');
848 6
            $expires =  1;
849 6
        } else {
850 4
            $this->page->session->set('bootpress/cookie', $value);
851 4
            $value = base64_encode($value);
852 4
            if ($expires != 0) {
853 4
                $expires += time();
854 4
            }
855
        }
856 6
        $match = session_get_cookie_params();
857 6
        setcookie('bootpress', $value, $expires, $match['path'], $match['domain'], $match['secure'], true);
858 6
    }
859
860
    /**
861
     * Generates the series and tokens we use to compare sessions and cookies with.
862
     * 
863
     * @return string  A random, 22 character string.
864
     */
865 4
    private function salt()
866
    {
867 4
        return substr(strtr(rtrim(base64_encode(random_bytes(16)), '='), '+', '.'), 0, 22);
868
    }
869
}
870