• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

cypht-org / cypht / 30095700391

24 Jul 2026 01:08PM UTC coverage: 87.092% (-0.2%) from 87.271%
30095700391

push

travis-ci

web-flow
Merge pull request #2045 from IrAlfred/fix-proxy-login-session-origin-cookie-domain

fix(other): fix proxy-related login loop and cookie domain mismatch

21 of 37 new or added lines in 3 files covered. (56.76%)

5816 of 6678 relevant lines covered (87.09%)

11.66 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

99.32
/lib/session_base.php
1
<?php
2

3
/**
4
 * Session handling
5
 * @package framework
6
 * @subpackage session
7
 */
8

9
/**
10
 * Use for browser fingerprinting
11
 */
12
trait Hm_Session_Fingerprint {
13

14
    /**
15
     * Save a value in the session
16
     * @param string $name the name to save
17
     * @param string $value the value to save
18
     * @return void
19
     */
20
    abstract protected function set($name, $value);
21

22
    /**
23
     * Destroy a session for good
24
     * @param object $request request details
25
     * @return void
26
     */
27
    abstract protected function destroy($request);
28

29
    /**
30
     * Return a session value, or a user settings value stored in the session
31
     * @param string $name session value name to return
32
     * @param string $default value to return if $name is not found
33
     * @return mixed the value if found, otherwise $default
34
     */
35
    abstract protected function get($name, $default = false);
36

37
    /**
38
     * Check HTTP header "fingerprint" against the session value
39
     * @param object $request request details
40
     * @return void
41
     */
42
    public function check_fingerprint($request) {
1✔
43
        if ($this->site_config->get('disable_fingerprint')) {
1✔
44
            return;
1✔
45
        }
46
        $id = $this->build_fingerprint($request->server);
1✔
47
        $fingerprint = $this->get('fingerprint', false);
1✔
48
        if ($fingerprint === false || $fingerprint === null) {
1✔
49
            $this->set_fingerprint($request);
1✔
50
            return;
1✔
51
        }
52
        if (!$fingerprint || $fingerprint !== $id) {
1✔
53
            Hm_Debug::add('HTTP header fingerprint check failed');
1✔
54
            $this->destroy($request);
1✔
55
        }
56
    }
57

58
    /**
59
     * Browser request properties to build a fingerprint with
60
     * @return array
61
     */
62
    private function fingerprint_flds() {
16✔
63
        $flds = ['HTTP_USER_AGENT', 'REQUEST_SCHEME', 'HTTP_ACCEPT_LANGUAGE',
16✔
64
            'HTTP_ACCEPT_CHARSET', 'HTTP_HOST'];
16✔
65
        if (!$this->site_config->get('allow_long_session') && !$this->site_config->get('disable_ip_check')) {
16✔
66
            $flds[] = 'REMOTE_ADDR';
16✔
67
        }
68
        return $flds;
16✔
69
    }
70

71
    /**
72
     * Build HTTP header "fingerprint"
73
     * @param array $env server env values
74
     * @return string fingerprint value
75
     */
76
    public function build_fingerprint($env, $input = '') {
16✔
77
        $id = $input;
16✔
78
        foreach ($this->fingerprint_flds() as $val) {
16✔
79
            $id .= (array_key_exists($val, $env)) ? $env[$val] : '';
16✔
80
        }
81
        return hash('sha256', $id);
16✔
82
    }
83

84
    /**
85
     * Save a fingerprint in the session
86
     * @param object $request request details
87
     * @return void
88
     */
89
    protected function set_fingerprint($request) {
2✔
90
        $id = $this->build_fingerprint($request->server);
2✔
91
        $this->set('fingerprint', $id);
2✔
92
    }
93
}
94

95
/**
96
 * Base class for session management. All session interaction happens through
97
 * classes that extend this.
98
 * @abstract
99
 */
100
abstract class Hm_Session {
101

102
    use Hm_Session_Fingerprint;
103

104
    /* set to true if the session was just loaded on this request */
105
    public $loaded = false;
106

107
    /* set to true if the session is active */
108
    public $active = false;
109

110
    /* set to true if the user authentication is local (DB) */
111
    public $internal_users = false;
112

113
    /* key used to encrypt session data */
114
    public $enc_key = '';
115

116
    /* authentication class name */
117
    public $auth_class;
118

119
    /* site config object */
120
    public $site_config;
121

122
    /* session data */
123
    protected $data = [];
124

125
    /* session cookie name */
126
    protected $cname = 'hm_session';
127

128
    /* authentication object */
129
    protected $auth_mech;
130

131
    /* close early flag */
132
    protected $session_closed = false;
133

134
    /* session key */
135
    public $session_key = '';
136

137
    /* session lifetime */
138
    public $lifetime = 0;
139

140
    /**
141
     * check for an active session or an attempt to start one
142
     * @param object $request request object
143
     * @return bool
144
     */
145
    abstract protected function check($request);
146

147
    /**
148
     * Start the session. This could be an existing session or a new login
149
     * @param object $request request details
150
     * @return void
151
     */
152
    abstract protected function start($request);
153

154
    /**
155
     * Call the configured authentication method to check user credentials
156
     * @param string $user username
157
     * @param string $pass password
158
     * @return bool true if the authentication was successful
159
     */
160
    abstract protected function auth($user, $pass);
161

162
    /**
163
     * Delete a value from the session
164
     * @param string $name name of value to delete
165
     * @return void
166
     */
167
    abstract protected function del($name);
168

169
    /**
170
     * End a session after a page request is complete. This only closes the session and
171
     * does not destroy it
172
     * @return void
173
     */
174
    abstract protected function end();
175

176
    /**
177
     * Setup initial data
178
     * @param object $config site config
179
     * @param string $auth_type authentication class
180
     */
181
    public function __construct($config, $auth_type='Hm_Auth_DB') {
42✔
182
        $this->site_config = $config;
42✔
183
        $this->auth_class = $auth_type;
42✔
184
        $this->internal_users = $auth_type::$internal_users;
42✔
185
    }
186

187
    /**
188
     * Lazy loader for the auth mech so modules can define their own
189
     * overrides
190
     * @return void
191
     */
192
    protected function load_auth_mech() {
4✔
193
        if (!is_object($this->auth_mech)) {
4✔
194
            $this->auth_mech = new $this->auth_class($this->site_config);
4✔
195
        }
196
    }
197

198
    /**
199
     * Dump current session contents
200
     * @return array
201
     */
202
    public function dump() {
1✔
203
        return $this->data;
1✔
204
    }
205

206
    /**
207
     * Method called on a new login
208
     * @return void
209
     */
210
    protected function just_started() {
2✔
211
        $this->set('login_time', time());
2✔
212
    }
213

214
    /**
215
     * Record session level changes not yet saved in persistant storage
216
     * @param string $value short description of the unsaved value
217
     * @return void
218
     */
219
    public function record_unsaved($value) {
1✔
220
        $this->data['changed_settings'][] = $value;
1✔
221
    }
222

223
    /**
224
     * Returns bool true if the session is active
225
     * @return bool
226
     */
227
    public function is_active() {
25✔
228
        return $this->active;
25✔
229
    }
230

231
    /**
232
     * Returns bool true if the user is an admin
233
     * @return bool
234
     */
235
    public function is_admin() {
1✔
236
        if (!$this->active) {
1✔
237
            return false;
1✔
238
        }
239
        $admins = array_filter(explode(',', $this->site_config->get('admin_users', '')));
1✔
240
        if (empty($admins)) {
1✔
241
            return false;
1✔
242
        }
243
        $user = $this->get('username', '');
1✔
244
        if (!mb_strlen($user)) {
1✔
245
            return false;
1✔
246
        }
247
        return in_array($user, $admins, true);
1✔
248
    }
249

250
    /**
251
     * Encrypt session data
252
     * @param array $data session data to encrypt
253
     * @return string encrypted session data
254
     */
255
    public function ciphertext($data) {
13✔
256
        return Hm_Crypt::ciphertext(Hm_transform::stringify($data), $this->enc_key);
13✔
257
    }
258

259
    /**
260
     * Decrypt session data
261
     * @param string $data encrypted session data
262
     * @return false|array decrpted session data
263
     */
264
    public function plaintext($data) {
10✔
265
        return Hm_transform::unstringify(Hm_Crypt::plaintext($data, $this->enc_key));
10✔
266
    }
267

268
    /**
269
     * Set the session level encryption key
270
     * @param Hm_Request $request request details
271
     * @return void
272
     */
273
    protected function set_key($request) {
2✔
274
        $this->enc_key = Hm_Crypt::unique_id();
2✔
275
        $this->secure_cookie($request, 'hm_id', $this->enc_key, '', '', 'Lax');
2✔
276
    }
277

278
    /**
279
     * Fetch the current encryption key
280
     * @param object $request request details
281
     * @return void
282
     */
283
    public function get_key($request) {
3✔
284
        if (array_key_exists('hm_id', $request->cookie)) {
3✔
285
            $this->enc_key = $request->cookie['hm_id'];
1✔
286
        }
287
        else {
288
            Hm_Debug::add('Unable to get session encryption key');
2✔
289
        }
290
    }
291

292
    /**
293
     * @param Hm_Request $request request object
294
     * @return string
295
     */
296
    private function cookie_domain($request) {
33✔
297
        $domain = $this->site_config->get('cookie_domain', false);
33✔
298
        if ($domain == 'none') {
33✔
299
            return '';
1✔
300
        }
301
        if (!$domain && array_key_exists('HTTP_X_FORWARDED_HOST', $request->server)) {
33✔
302
            $domain = trim(explode(',', $request->server['HTTP_X_FORWARDED_HOST'])[0]);
1✔
303
        }
304
        if (!$domain && array_key_exists('HTTP_HOST', $request->server)) {
33✔
305
            $domain = $request->server['HTTP_HOST'];
3✔
306
        }
307
        if ($domain && preg_match('/:\d+$/', $domain, $matches)) {
33✔
NEW
308
            $domain = str_replace($matches[0], '', $domain);
×
309
        }
310
        return $domain;
33✔
311
    }
312

313
    /**
314
     * @param Hm_Request $request request object
315
     * @return string
316
     */
317
    private function cookie_path($request) {
33✔
318
        $path = $this->site_config->get('cookie_path', false);
33✔
319
        if ($path == 'none') {
33✔
320
            $path = '';
1✔
321
        }
322
        if (!$path) {
33✔
323
            $path = $request->path;
33✔
324
        }
325
        return $path;
33✔
326
    }
327

328

329
    /**
330
     * Set a cookie, secure if possible
331
     * @param object $request request details
332
     * @param string $name cookie name
333
     * @param string $value cookie value
334
     * @param string $path cookie path
335
     * @param string $domain cookie domain
336
     * @param string $same_site cookie SameSite
337
     * @return boolean
338
     */
339
    public function secure_cookie($request, $name, $value, $path='', $domain='', $same_site = 'Strict') {
10✔
340
        list($path, $domain, $html_only) = $this->prep_cookie_params($request, $name, $path, $domain);
10✔
341
        return Hm_Functions::setcookie($name, $value, $this->lifetime, $path, $domain, $request->tls, $html_only, $same_site);
10✔
342
    }
343

344
    /**
345
     * Prep cookie paramaters
346
     * @param object $request request details
347
     * @param string $name cookie name
348
     * @param string $path cookie path
349
     * @param string $domain cookie domain
350
     * @return array
351
     */
352
    private function prep_cookie_params($request, $name, $path, $domain) {
33✔
353
        $html_only = true;
33✔
354
        if ($name == 'hm_reload_folders') {
33✔
355
            $html_only = false;
28✔
356
        }
357
        if ($name != 'hm_reload_folders' && !$path && isset($request->path)) {
33✔
358
            $path = $this->cookie_path($request);
33✔
359
        }
360
        if (!$domain) {
33✔
361
            $domain = $this->cookie_domain($request);
33✔
362
        }
363
        if (preg_match("/:\d+$/", $domain, $matches)) {
33✔
364
            $domain = str_replace($matches[0], '', $domain);
1✔
365
        }
366
        return [$path, $domain, $html_only];
33✔
367
    }
368

369
    /**
370
     * Delete a cookie
371
     * @param object $request request details
372
     * @param string $name cookie name
373
     * @param string $path cookie path
374
     * @param string $domain cookie domain
375
     * @return boolean
376
     */
377
    public function delete_cookie($request, $name, $path='', $domain='') {
31✔
378
        list($path, $domain, $html_only) = $this->prep_cookie_params($request, $name, $path, $domain);
31✔
379
        return Hm_Functions::setcookie($name, '', time()-3600, $path, $domain, $request->tls, $html_only);
31✔
380
    }
381
}
382

383

384
/**
385
 * Setup the session and authentication classes based on the site config
386
 */
387
class Hm_Session_Setup {
388

389
    private $config;
390
    private $auth_type;
391
    private $session_type;
392

393
    /**
394
     * @param object $config site configuration
395
     */
396
    public function __construct($config) {
7✔
397
        $this->config = $config;
7✔
398
        $this->auth_type = $config->get('auth_type', false);
7✔
399
        $this->session_type = $config->get('session_type', false);
7✔
400

401
    }
402

403
    /**
404
     * @return object
405
     */
406
    public function setup_session() {
7✔
407
        $auth_class = $this->setup_auth();
7✔
408
        $session_class = $this->get_session_class();
7✔
409
        if (!Hm_Functions::class_exists($auth_class)) {
7✔
410
            Hm_Functions::cease('Invalid auth configuration');
6✔
411
        }
412
        Hm_Debug::add(sprintf('Using %s with %s', $session_class, $auth_class), 'info');
7✔
413
        return new $session_class($this->config, $auth_class);
7✔
414
    }
415

416
    /**
417
     * @return string
418
     */
419
    private function get_session_class() {
7✔
420
        switch ($this->session_type) {
7✔
421
            case 'DB':
7✔
422
                $session_class = 'Hm_DB_Session';
1✔
423
                break;
1✔
424
            case 'MEM':
7✔
425
                $session_class = 'Hm_Memcached_Session';
1✔
426
                break;
1✔
427
            case 'REDIS':
7✔
428
                $session_class = 'Hm_Redis_Session';
1✔
429
                break;
1✔
430
            case 'custom':
7✔
431
                $session_class = $this->config->get('session_class', 'Custom_Session');
1✔
432
                break;
1✔
433
        }
434
        return (isset($session_class) && class_exists($session_class))
7✔
435
             ? $session_class
1✔
436
             : 'Hm_PHP_Session';
7✔
437
    }
438

439
    /**
440
     * @return string
441
     */
442
    private function setup_auth() {
7✔
443
        $auth_class = $this->standard_auth();
7✔
444
        if ($auth_class === false) {
7✔
445
            $auth_class = $this->dynamic_auth();
7✔
446
        }
447
        if ($auth_class === false) {
7✔
448
            $auth_class = $this->custom_auth();
7✔
449
        }
450
        if ($auth_class === false) {
7✔
451
            Hm_Functions::cease('Invalid auth configuration');
7✔
452
            $auth_class = 'Hm_Auth_None';
7✔
453
        }
454
        return $auth_class;
7✔
455
    }
456

457
    /**
458
     * @return string|false
459
     */
460
    private function dynamic_auth() {
7✔
461
        if ($this->auth_type == 'dynamic' && in_array('dynamic_login', $this->config->get_modules(), true)) {
7✔
462
            return 'Hm_Auth_Dynamic';
1✔
463
        }
464
        return false;
7✔
465
    }
466

467
    /**
468
     * @return string|false
469
     */
470
    private function standard_auth() {
7✔
471
        if ($this->auth_type && in_array($this->auth_type, ['DB', 'LDAP', 'IMAP'], true)) {
7✔
472
            return sprintf('Hm_Auth_%s', $this->auth_type);
1✔
473
        }
474
        return false;
7✔
475
    }
476

477
    /**
478
     * @return string|false
479
     */
480
    private function custom_auth() {
7✔
481
        $custom_auth_class = $this->config->get('auth_class', 'Custom_Auth');
7✔
482
        if ($this->auth_type == 'custom' && Hm_Functions::class_exists($custom_auth_class)) {
7✔
483
            return $custom_auth_class;
1✔
484
        }
485
        return false;
7✔
486
    }
487
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc