Issues (994)

src/Session/session.php (8 issues)

1
<?php
2
3
namespace Session;
4
5
use DateTime;
6
use Filemanager\file;
7
8
if (!function_exists('folder_session')) {
9
  include __DIR__ . '/../MVC/loader.php';
10
}
11
12
if (!defined('ROOT')) {
13
  define('ROOT', __DIR__);
14
}
15
16
class session
17
{
18
  private $path_defined = null;
19
  public $sessionCookieName = 'uf';
20
  public $cookiePath = '/';
21
  public $cookieDomain = '';
22
  /**
23
   * Cookie will only be set if a secure HTTPS connection exists.
24
   *
25
   * @var bool
26
   */
27
  private $cookieSecure = false;
28
  /**
29
   * sid regex expression.
30
   *
31
   * @var string
32
   */
33
  private $sidRegexp;
34
35
  public function __construct(int $timeout = 3600, string $session_folder = null)
36
  {
37
    if (!$this->is_session_started()) {
38
      //$this->configure($timeout, $session_folder);
39
      //$this->start_timeout($timeout);
40
      $this->handle($timeout, $session_folder);
41
    }
42
  }
43
44
  public function is_session_started()
45
  {
46
    return PHP_SESSION_ACTIVE == session_status();
47
  }
48
49
  public function handle(int $timeout, string $folder = null)
50
  {
51
    $name =  '_' . $timeout . md5(\MVC\helper::getRequestIP() . \MVC\helper::useragent());
52
    if (empty(trim($folder)) || !$folder) {
0 ignored issues
show
It seems like $folder can also be of type null; however, parameter $string of trim() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

52
    if (empty(trim(/** @scrutinizer ignore-type */ $folder)) || !$folder) {
Loading history...
53
      $folder = \Filemanager\file::folder(__DIR__ . '/sessions');
54
    }
55
    session_save_path($folder);
56
57
    session_set_cookie_params($timeout);
58
59
    ini_set('session.gc_maxlifetime', $timeout);
60
    ini_set('session.cookie_lifetime', $timeout);
61
    ini_set('session.gc_probability', 100);
62
    ini_set('session.gc_divisor', 100);
63
64
    session_id($name);
65
    ini_set('session.use_strict_mode', 0);
66
67
    session_name($name);
68
    ini_set('session.use_strict_mode', 1);
69
70
    $handler = new FileSessionHandler($folder, 'PHPJS');
71
    session_set_save_handler(
72
      [$handler, 'open'],
73
      [$handler, 'close'],
74
      [$handler, 'read'],
75
      [$handler, 'write'],
76
      [$handler, 'destroy'],
77
      [$handler, 'gc']
78
    );
79
    register_shutdown_function('session_write_close');
80
    session_start();
81
82
    $path = ini_get('session.save_path');
83
    if (!file_exists($path . '/.htaccess')) {
84
      file::file($path . '/.htaccess', 'deny from all');
0 ignored issues
show
'deny from all' of type string is incompatible with the type boolean expected by parameter $create of Filemanager\file::file(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

84
      file::file($path . '/.htaccess', /** @scrutinizer ignore-type */ 'deny from all');
Loading history...
85
    }
86
    if (!isset($_SESSION['session_started'])) {
87
      $_SESSION['session_started'] = $this->now();
88
      $_SESSION['session_timeout'] = ini_get('session.gc_maxlifetime');
89
      $_SESSION['cookie_timeout'] = ini_get('session.cookie_lifetime');
90
    }
91
  }
92
93
  private function configure(int $timeout, string $folder = null)
94
  {
95
    ini_set('session.autostart', false);
0 ignored issues
show
false of type false is incompatible with the type string expected by parameter $value of ini_set(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

95
    ini_set('session.autostart', /** @scrutinizer ignore-type */ false);
Loading history...
96
    //increase memory
97
    ini_set('memory_limit', '256M'); //-1
98
99
    // Set the max lifetime
100
    ini_set('session.gc_maxlifetime', $timeout);
101
102
    // Set the session cookie to timeout
103
    ini_set('session.cookie_lifetime', $timeout);
104
105
    //coookie name
106
    if (empty($this->sessionCookieName)) {
107
      $this->sessionCookieName = \MVC\helper::clean_special_characters($_SERVER['HTTP_HOST']);
108
    }
109
    session_name('WebsiteID');
110
    ini_set('session.name', 'WebsiteID');
111
112
    //set session id
113
    session_id(md5(\MVC\helper::getRequestIP()));
114
115
    /*session_set_cookie_params(
116
      $timeout,
117
      $this->cookiePath,
118
      $this->cookieDomain,
119
      $this->cookieSecure,
120
      true // HTTP only; Yes, this is intentional and not configurable for security reasons.
121
    );*/
122
123
    //========== Session save path
124
    if ($folder && realpath($folder)) {
125
      $folder = realpath($folder);
126
    } else {
127
      $folder = \Filemanager\file::folder(__DIR__ . '/sessions');
128
    }
129
    // Set session directory save path
130
    ini_set('session.save_path', $folder);
131
132
    // Change the save path. Sessions stored in teh same path
133
    // all share the same lifetime; the lowest lifetime will be
134
    // used for all. Therefore, for this to work, the session
135
    // must be stored in a directory where only sessions sharing
136
    // it's lifetime are. Best to just dynamically create on.
137
    $seperator = strstr(strtoupper(substr(PHP_OS, 0, 3)), 'WIN') ? '\\' : '/';
138
    $dir = isset($_SERVER['HTTP_USER_AGENT']) ? md5($_SERVER['HTTP_USER_AGENT']) : \MVC\helper::getRequestIP();
139
    if (!$dir) {
140
      $dir = 'null';
141
    }
142
    $path = ini_get('session.save_path') . $seperator . $timeout . $dir;
143
    $path = file::folder($path);
144
    if (!is_dir(dirname($path))) {
145
      if (!mkdir(dirname($path), 0777, true)) {
146
        trigger_error("Failed to create session save path directory '$path'. Check permissions.", E_USER_ERROR);
147
      }
148
    }
149
    if (!file_exists($path)) {
150
      if (!mkdir($path, 0777, true)) {
151
        trigger_error("Failed to create session save path directory '$path'. Check permissions.", E_USER_ERROR);
152
      }
153
    }
154
    ini_set('session.save_path', $path);
155
    $this->path_defined = $path;
156
    //========== Session save path
157
158
    // Security is king
159
    ini_set('session.use_trans_sid', 0);
160
    ini_set('session.use_strict_mode', 1);
161
    ini_set('session.use_cookies', 1);
162
    //ini_set('session.use_only_cookies', 1);
163
164
    $bits_per_character = (int) (false !== ini_get('session.sid_bits_per_character')
165
      ? ini_get('session.sid_bits_per_character')
166
      : 4);
167
    $sid_length = (int) (false !== ini_get('session.sid_length')
168
      ? ini_get('session.sid_length')
169
      : 40);
170
    if (($sid_length * $bits_per_character) < 160) {
171
      $bits = ($sid_length * $bits_per_character);
172
      // Add as many more characters as necessary to reach at least 160 bits
173
      $sid_length += (int) ceil((160 % $bits) / $bits_per_character);
174
      ini_set('session.sid_length', $sid_length);
175
    }
176
177
    // Yes, 4,5,6 are the only known possible values as of 2016-10-27
178
    switch ($bits_per_character) {
179
      case 4:
180
        $this->sidRegexp = '[0-9a-f]';
181
        break;
182
      case 5:
183
        $this->sidRegexp = '[0-9a-v]';
184
        break;
185
      case 6:
186
        $this->sidRegexp = '[0-9a-zA-Z,-]';
187
        break;
188
    }
189
190
    $this->sidRegexp .= '{' . $sid_length . '}';
191
  }
192
193
  /***
194
   * Starts a session with a specific timeout and a specific GC probability.
195
   * @param int $timeout The number of seconds until it should time out.
196
   * @param int $probability The probablity, in int percentage, that the garbage
197
   *        collection routine will be triggered right now.
198
   * @param strint $cookie_domain The domain path for the cookie.
0 ignored issues
show
The type Session\strint was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
199
   */
200
  public function start_timeout($timeout = 5, $probability = 100)
201
  {
202
    if ($this->is_session_started()) {
203
      throw new \MVC\Exception('Session already started', 1);
204
    }
205
206
    // Set the chance to trigger the garbage collection.
207
    ini_set('session.gc_probability', $probability);
208
    ini_set('session.gc_divisor', 100); // Should always be 100
209
210
    if (!$this->path_defined) {
211
      $this->configure($timeout, null);
212
    }
213
214
    // Start the session!
215
    session_start();
216
217
    // Renew the time left until this session times out.
218
    // If you skip this, the session will time out based
219
    // on the time when it was created, rather than when
220
    // it was last used.
221
    if (isset($_COOKIE[session_name()]) && !headers_sent()) {
222
      setcookie(session_name(), $_COOKIE[session_name()], time() + $timeout, $this->cookiePath, $this->cookieDomain, $this->cookieSecure, true);
223
    }
224
    $this->protect_session($timeout);
225
  }
226
227
  public function dump()
228
  {
229
    exit(\JSON\json::json(
0 ignored issues
show
Are you sure the usage of JSON\json::json(array('s...sion.hash_function')))) targeting JSON\json::json() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
230
      [
231
        'sessions' => [
232
          'active' => PHP_SESSION_NONE == session_status(),
233
          'id' => session_id(),
234
          'folder' => \MVC\helper::fixSlash(ini_get('session.save_path')),
235
          'session.gc_maxlifetime' => ini_get('session.gc_maxlifetime'),
236
          'session.cookie_lifetime' => ini_get('session.cookie_lifetime'),
237
          'session.gc_probability' => ini_get('session.gc_probability'),
238
          'session.gc_divisor' => ini_get('session.gc_divisor'),
239
          'session.hash_function' => ini_get('session.hash_function'),
240
        ],
241
      ]
242
    ));
243
  }
244
245
  public static function has($key, bool $empty = true)
246
  {
247
    $return = isset($_SESSION[$key]);
248
    if ($return && !$empty) {
249
      $return = $return && !empty($return);
250
    }
251
252
    return $return;
253
  }
254
255
  public static function get($key)
256
  {
257
    if (isset($_SESSION[$key])) {
258
      return $_SESSION[$key];
259
    }
260
  }
261
262
  public static function gets(array $keys)
263
  {
264
    $result = [];
265
    foreach ($keys as $key) {
266
      if (isset($_SESSION[$key])) {
267
        $result[] = $_SESSION[$key];
268
      }
269
    }
270
271
    return $result;
272
  }
273
274
  public static function all()
275
  {
276
    return $_SESSION;
277
  }
278
279
  private static $_instance = null;
280
281
  public static function getInstance()
282
  {
283
    if (null === self::$_instance) {
284
      self::$_instance = new self();
285
    }
286
287
    return self::$_instance;
288
  }
289
290
  /**
291
   * Unset Session.
292
   *
293
   * @param array|string|Number $name
294
   */
295
  public static function unses($name)
296
  {
297
    if (is_array($name)) {
298
      foreach ($name as $n) {
299
        if (isset($_SESSION[$n])) {
300
          unset($_SESSION[$n]);
301
        }
302
      }
303
    } elseif (is_string($name)) {
304
      if (isset($_SESSION[$name])) {
305
        unset($_SESSION[$name]);
306
      }
307
    }
308
  }
309
310
  private function protect_session(int $timeout)
311
  {
312
    //creating htaccess for deny direct access
313
    $path = ini_get('session.save_path');
314
    if (!file_exists($path . '/.htaccess')) {
315
      file::file($path . '/.htaccess', 'deny from all');
0 ignored issues
show
'deny from all' of type string is incompatible with the type boolean expected by parameter $create of Filemanager\file::file(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

315
      file::file($path . '/.htaccess', /** @scrutinizer ignore-type */ 'deny from all');
Loading history...
316
    }
317
    if (!isset($_SESSION['session_started'])) {
318
      $_SESSION['session_started'] = $this->now();
319
      $_SESSION['session_timeout'] = ini_get('session.gc_maxlifetime');
320
      $_SESSION['cookie_timeout'] = ini_get('session.cookie_lifetime');
321
    } elseif (isset($_SESSION['session_started'])) {
322
      /**
323
       * @var DateTime
324
       */
325
      $started = $_SESSION['session_started'];
326
      $ago = $started->getTimestamp() + $timeout;
327
      $_SESSION['session_expires_in'] = date('D M j G:i:s O Y', $ago);
328
      if ($ago < $this->now()->getTimestamp()) {
329
        session_regenerate_id(isset($_SERVER['HTTP_LOGOUT']));
330
      }
331
    }
332
    if (isset($_SESSION['login'])) {
333
      if (isset($_REQUEST['dump-session']) && isset($_SESSION['login']['role'])) {
334
        if (preg_match('/admin/s', $_SESSION['login']['role'])) {
335
          $this->dump();
336
        }
337
      }
338
    }
339
  }
340
341
  public function now()
342
  {
343
    $now = new \DateTime(null, new \DateTimeZone('Asia/Jakarta'));
344
345
    return $now;
346
  }
347
348
  public function is_sess($session_name, $not_found = null)
349
  {
350
    if (isset($_SESSION[$session_name])) {
351
      return $_SESSION[$session_name];
352
    }
353
354
    return $not_found;
355
  }
356
357
  public function sess($key, $val)
358
  {
359
    $_SESSION[$key] = $val;
360
  }
361
362
  public static function set_session($data, $value = null)
363
  {
364
    if (\ArrayHelper\helper::is_iterable($data)) {
365
      foreach ($data as $key => $val) {
366
        $_SESSION[$key] = $val;
367
      }
368
    }
369
    if (is_string($data)) {
370
      $_SESSION[$data] = $value;
371
    }
372
  }
373
374
  /**
375
   * Regenerates the session ID.
376
   *
377
   * @param bool $destroy Should old session data be destroyed?
378
   */
379
  private function regenerate(bool $destroy = false)
0 ignored issues
show
The method regenerate() is not used, and could be removed.

This check looks for private methods that have been defined, but are not used inside the class.

Loading history...
380
  {
381
    $_SESSION['session_last_generate'] = time();
382
    session_regenerate_id($destroy);
383
  }
384
}
385