1<?php 2 3/** 4 * Session handling 5 * @package framework 6 * @subpackage session 7 */ 8 9/** 10 * Use for browser fingerprinting 11 */ 12trait 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) { 43 if ($this->site_config->get('disable_fingerprint')) { 44 return; 45 } 46 $id = $this->build_fingerprint($request->server); 47 $fingerprint = $this->get('fingerprint', null); 48 if ($fingerprint === false) { 49 $this->set_fingerprint($request); 50 return; 51 } 52 if (!$fingerprint || $fingerprint !== $id) { 53 Hm_Debug::add('HTTP header fingerprint check failed'); 54 $this->destroy($request); 55 } 56 } 57 58 /** 59 * Browser request properties to build a fingerprint with 60 * @return array 61 */ 62 private function fingerprint_flds() { 63 $flds = array('HTTP_USER_AGENT', 'REQUEST_SCHEME', 'HTTP_ACCEPT_LANGUAGE', 64 'HTTP_ACCEPT_CHARSET', 'HTTP_HOST'); 65 if (!$this->site_config->get('allow_long_session') && !$this->site_config->get('disable_ip_check')) { 66 $flds[] = 'REMOTE_ADDR'; 67 } 68 return $flds; 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='') { 77 $id = $input; 78 foreach ($this->fingerprint_flds() as $val) { 79 $id .= (array_key_exists($val, $env)) ? $env[$val] : ''; 80 } 81 return hash('sha256', $id); 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) { 90 $id = $this->build_fingerprint($request->server); 91 $this->set('fingerprint', $id); 92 } 93} 94 95/** 96 * Base class for session management. All session interaction happens through 97 * classes that extend this. 98 * @abstract 99 */ 100abstract 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 = array(); 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') { 182 $this->site_config = $config; 183 $this->auth_class = $auth_type; 184 $this->internal_users = $auth_type::$internal_users; 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() { 193 if (!is_object($this->auth_mech)) { 194 $this->auth_mech = new $this->auth_class($this->site_config); 195 } 196 } 197 198 /** 199 * Dump current session contents 200 * @return array 201 */ 202 public function dump() { 203 return $this->data; 204 } 205 206 /** 207 * Method called on a new login 208 * @return void 209 */ 210 protected function just_started() { 211 $this->set('login_time', time()); 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) { 220 $this->data['changed_settings'][] = $value; 221 } 222 223 /** 224 * Returns bool true if the session is active 225 * @return bool 226 */ 227 public function is_active() { 228 return $this->active; 229 } 230 231 /** 232 * Returns bool true if the user is an admin 233 * @return bool 234 */ 235 public function is_admin() { 236 if (!$this->active) { 237 return false; 238 } 239 $admins = array_filter(explode(',', $this->site_config->get('admin_users', ''))); 240 if (empty($admins)) { 241 return false; 242 } 243 $user = $this->get('username', ''); 244 if (!strlen($user)) { 245 return false; 246 } 247 return in_array($user, $admins, true); 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) { 256 return Hm_Crypt::ciphertext(Hm_transform::stringify($data), $this->enc_key); 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) { 265 return Hm_transform::unstringify(Hm_Crypt::plaintext($data, $this->enc_key)); 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) { 274 $this->enc_key = Hm_Crypt::unique_id(); 275 $this->secure_cookie($request, 'hm_id', $this->enc_key); 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) { 284 if (array_key_exists('hm_id', $request->cookie)) { 285 $this->enc_key = $request->cookie['hm_id']; 286 } 287 else { 288 Hm_Debug::add('Unable to get session encryption key'); 289 } 290 } 291 292 /** 293 * @param Hm_Request $request request object 294 * @return string 295 */ 296 private function cookie_domain($request) { 297 $domain = $this->site_config->get('cookie_domain', false); 298 if ($domain == 'none') { 299 return ''; 300 } 301 if (!$domain && array_key_exists('HTTP_HOST', $request->server)) { 302 $domain = $request->server['HTTP_HOST']; 303 } 304 return $domain; 305 } 306 307 /** 308 * @param Hm_Request $request request object 309 * @return string 310 */ 311 private function cookie_path($request) { 312 $path = $this->site_config->get('cookie_path', false); 313 if ($path == 'none') { 314 $path = ''; 315 } 316 if (!$path) { 317 $path = $request->path; 318 } 319 return $path; 320 } 321 322 323 /** 324 * Set a cookie, secure if possible 325 * @param object $request request details 326 * @param string $name cookie name 327 * @param string $value cookie value 328 * @param string $path cookie path 329 * @param string $domain cookie domain 330 * @return boolean 331 */ 332 public function secure_cookie($request, $name, $value, $path='', $domain='') { 333 list($path, $domain, $html_only) = $this->prep_cookie_params($request, $name, $path, $domain); 334 return Hm_Functions::setcookie($name, $value, $this->lifetime, $path, $domain, $request->tls, $html_only); 335 } 336 337 /** 338 * Prep cookie paramaters 339 * @param object $request request details 340 * @param string $name cookie name 341 * @param string $path cookie path 342 * @param string $domain cookie domain 343 * @return array 344 */ 345 private function prep_cookie_params($request, $name, $path, $domain) { 346 $html_only = true; 347 if ($name == 'hm_reload_folders') { 348 $html_only = false; 349 } 350 if ($name != 'hm_reload_folders' && !$path && isset($request->path)) { 351 $path = $this->cookie_path($request); 352 } 353 if (!$domain) { 354 $domain = $this->cookie_domain($request); 355 } 356 if (preg_match("/:\d+$/", $domain, $matches)) { 357 $domain = str_replace($matches[0], '', $domain); 358 } 359 return array($path, $domain, $html_only); 360 } 361 362 /** 363 * Delete a cookie 364 * @param object $request request details 365 * @param string $name cookie name 366 * @param string $path cookie path 367 * @param string $domain cookie domain 368 * @return boolean 369 */ 370 public function delete_cookie($request, $name, $path='', $domain='') { 371 list($path, $domain, $html_only) = $this->prep_cookie_params($request, $name, $path, $domain); 372 return Hm_Functions::setcookie($name, '', time()-3600, $path, $domain, $request->tls, $html_only); 373 } 374} 375 376 377/** 378 * Setup the session and authentication classes based on the site config 379 */ 380class Hm_Session_Setup { 381 382 private $config; 383 private $auth_type; 384 private $session_type; 385 386 /** 387 * @param object $config site configuration 388 */ 389 public function __construct($config) { 390 $this->config = $config; 391 $this->auth_type = $config->get('auth_type', false); 392 $this->session_type = $config->get('session_type', false); 393 394 } 395 396 /** 397 * @return object 398 */ 399 public function setup_session() { 400 $auth_class = $this->setup_auth(); 401 $session_class = $this->get_session_class(); 402 if (!Hm_Functions::class_exists($auth_class)) { 403 Hm_Functions::cease('Invalid auth configuration'); 404 } 405 Hm_Debug::add(sprintf('Using %s with %s', $session_class, $auth_class)); 406 return new $session_class($this->config, $auth_class); 407 } 408 409 /** 410 * @return string 411 */ 412 private function get_session_class() { 413 $custom_session_class = $this->config->get('session_class', 'Custom_Session'); 414 if ($this->session_type == 'DB') { 415 $session_class = 'Hm_DB_Session'; 416 } 417 elseif ($this->session_type == 'MEM') { 418 $session_class = 'Hm_Memcached_Session'; 419 } 420 elseif ($this->session_type == 'REDIS') { 421 $session_class = 'Hm_Redis_Session'; 422 } 423 elseif ($this->session_type == 'custom' && class_exists($custom_session_class)) { 424 $session_class = $custom_session_class; 425 } 426 else { 427 $session_class = 'Hm_PHP_Session'; 428 } 429 return $session_class; 430 } 431 432 /** 433 * @return string 434 */ 435 private function setup_auth() { 436 $auth_class = $this->standard_auth(); 437 if ($auth_class === false) { 438 $auth_class = $this->dynamic_auth(); 439 } 440 if ($auth_class === false) { 441 $auth_class = $this->custom_auth(); 442 } 443 if ($auth_class === false) { 444 Hm_Functions::cease('Invalid auth configuration'); 445 $auth_class = 'Hm_Auth_None'; 446 } 447 return $auth_class; 448 } 449 450 /** 451 * @return string|false 452 */ 453 private function dynamic_auth() { 454 if ($this->auth_type == 'dynamic' && in_array('dynamic_login', $this->config->get_modules(), true)) { 455 return 'Hm_Auth_Dynamic'; 456 } 457 return false; 458 } 459 460 /** 461 * @return string|false 462 */ 463 private function standard_auth() { 464 if ($this->auth_type && in_array($this->auth_type, array('DB', 'LDAP', 'IMAP', 'POP3'), true)) { 465 return sprintf('Hm_Auth_%s', $this->auth_type); 466 } 467 return false; 468 } 469 470 /** 471 * @return string|false 472 */ 473 private function custom_auth() { 474 $custom_auth_class = $this->config->get('auth_class', 'Custom_Auth'); 475 if ($this->auth_type == 'custom' && Hm_Functions::class_exists($custom_auth_class)) { 476 return 'Custom_Auth'; 477 } 478 return false; 479 } 480} 481