Difference between revisions for Users / Eo Ny




← Previous edit
Next edit →

Version1 Version2 Differences
36 36
37 37 ==== Session Data Storage ====
38 38 Session data is stored as an array accessible through ##ArrayObject## interface:
39   %%php
  39 %%(hl php)
40 40 $session['user_id'] = 123; // Set data
41 41 echo $session['user_id']; // Get data
42 42 %%
112 112 All configuration properties are prefixed with ##cf_## (config) and can be set before calling ##start()##:
113 113
114 114 ===== Session Behavior =====
115   %%php
  115 %%(hl php)
116 116 $session->cf_static = 0; // Disable regenerations (e.g., for CAPTCHA)
117 117 $session->cf_max_session = 7200; // Max session lifetime (seconds)
118 118 $session->cf_max_idle = 1440; // Max idle time before destruction (seconds)
121 121 %%
122 122
123 123 ===== Nonce & Replay Protection =====
124   %%php
  124 %%(hl php)
125 125 $session->cf_secret = 'adyaiD9+255JeiskPybgisby'; // Secret for nonce generation
126 126 $session->cf_nonce_lifetime = 7200; // Nonce expiration (seconds)
127 127 $session->cf_prevent_replay = 1; // Enable replay attack prevention
128 128 %%
129 129
130 130 ===== Garbage Collection =====
131   %%php
  131 %%(hl php)
132 132 $session->cf_gc_probability = 2; // Probability of GC on shutdown (0-100)
133 133 $session->cf_gc_maxlifetime = 1440; // Max session file lifetime (seconds)
134 134 %%
135 135
136 136 ===== Cookie Settings =====
137   %%php
  137 %%(hl php)
138 138 $session->cf_cookie_prefix = ''; // Prefix for all cookies
139 139 $session->cf_cookie_persistent = false; // Make cookies persistent
140 140 $session->cf_cookie_lifetime = 0; // Cookie lifetime (0 = session cookie)
146 146 %%
147 147
148 148 ===== Cache Control =====
149   %%php
  149 %%(hl php)
150 150 $session->cf_cache_limiter = 'none'; // Cache control mode (public|private|nocache|none)
151 151 $session->cf_cache_expire = 180*60; // Cache TTL (seconds)
152 152 $session->cf_cache_mtime = 0; // Modify time for Last-Modified header
153 153 %%
154 154
155 155 ===== Security Validation =====
156   %%php
  156 %%(hl php)
157 157 $session->cf_referer_check = ''; // Check HTTP Referer header
158 158 %%
159 159
160 160 ===== HTTP Context (Set by HTTP class) =====
161   %%php
  161 %%(hl php)
162 162 $session->cf_ip; // Client IP address
163 163 $session->cf_tls; // TLS/SSL connection indicator
164 164 %%
169 169
170 170 ==== Basic Session Setup ====
171 171
172   %%php
  172 %%(hl php)
173 173 // Create a concrete session implementation
174 174 class MySession extends Session {
175 175     // Implement abstract store_* methods
202 202
203 203 ==== Session Data Access ====
204 204
205   %%php
  205 %%(hl php)
206 206 // Array-like access (via ArrayObject)
207 207 $session['user_id'] = 123;
208 208 echo $session['user_id'];
215 215
216 216 ==== Session ID Management ====
217 217
218   %%php
  218 %%(hl php)
219 219 // Get current session ID
220 220 $id = $session->id(); // Returns: e.g., "abc123xyz..."
221 221
228 228
229 229 ==== Session State ====
230 230
231   %%php
  231 %%(hl php)
232 232 // Check if session is active
233 233 if ($session->active()) {
234 234     // Session is running
256 256   - Session validation failures
257 257
258 258 **Manual Trigger:**
259   %%php
  259 %%(hl php)
260 260 $session->regenerate_id($delete_old = false, $message = 'custom_reason');
261 261 %%
262 262
274 274   - Single regeneration per request (checked via ##$this->regenerated## flag)
275 275   - Logged in ##sticky__log## for debugging (max 15 entries)
276 276
277   %%php
  277 %%(hl php)
278 278 // Example: Force regeneration on login
279 279 $session->start('myapp');
280 280 if ($user_authenticated) {
294 294   - Useful against bot attacks or stolen sessions
295 295
296 296 **Configuration:**
297   %%php
  297 %%(hl php)
298 298 // Automatic on each request (if enabled in code logic)
299 299 // Triggers session destruction if UA changes significantly
300 300 %%
310 310   - Tracks IP changes in ##sticky__ip##
311 311
312 312 **Configuration:**
313   %%php
  313 %%(hl php)
314 314 $session->cf_ip = $_SERVER['REMOTE_ADDR']; // Set by HTTP class
315 315 // Validation happens automatically during start()
316 316 %%
317 317
318 318 **IP Change Tracking:**
319   %%php
  319 %%(hl php)
320 320 // Access IP change history
321 321 $ip_history = $session->sticky__ip; // Array of [ip => change_count]
322 322 %%
330 330   - Destroys session on mismatch
331 331
332 332 **Configuration:**
333   %%php
  333 %%(hl php)
334 334 $session->cf_tls = !empty($_SERVER['HTTPS']); // Set by HTTP class
335 335 // Validation happens automatically during start()
336 336 %%
345 345   - Detects rapid-fire requests (AJAX attacks)
346 346
347 347 **Configuration:**
348   %%php
  348 %%(hl php)
349 349 $session->cf_prevent_replay = 1; // Enable (default)
350 350 $session->cf_prevent_replay = 0; // Disable if needed
351 351 %%
362 362 **Purpose:** Prevent CSRF via header checking
363 363
364 364 **Configuration:**
365   %%php
  365 %%(hl php)
366 366 $session->cf_referer_check = 'example.com';
367 367 // Session rejected if HTTP_REFERER doesn't contain this string
368 368 %%
391 391   - May trigger session ID regeneration
392 392
393 393 **Example:**
394   %%php
  394 %%(hl php)
395 395 if ($session->start('webapp', $_COOKIE['sess_id'] ?? null)) {
396 396     // Session ready
397 397 } else {
421 421   - Sets ##$active = false##
422 422
423 423 **Example:**
424   %%php
  424 %%(hl php)
425 425 $session['key'] = 'value';
426 426 $session->write_close(); // Ensure data is saved
427 427 %%
441 441   - Complete session refresh
442 442
443 443 **Example:**
444   %%php
  444 %%(hl php)
445 445 $session->restart();
446 446 // New session created, old data cleared, sticky_ vars preserved
447 447 %%
455 455
456 456 **Returns:** Session ID string or null if not started
457 457
458   %%php
  458 %%(hl php)
459 459 $sid = $session->id(); // "abc123xyz..."
460 460 %%
461 461
466 466
467 467 **Returns:** Session name
468 468
469   %%php
  469 %%(hl php)
470 470 $name = $session->name(); // "myapp"
471 471 %%
472 472
477 477
478 478 **Returns:** ##true## if session is started and active, ##false## otherwise
479 479
480   %%php
  480 %%(hl php)
481 481 if ($session->active()) {
482 482     $session['key'] = 'value';
483 483 }
503 503   - ##null##: No state change
504 504
505 505 **Example:**
506   %%php
  506 %%(hl php)
507 507 $session->start('app');
508 508 if ($message = $session->message()) {
509 509     error_log("Session issue: $message");
519 519
520 520 **Note:** This is a direct call to ##ArrayObject::getArrayCopy()##
521 521
522   %%php
  522 %%(hl php)
523 523 $data = $session->toArray();
524 524 foreach ($data as $key => $value) {
525 525     echo "$key => $value\n";
540 540 **Returns:** Nonce token string (11 characters)
541 541
542 542 **Example:**
543   %%php
  543 %%(hl php)
544 544 $nonce = $session->create_nonce('form_submit', 3600);
545 545 // Use in HTML: <input type="hidden" name="nonce" value="<?= $nonce ?>">
546 546 %%
568 568   - ##-1##: Protected nonce used twice in quick succession (possible AJAX attack)
569 569
570 570 **Example:**
571   %%php
  571 %%(hl php)
572 572 if ($nonce = $session->verify_nonce('form_submit', $_POST['nonce'])) {
573 573     if ($nonce === -1) {
574 574         // Possible replay, but might be legitimate AJAX
611 611   - Does NOT replace existing cookies (allows multiple Set-Cookie headers)
612 612
613 613 **Example:**
614   %%php
  614 %%(hl php)
615 615 // Session cookie
616 616 $session->setcookie('user_pref', 'dark_mode');
617 617
636 636
637 637 **Returns:** Cookie value or null if not set
638 638
639   %%php
  639 %%(hl php)
640 640 $value = $session->get_cookie('user_pref'); // Reads $_COOKIE['user_pref']
641 641 %%
642 642
654 654   - ##0##: Use ##cf_cookie_persistent## config
655 655
656 656 **Example:**
657   %%php
  657 %%(hl php)
658 658 $session->set_cookie('theme', 'dark'); // Session cookie
659 659 $session->set_cookie('lang', 'en', 365); // 1 year
660 660 %%
669 669
670 670 **Implementation:** Sets empty value with immediate expiration
671 671
672   %%php
  672 %%(hl php)
673 673 $session->delete_cookie('old_preference');
674 674 %%
675 675
678 678 ====== ##unsetcookie($name): void## ======
679 679 Alias for ##setcookie($name)## with no value (convenience method).
680 680
681   %%php
  681 %%(hl php)
682 682 $session->unsetcookie('cookie_name');
683 683 %%
684 684
699 699 **Default Implementation:** Returns 21-character random alphanumeric string via ##Ut::random_token(21)##
700 700
701 701 **Override in subclass to customize:**
702   %%php
  702 %%(hl php)
703 703 protected function store_generate_id(): string {
704 704     return hash('sha256', random_bytes(32)); // Your format
705 705 }
713 713 **Default Implementation:** Regex check: ##/^[a-zA-Z\d]{21}$/##
714 714
715 715 **Override in subclass to match your format:**
716   %%php
  716 %%(hl php)
717 717 protected function store_validate_id($id): bool {
718 718     return preg_match('/^[a-f0-9]{64}$/', $id); // SHA256 format
719 719 }
727 727 **Subclass must implement** - Initialize storage handler
728 728
729 729 **Example:**
730   %%php
  730 %%(hl php)
731 731 protected function store_open($name): void {
732 732     $this->db = new PDO('sqlite::memory:');
733 733 }
750 750   - ##false## if session doesn't exist or read error
751 751
752 752 **Example:**
753   %%php
  753 %%(hl php)
754 754 protected function store_read($id, $lock = false): string|false {
755 755     $data = file_get_contents("/tmp/sess_$id");
756 756     return $data ?: false;
769 769   - ##$data##: Serialized session data (already processed by ##Ut::serialize()##)
770 770
771 771 **Example:**
772   %%php
  772 %%(hl php)
773 773 protected function store_write($id, $data): void {
774 774     file_put_contents("/tmp/sess_$id", $data);
775 775 }
783 783 **Subclass must implement** - Release resources
784 784
785 785 **Example:**
786   %%php
  786 %%(hl php)
787 787 protected function store_close(): void {
788 788     // Close database, file, etc.
789 789 }
803 803   - Sessions older than ##cf_gc_maxlifetime## seconds
804 804
805 805 **Example:**
806   %%php
  806 %%(hl php)
807 807 protected function store_gc(): void {
808 808     $max_age = time() - $this->cf_gc_maxlifetime;
809 809     // Delete files/records older than $max_age
1038 1038
1039 1039 ==== Usage ====
1040 1040
1041   %%php
  1041 %%(hl php)
1042 1042 // Store flash message for next request
1043 1043 $session->set_flash('error', 'Username already exists', 1); // 1 request
1044 1044 $session->set_flash('info', 'Welcome back!', 2); // 2 requests
1052 1052   - Key: Variable name
1053 1053   - Value: Lifetime in requests
1054 1054   2. **Cleanup:** In ##terminator()## (shutdown handler):
1055      %%php
  1055    %%(hl php)
1056 1056    foreach ($sticky__flash as $var => $age) {
1057 1057        if (!isset($session[$var])) {
1058 1058            unset($sticky__flash[$var]); // Already deleted
1068 1068
1069 1069 ==== Example: Login Flow ====
1070 1070
1071   %%php
  1071 %%(hl php)
1072 1072 // POST /login
1073 1073 if ($credentials_valid) {
1074 1074     $session->restart(); // New session
1102 1102
1103 1103 ==== Complete Example: Form Protection ====
1104 1104
1105   %%php
  1105 %%(hl php)
1106 1106 // 1. Display form with nonce
1107 1107 $nonce = $session->create_nonce('user_update', 3600);
1108 1108 ?>
1127 1127
1128 1128 ==== Example: Protected Nonce (AJAX-Safe) ====
1129 1129
1130   %%php
  1130 %%(hl php)
1131 1131 // Generate protected nonce (can verify multiple times)
1132 1132 $nonce = $session->create_nonce('ajax_action', 300);
1133 1133
1181 1181 The ##setcookie()## method implements comprehensive cookie security:
1182 1182
1183 1183 ===== Encoding =====
1184   %%php
  1184 %%(hl php)
1185 1185 // Cookie names: RFC 2616 2.2 token format
1186 1186 // Cookie values: RFC 6265 4.1.1 cookie-octet format
1187 1187 // Unsafe characters automatically URL-encoded
1188 1188 %%
1189 1189
1190 1190 ===== Security Attributes =====
1191   %%php
  1191 %%(hl php)
1192 1192 setcookie('auth', 'token',
1193 1193     expires: time() + 3600,
1194 1194     secure: true, // HTTPS only
1198 1198 %%
1199 1199
1200 1200 ===== No Duplicate Headers =====
1201   %%php
  1201 %%(hl php)
1202 1202 // Automatically removes old Set-Cookie header before setting new one
1203 1203 // Prevents cookie header duplication
1204 1204 remove_cookie($name) → clears old headers
1207 1207
1208 1208 ==== Configuration-Driven Defaults ====
1209 1209
1210   %%php
  1210 %%(hl php)
1211 1211 $session->cf_cookie_path = '/app'; // Path
1212 1212 $session->cf_cookie_domain = '.example.com'; // Domain
1213 1213 $session->cf_cookie_secure = true; // HTTPS
1221 1221
1222 1222 ==== Typical Secure Configuration ====
1223 1223
1224   %%php
  1224 %%(hl php)
1225 1225 // Prevent XSS and CSRF
1226 1226 $session->cf_cookie_secure = true; // HTTPS only
1227 1227 $session->cf_cookie_httponly = true; // No JavaScript access
1245 1245 The Session class gracefully handles errors:
1246 1246
1247 1247 ===== Headers Already Sent =====
1248   %%php
  1248 %%(hl php)
1249 1249 if (headers_sent($file, $line)) {
1250 1250     trigger_error("id regeneration requested after headers flushed at $file:$line",
1251 1251                   E_USER_WARNING);
1256 1256 **Impact:** Session ID cannot be regenerated, but session continues
1257 1257
1258 1258 ===== Cookie Setting Failure =====
1259   %%php
  1259 %%(hl php)
1260 1260 if (headers_sent($file, $line)) {
1261 1261     trigger_error("cannot place session cookie $name=$value due to $file:$line",
1262 1262                   E_USER_WARNING);
1267 1267 **Impact:** Cookie not set, but session data remains accessible
1268 1268
1269 1269 ===== Storage Errors =====
1270   %%php
  1270 %%(hl php)
1271 1271 if ($this->store_read($this->id, true) !== '') {
1272 1272     // error! [comment indicates error, but continues]
1273 1273 }
1279 1279
1280 1280 The Session class includes commented debug statements:
1281 1281
1282   %%php
  1282 %%(hl php)
1283 1283 # Ut::dbg("regeneration failed by flush at $file:$line");
1284 1284 # Ut::dbg($destroy, $message);
1285 1285 # Ut::dbg("session setcookie $name failed by $file:$line");
1291 1291
1292 1292 Session events tracked in ##sticky__log##:
1293 1293
1294   %%php
  1294 %%(hl php)
1295 1295 // Access session event history
1296 1296 if (isset($session->sticky__log)) {
1297 1297     foreach ($session->sticky__log as [$timestamp, $message]) {
1314 1314
1315 1315 ===== File-Based Storage =====
1316 1316
1317   %%php
  1317 %%(hl php)
1318 1318 <?php
1319 1319
1320 1320 class FileSession extends Session {
1375 1375
1376 1376 ===== Database Storage (PDO) =====
1377 1377
1378   %%php
  1378 %%(hl php)
1379 1379 <?php
1380 1380
1381 1381 class DatabaseSession extends Session {
1443 1443
1444 1444 ===== Redis Storage =====
1445 1445
1446   %%php
  1446 %%(hl php)
1447 1447 <?php
1448 1448
1449 1449 class RedisSession extends Session {
1493 1493
1494 1494 ==== Complete Integration Example ====
1495 1495
1496   %%php
  1496 %%(hl php)
1497 1497 <?php
1498 1498
1499 1499 // Initialize session with configuration
1543 1543
1544 1544 ==== Configuration Best Practices ====
1545 1545
1546   %%php
  1546 %%(hl php)
1547 1547 <?php
1548 1548
1549 1549 class SessionConfig {
1584 1584
1585 1585 ==== Testing Tips ====
1586 1586
1587   %%php
  1587 %%(hl php)
1588 1588 <?php
1589 1589
1590 1590 // Test nonce generation and verification
1643 1643
1644 1644 ==== Login Flow ====
1645 1645
1646   %%php
  1646 %%(hl php)
1647 1647 if ($_POST['action'] === 'login') {
1648 1648     $user = authenticate($_POST['username'], $_POST['password']);
1649 1649     if ($user) {
1661 1661
1662 1662 ==== Logout Flow ====
1663 1663
1664   %%php
  1664 %%(hl php)
1665 1665 if ($_GET['action'] === 'logout') {
1666 1666     $session->restart(); // Complete reset
1667 1667     header('Location: /');
1670 1670
1671 1671 ==== CSRF-Protected Form ====
1672 1672
1673   %%php
  1673 %%(hl php)
1674 1674 // Display form
1675 1675 $csrf = $session->create_nonce('form_' . $form_id, 3600);
1676 1676 echo '<form method="POST">';
1689 1689
1690 1690 ==== Permission Check with Session Regeneration ====
1691 1691
1692   %%php
  1692 %%(hl php)
1693 1693 if ($user->privilege_level < ADMIN_LEVEL && $promoted_to_admin) {
1694 1694     $session->regenerate_id(false, 'privilege_escalation');
1695 1695     $session['is_admin'] = true;
1698 1698
1699 1699 ==== Session Messages/Flash ====
1700 1700
1701   %%php
  1701 %%(hl php)
1702 1702 // After action
1703 1703 $session->set_flash('info', 'Profile updated successfully', 1);
1704 1704
1745 1745
1746 1746 ==== Session Not Starting ====
1747 1747
1748   %%php
  1748 %%(hl php)
1749 1749 if (!$session->start('myapp')) {
1750 1750     // Check reasons:
1751 1751     // 1. Headers already sent?
1757 1757
1758 1758 ==== Cookie Not Setting ====
1759 1759
1760   %%php
  1760 %%(hl php)
1761 1761 // If setcookie() returns false:
1762 1762 // - Check if headers_sent()
1763 1763 // - Check if cookie name is RFC 2616 compliant
1766 1766
1767 1767 ==== Session ID Not Regenerating ====
1768 1768
1769   %%php
  1769 %%(hl php)
1770 1770 // If regenerate_id() returns false:
1771 1771 // - Headers might be sent
1772 1772 // - $active might be false
1778 1778
1779 1779 ==== Nonce Verification Failing ====
1780 1780
1781   %%php
  1781 %%(hl php)
1782 1782 // If verify_nonce() returns false:
1783 1783 // 1. Nonce might be expired
1784 1784 // 2. Nonce might be for different action
1791 1791
1792 1792 ==== Session Data Lost ====
1793 1793
1794   %%php
  1794 %%(hl php)
1795 1795 // Possible causes:
1796 1796 // 1. write_close() not called (usually automatic via shutdown)
1797 1797 // 2. Storage backend failing silently