Difference between revisions for Users / Eo Ny




← Previous edit
Next edit →

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