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
4 === Overview ===
5
6 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.
37
38 ==== Session Data Storage ====
39 Session data is stored as an array accessible through ##ArrayObject## interface:
40 %%php(hl php)
41 $session['user_id'] = 123; // Set data
42 echo $session['user_id']; // Get data
43 %%
113 All configuration properties are prefixed with ##cf_## (config) and can be set before calling ##start()##:
114
115 ===== Session Behavior =====
116 %%php(hl php)
117 $session->cf_static = 0; // Disable regenerations (e.g., for CAPTCHA)
118 $session->cf_max_session = 7200; // Max session lifetime (seconds)
119 $session->cf_max_idle = 1440; // Max idle time before destruction (seconds)
122 %%
123
124 ===== Nonce & Replay Protection =====
125 %%php(hl php)
126 $session->cf_secret = 'adyaiD9+255JeiskPybgisby'; // Secret for nonce generation
127 $session->cf_nonce_lifetime = 7200; // Nonce expiration (seconds)
128 $session->cf_prevent_replay = 1; // Enable replay attack prevention
129 %%
130
131 ===== Garbage Collection =====
132 %%php(hl php)
133 $session->cf_gc_probability = 2; // Probability of GC on shutdown (0-100)
134 $session->cf_gc_maxlifetime = 1440; // Max session file lifetime (seconds)
135 %%
136
137 ===== Cookie Settings =====
138 %%php(hl php)
139 $session->cf_cookie_prefix = ''; // Prefix for all cookies
140 $session->cf_cookie_persistent = false; // Make cookies persistent
141 $session->cf_cookie_lifetime = 0; // Cookie lifetime (0 = session cookie)
147 %%
148
149 ===== Cache Control =====
150 %%php(hl php)
151 $session->cf_cache_limiter = 'none'; // Cache control mode (public|private|nocache|none)
152 $session->cf_cache_expire = 180*60; // Cache TTL (seconds)
153 $session->cf_cache_mtime = 0; // Modify time for Last-Modified header
154 %%
155
156 ===== Security Validation =====
157 %%php(hl php)
158 $session->cf_referer_check = ''; // Check HTTP Referer header
159 %%
160
161 ===== HTTP Context (Set by HTTP class) =====
162 %%php(hl php)
163 $session->cf_ip; // Client IP address
164 $session->cf_tls; // TLS/SSL connection indicator
165 %%
170
171 ==== Basic Session Setup ====
172
173 %%php(hl php)
174 // Create a concrete session implementation
175 class MySession extends Session {
176     // Implement abstract store_* methods
203
204 ==== Session Data Access ====
205
206 %%php(hl php)
207 // Array-like access (via ArrayObject)
208 $session['user_id'] = 123;
209 echo $session['user_id'];
216
217 ==== Session ID Management ====
218
219 %%php(hl php)
220 // Get current session ID
221 $id = $session->id(); // Returns: e.g., "abc123xyz..."
222
229
230 ==== Session State ====
231
232 %%php(hl php)
233 // Check if session is active
234 if ($session->active()) {
235     // Session is running
257   - Session validation failures
258
259 **Manual Trigger:**
260 %%php(hl php)
261 $session->regenerate_id($delete_old = false, $message = 'custom_reason');
262 %%
263
275   - Single regeneration per request (checked via ##$this->regenerated## flag)
276   - Logged in ##sticky__log## for debugging (max 15 entries)
277
278 %%php(hl php)
279 // Example: Force regeneration on login
280 $session->start('myapp');
281 if ($user_authenticated) {
295   - Useful against bot attacks or stolen sessions
296
297 **Configuration:**
298 %%php(hl php)
299 // Automatic on each request (if enabled in code logic)
300 // Triggers session destruction if UA changes significantly
301 %%
311   - Tracks IP changes in ##sticky__ip##
312
313 **Configuration:**
314 %%php(hl php)
315 $session->cf_ip = $_SERVER['REMOTE_ADDR']; // Set by HTTP class
316 // Validation happens automatically during start()
317 %%
318
319 **IP Change Tracking:**
320 %%php(hl php)
321 // Access IP change history
322 $ip_history = $session->sticky__ip; // Array of [ip => change_count]
323 %%
331   - Destroys session on mismatch
332
333 **Configuration:**
334 %%php(hl php)
335 $session->cf_tls = !empty($_SERVER['HTTPS']); // Set by HTTP class
336 // Validation happens automatically during start()
337 %%
346   - Detects rapid-fire requests (AJAX attacks)
347
348 **Configuration:**
349 %%php(hl php)
350 $session->cf_prevent_replay = 1; // Enable (default)
351 $session->cf_prevent_replay = 0; // Disable if needed
352 %%
363 **Purpose:** Prevent CSRF via header checking
364
365 **Configuration:**
366 %%php(hl php)
367 $session->cf_referer_check = 'example.com';
368 // Session rejected if HTTP_REFERER doesn't contain this string
369 %%
392   - May trigger session ID regeneration
393
394 **Example:**
395 %%php(hl php)
396 if ($session->start('webapp', $_COOKIE['sess_id'] ?? null)) {
397     // Session ready
398 } else {
422   - Sets ##$active = false##
423
424 **Example:**
425 %%php(hl php)
426 $session['key'] = 'value';
427 $session->write_close(); // Ensure data is saved
428 %%
442   - Complete session refresh
443
444 **Example:**
445 %%php(hl php)
446 $session->restart();
447 // New session created, old data cleared, sticky_ vars preserved
448 %%
456
457 **Returns:** Session ID string or null if not started
458
459 %%php(hl php)
460 $sid = $session->id(); // "abc123xyz..."
461 %%
462
467
468 **Returns:** Session name
469
470 %%php(hl php)
471 $name = $session->name(); // "myapp"
472 %%
473
478
479 **Returns:** ##true## if session is started and active, ##false## otherwise
480
481 %%php(hl php)
482 if ($session->active()) {
483     $session['key'] = 'value';
484 }
504   - ##null##: No state change
505
506 **Example:**
507 %%php(hl php)
508 $session->start('app');
509 if ($message = $session->message()) {
510     error_log("Session issue: $message");
520
521 **Note:** This is a direct call to ##ArrayObject::getArrayCopy()##
522
523 %%php(hl php)
524 $data = $session->toArray();
525 foreach ($data as $key => $value) {
526     echo "$key => $value\n";
541 **Returns:** Nonce token string (11 characters)
542
543 **Example:**
544 %%php(hl php)
545 $nonce = $session->create_nonce('form_submit', 3600);
546 // Use in HTML: <input type="hidden" name="nonce" value="<?= $nonce ?>">
547 %%
569   - ##-1##: Protected nonce used twice in quick succession (possible AJAX attack)
570
571 **Example:**
572 %%php(hl php)
573 if ($nonce = $session->verify_nonce('form_submit', $_POST['nonce'])) {
574     if ($nonce === -1) {
575         // Possible replay, but might be legitimate AJAX
612   - Does NOT replace existing cookies (allows multiple Set-Cookie headers)
613
614 **Example:**
615 %%php(hl php)
616 // Session cookie
617 $session->setcookie('user_pref', 'dark_mode');
618
637
638 **Returns:** Cookie value or null if not set
639
640 %%php(hl php)
641 $value = $session->get_cookie('user_pref'); // Reads $_COOKIE['user_pref']
642 %%
643
655   - ##0##: Use ##cf_cookie_persistent## config
656
657 **Example:**
658 %%php(hl php)
659 $session->set_cookie('theme', 'dark'); // Session cookie
660 $session->set_cookie('lang', 'en', 365); // 1 year
661 %%
670
671 **Implementation:** Sets empty value with immediate expiration
672
673 %%php(hl php)
674 $session->delete_cookie('old_preference');
675 %%
676
679 ====== ##unsetcookie($name): void## ======
680 Alias for ##setcookie($name)## with no value (convenience method).
681
682 %%php(hl php)
683 $session->unsetcookie('cookie_name');
684 %%
685
700 **Default Implementation:** Returns 21-character random alphanumeric string via ##Ut::random_token(21)##
701
702 **Override in subclass to customize:**
703 %%php(hl php)
704 protected function store_generate_id(): string {
705     return hash('sha256', random_bytes(32)); // Your format
706 }
714 **Default Implementation:** Regex check: ##/^[a-zA-Z\d]{21}$/##
715
716 **Override in subclass to match your format:**
717 %%php(hl php)
718 protected function store_validate_id($id): bool {
719     return preg_match('/^[a-f0-9]{64}$/', $id); // SHA256 format
720 }
728 **Subclass must implement** - Initialize storage handler
729
730 **Example:**
731 %%php(hl php)
732 protected function store_open($name): void {
733     $this->db = new PDO('sqlite::memory:');
734 }
751   - ##false## if session doesn't exist or read error
752
753 **Example:**
754 %%php(hl php)
755 protected function store_read($id, $lock = false): string|false {
756     $data = file_get_contents("/tmp/sess_$id");
757     return $data ?: false;
770   - ##$data##: Serialized session data (already processed by ##Ut::serialize()##)
771
772 **Example:**
773 %%php(hl php)
774 protected function store_write($id, $data): void {
775     file_put_contents("/tmp/sess_$id", $data);
776 }
784 **Subclass must implement** - Release resources
785
786 **Example:**
787 %%php(hl php)
788 protected function store_close(): void {
789     // Close database, file, etc.
790 }
804   - Sessions older than ##cf_gc_maxlifetime## seconds
805
806 **Example:**
807 %%php(hl php)
808 protected function store_gc(): void {
809     $max_age = time() - $this->cf_gc_maxlifetime;
810     // Delete files/records older than $max_age
1039
1040 ==== Usage ====
1041
1042 %%php(hl php)
1043 // Store flash message for next request
1044 $session->set_flash('error', 'Username already exists', 1); // 1 request
1045 $session->set_flash('info', 'Welcome back!', 2); // 2 requests
1053   - Key: Variable name
1054   - Value: Lifetime in requests
1055   2. **Cleanup:** In ##terminator()## (shutdown handler):
1056    %%php(hl php)
1057    foreach ($sticky__flash as $var => $age) {
1058        if (!isset($session[$var])) {
1059            unset($sticky__flash[$var]); // Already deleted
1069
1070 ==== Example: Login Flow ====
1071
1072 %%php(hl php)
1073 // POST /login
1074 if ($credentials_valid) {
1075     $session->restart(); // New session
1103
1104 ==== Complete Example: Form Protection ====
1105
1106 %%php(hl php)
1107 // 1. Display form with nonce
1108 $nonce = $session->create_nonce('user_update', 3600);
1109 ?>
1128
1129 ==== Example: Protected Nonce (AJAX-Safe) ====
1130
1131 %%php(hl php)
1132 // Generate protected nonce (can verify multiple times)
1133 $nonce = $session->create_nonce('ajax_action', 300);
1134
1182 The ##setcookie()## method implements comprehensive cookie security:
1183
1184 ===== Encoding =====
1185 %%php(hl php)
1186 // Cookie names: RFC 2616 2.2 token format
1187 // Cookie values: RFC 6265 4.1.1 cookie-octet format
1188 // Unsafe characters automatically URL-encoded
1189 %%
1190
1191 ===== Security Attributes =====
1192 %%php(hl php)
1193 setcookie('auth', 'token',
1194     expires: time() + 3600,
1195     secure: true, // HTTPS only
1199 %%
1200
1201 ===== No Duplicate Headers =====
1202 %%php(hl php)
1203 // Automatically removes old Set-Cookie header before setting new one
1204 // Prevents cookie header duplication
1205 remove_cookie($name) → clears old headers
1208
1209 ==== Configuration-Driven Defaults ====
1210
1211 %%php(hl php)
1212 $session->cf_cookie_path = '/app'; // Path
1213 $session->cf_cookie_domain = '.example.com'; // Domain
1214 $session->cf_cookie_secure = true; // HTTPS
1222
1223 ==== Typical Secure Configuration ====
1224
1225 %%php(hl php)
1226 // Prevent XSS and CSRF
1227 $session->cf_cookie_secure = true; // HTTPS only
1228 $session->cf_cookie_httponly = true; // No JavaScript access
1246 The Session class gracefully handles errors:
1247
1248 ===== Headers Already Sent =====
1249 %%php(hl php)
1250 if (headers_sent($file, $line)) {
1251     trigger_error("id regeneration requested after headers flushed at $file:$line",
1252                   E_USER_WARNING);
1257 **Impact:** Session ID cannot be regenerated, but session continues
1258
1259 ===== Cookie Setting Failure =====
1260 %%php(hl php)
1261 if (headers_sent($file, $line)) {
1262     trigger_error("cannot place session cookie $name=$value due to $file:$line",
1263                   E_USER_WARNING);
1268 **Impact:** Cookie not set, but session data remains accessible
1269
1270 ===== Storage Errors =====
1271 %%php(hl php)
1272 if ($this->store_read($this->id, true) !== '') {
1273     // error! [comment indicates error, but continues]
1274 }
1280
1281 The Session class includes commented debug statements:
1282
1283 %%php(hl php)
1284 # Ut::dbg("regeneration failed by flush at $file:$line");
1285 # Ut::dbg($destroy, $message);
1286 # Ut::dbg("session setcookie $name failed by $file:$line");
1292
1293 Session events tracked in ##sticky__log##:
1294
1295 %%php(hl php)
1296 // Access session event history
1297 if (isset($session->sticky__log)) {
1298     foreach ($session->sticky__log as [$timestamp, $message]) {
1315
1316 ===== File-Based Storage =====
1317
1318 %%php(hl php)
1319 <?php
1320
1321 class FileSession extends Session {
1376
1377 ===== Database Storage (PDO) =====
1378
1379 %%php(hl php)
1380 <?php
1381
1382 class DatabaseSession extends Session {
1444
1445 ===== Redis Storage =====
1446
1447 %%php(hl php)
1448 <?php
1449
1450 class RedisSession extends Session {
1494
1495 ==== Complete Integration Example ====
1496
1497 %%php(hl php)
1498 <?php
1499
1500 // Initialize session with configuration
1544
1545 ==== Configuration Best Practices ====
1546
1547 %%php(hl php)
1548 <?php
1549
1550 class SessionConfig {
1585
1586 ==== Testing Tips ====
1587
1588 %%php(hl php)
1589 <?php
1590
1591 // Test nonce generation and verification
1644
1645 ==== Login Flow ====
1646
1647 %%php(hl php)
1648 if ($_POST['action'] === 'login') {
1649     $user = authenticate($_POST['username'], $_POST['password']);
1650     if ($user) {
1662
1663 ==== Logout Flow ====
1664
1665 %%php(hl php)
1666 if ($_GET['action'] === 'logout') {
1667     $session->restart(); // Complete reset
1668     header('Location: /');
1671
1672 ==== CSRF-Protected Form ====
1673
1674 %%php(hl php)
1675 // Display form
1676 $csrf = $session->create_nonce('form_' . $form_id, 3600);
1677 echo '<form method="POST">';
1690
1691 ==== Permission Check with Session Regeneration ====
1692
1693 %%php(hl php)
1694 if ($user->privilege_level < ADMIN_LEVEL && $promoted_to_admin) {
1695     $session->regenerate_id(false, 'privilege_escalation');
1696     $session['is_admin'] = true;
1699
1700 ==== Session Messages/Flash ====
1701
1702 %%php(hl php)
1703 // After action
1704 $session->set_flash('info', 'Profile updated successfully', 1);
1705
1713
1714 === Performance Considerations ===
1715
1716 ==== Optimization Tips Optimization Tips====
1717   1. **Minimize Session Writes:**
1718     - Session data only written during ##write_close()## or regeneration
1719     - No unnecessary serialization during reads
1720   2. **Garbage Collection:**
1721     - Probabilistic GC (based on ##cf_gc_probability##)
1722     - Only runs on ~2% of requests by default
1723     - Customize based on your session volume
1724   3. **Nonce Cleanup:**
1725     - Expired nonces automatically removed on verification
1726     - Verified nonces removed from storage
1727     - No manual cleanup needed
1728   4. **Session ID Validation:**
1729     - Regex-based validation is fast
1730     - No database lookup needed
1731   5. **Caching Strategy:**
1732     - Cache expensive lookups between session operations
1733     - Session data loaded once per request
1734
1735 ==== Benchmarks ====
1736
1746
1747 ==== Session Not Starting ====
1748
1749 %%php(hl php)
1750 if (!$session->start('myapp')) {
1751     // Check reasons:
1752     // 1. Headers already sent?
1758
1759 ==== Cookie Not Setting ====
1760
1761 %%php(hl php)
1762 // If setcookie() returns false:
1763 // - Check if headers_sent()
1764 // - Check if cookie name is RFC 2616 compliant
1767
1768 ==== Session ID Not Regenerating ====
1769
1770 %%php(hl php)
1771 // If regenerate_id() returns false:
1772 // - Headers might be sent
1773 // - $active might be false
1779
1780 ==== Nonce Verification Failing ====
1781
1782 %%php(hl php)
1783 // If verify_nonce() returns false:
1784 // 1. Nonce might be expired
1785 // 2. Nonce might be for different action
1792
1793 ==== Session Data Lost ====
1794
1795 %%php(hl php)
1796 // Possible causes:
1797 // 1. write_close() not called (usually automatic via shutdown)
1798 // 2. Storage backend failing silently
1807
1808 ----
1809
1810 ===TODO Items (From Code Comments)===
1811 The following improvements are planned:
1812   1. **Do not store session ID in filename or DB index - store hash instead**
1813     - Improves security by not exposing IDs in storage layer
1814     - Would require hashing logic in store_* methods
1815   2. **Log of IP changes and other possible security alerts**
1816     - Track ##sticky__ip## changes more comprehensively
1817     - Create security audit trail
1818   3. **Allocate internal unique session which lives through lifetime of uber-session**
1819     - Multi-session management (parent/child sessions)
1820     - Useful for complex user flows
1821   4. **Do not delete old sessions, but use them as hijack pointers**
1822     - Maintain session history for analysis
1823     - Detect potential session hijacking patterns
1824     - Implement session relationship tracking
1825   5. **All SIDs used later than ~5secs of regenerations is hijacks**
1826     - Detect and block delayed session ID usage
1827     - Current implementation allows 5-second window
1828     - Could be more granular
1829
1830 ----
1831
1852
1853 ----
1854
1855 === Version History Version History===
1856   - **Current**: Abstract session class with security features
1857   - **Planned**: Implementation of TODO items above
1858
1859 ----
1860   *Documentation generated: 2026-05-05* **Documentation generated: 2026-05-05**
1861   *For latest updates, see: https://github.com/Trojer/wackowiki/blob/main/docs/SESSION_DOCUMENTATION.md**For latest updates, see: https://github.com/Trojer/wackowiki/blob/main/docs/SESSION_DOCUMENTATION.md**