mirror of
				https://github.com/beestat/app.git
				synced 2025-11-03 18:37:01 -05:00 
			
		
		
		
	
		
			
				
	
	
		
			318 lines
		
	
	
		
			9.2 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			318 lines
		
	
	
		
			9.2 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
<?php
 | 
						|
 | 
						|
namespace cora;
 | 
						|
 | 
						|
/**
 | 
						|
 * Offers session-related functions.
 | 
						|
 *
 | 
						|
 * @author Jon Ziebell
 | 
						|
 */
 | 
						|
final class session {
 | 
						|
 | 
						|
  /**
 | 
						|
   * The current session.
 | 
						|
   *
 | 
						|
   * @var array
 | 
						|
   */
 | 
						|
  private $session;
 | 
						|
 | 
						|
  /**
 | 
						|
   * The singleton.
 | 
						|
   *
 | 
						|
   * @var session
 | 
						|
   */
 | 
						|
  private static $instance;
 | 
						|
 | 
						|
  /**
 | 
						|
   * Constructor
 | 
						|
   */
 | 
						|
  private function __construct() {}
 | 
						|
 | 
						|
  /**
 | 
						|
   * Use this function to instantiate this class instead of calling new
 | 
						|
   * session() (which isn't allowed anyways). This avoids confusion from
 | 
						|
   * trying to use dependency injection by passing an instance of this class
 | 
						|
   * around everywhere.
 | 
						|
   *
 | 
						|
   * @return session A new session object or the already created one.
 | 
						|
   */
 | 
						|
  public static function get_instance() {
 | 
						|
    if(isset(self::$instance) === false) {
 | 
						|
      self::$instance = new self();
 | 
						|
    }
 | 
						|
    return self::$instance;
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Return whether or not this class has been instantiated.
 | 
						|
   *
 | 
						|
   * @return bool
 | 
						|
   */
 | 
						|
  public static function has_instance() {
 | 
						|
    return isset(self::$instance);
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Request a session. This method sets a couple cookies and returns the
 | 
						|
   * session key. By default, all cookies except those set in
 | 
						|
   * $additional_cookie_values are marked as httponly, which means only the
 | 
						|
   * server can access them. Use the following table to determine when the
 | 
						|
   * local cookie will be set to expire.
 | 
						|
   *
 | 
						|
   * timeout | life | expire
 | 
						|
   * -------------------------------
 | 
						|
   * null    | null | never expires due to inactivity, never expires due to time limit
 | 
						|
   * null    | set  | never expires due to inactivity, expires in life seconds
 | 
						|
   * set     | null | expires after timeout inactivity, never expires due to time limit
 | 
						|
   * set     | set  | expires after timeout inactivity, expires in life seconds
 | 
						|
   *
 | 
						|
   * @param int $timeout How long, in seconds, until the session expires due
 | 
						|
   * to inactivity. Set to null for no timeout.
 | 
						|
   * @param int $life How long, in seconds, until the session expires. Set to
 | 
						|
   * null for no expiration.
 | 
						|
   * @param int $user_id An optional external integer pointer to another
 | 
						|
   * table. This will most often be user.user_id, but could be something like
 | 
						|
   * person.person_id or player.player_id.
 | 
						|
   * @param array $additional_cookie_values Set additional values in the
 | 
						|
   * cookie by setting this value. Doing this is generally discouraged as
 | 
						|
   * cookies add state to the application, but something like a username for a
 | 
						|
   * "remember me" checkbox is reasonable.
 | 
						|
   *
 | 
						|
   * @return string The generated session key.
 | 
						|
   */
 | 
						|
  public function request($timeout = null, $life = null, $user_id = null, $additional_cookie_values = null) {
 | 
						|
    $database = database::get_instance();
 | 
						|
    $session_key = $this->generate_session_key();
 | 
						|
 | 
						|
    $this->session = $database->create(
 | 
						|
      'cora\session',
 | 
						|
      [
 | 
						|
        'session_key' => $session_key,
 | 
						|
        'timeout' => $timeout,
 | 
						|
        'life' => $life,
 | 
						|
        'user_id' => $user_id,
 | 
						|
        'created_by' => ip2long($_SERVER['REMOTE_ADDR']),
 | 
						|
        'last_used_by' => ip2long($_SERVER['REMOTE_ADDR']),
 | 
						|
        'last_used_at' => date('Y-m-d H:i:s')
 | 
						|
      ]
 | 
						|
    );
 | 
						|
 | 
						|
    // Set the local cookie expiration.
 | 
						|
    if($life !== null) {
 | 
						|
      $expire = time() + $life;
 | 
						|
    }
 | 
						|
    else {
 | 
						|
      if($timeout === null) {
 | 
						|
        $expire = 4294967295; // 2038
 | 
						|
      }
 | 
						|
      else {
 | 
						|
        $expire = 0; // Browser close
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    // Set all of the necessary cookies. Both *_session_key and *_user_id are
 | 
						|
    // read every API request and made available to the API.
 | 
						|
    $this->set_cookie('session_key', $session_key, $expire);
 | 
						|
    if(isset($additional_cookie_values) === true) {
 | 
						|
      foreach($additional_cookie_values as $key => $value) {
 | 
						|
        $this->set_cookie($key, $value, $expire, false);
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    return $session_key;
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Similar to the Linux touch command, this method "touches" the session and
 | 
						|
   * updates last_used_at and last_used_by. This is executed every time a
 | 
						|
   * request that requires a session is sent to the API. Note that this uses
 | 
						|
   * the cookie sent by the client directly so there is no default way to
 | 
						|
   * touch a session unless you are the one logged in to it.
 | 
						|
   *
 | 
						|
   * @param $string session_key The session_key to use. If not set, will use
 | 
						|
   * $_COOKIE['session_key'].
 | 
						|
   *
 | 
						|
   * @return bool True if it was successfully updated, false if the session
 | 
						|
   * does not exist or is expired. Basically, return bool whether or not the
 | 
						|
   * sesion is valid.
 | 
						|
   */
 | 
						|
  public function touch($session_key = null) {
 | 
						|
    if($session_key === null && isset($_COOKIE['session_key']) === true) {
 | 
						|
      $session_key = $_COOKIE['session_key'];
 | 
						|
    }
 | 
						|
 | 
						|
    if($session_key === null) {
 | 
						|
      return false;
 | 
						|
    }
 | 
						|
 | 
						|
    $database = database::get_instance();
 | 
						|
 | 
						|
    $sessions = $database->read(
 | 
						|
      'cora\session',
 | 
						|
      [
 | 
						|
        'session_key' => $session_key,
 | 
						|
        'deleted' => 0
 | 
						|
      ]
 | 
						|
    );
 | 
						|
    if(count($sessions) === 1) {
 | 
						|
      $session = $sessions[0];
 | 
						|
 | 
						|
      // Check for expired session.
 | 
						|
      if(
 | 
						|
        (
 | 
						|
          $session['timeout'] !== null &&
 | 
						|
          (strtotime($session['last_used_at']) + strtotime($session['timeout'])) < time()
 | 
						|
        ) ||
 | 
						|
        (
 | 
						|
          $session['life'] !== null &&
 | 
						|
          (strtotime($session['last_used_at']) + strtotime($session['life'])) < time()
 | 
						|
        )
 | 
						|
      ) {
 | 
						|
        $this->delete_cookie('session_key');
 | 
						|
        return false;
 | 
						|
      }
 | 
						|
 | 
						|
      $this->session = $database->update(
 | 
						|
        'cora\session',
 | 
						|
        [
 | 
						|
          'session_id' => $session['session_id'],
 | 
						|
          'last_used_at' => date('Y-m-d H:i:s'),
 | 
						|
          'last_used_by' => ip2long($_SERVER['REMOTE_ADDR'])
 | 
						|
        ]
 | 
						|
      );
 | 
						|
 | 
						|
      return true;
 | 
						|
    }
 | 
						|
    else {
 | 
						|
      $this->delete_cookie('session_key');
 | 
						|
      return false;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  private function invalidate_cookies() {
 | 
						|
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Delete the session with the provided session_key. If no session_key is
 | 
						|
   * provided, delete the current session. This function is provided to aid
 | 
						|
   * session management. Call it with no parameters for something like
 | 
						|
   * user->log_out(), or set $session_key to end a specific session. You would
 | 
						|
   * typically want to have your own permission layer on top of that to enable
 | 
						|
   * only admins to do that.
 | 
						|
   *
 | 
						|
   * @param string $session_key The session key of the session to delete.
 | 
						|
   *
 | 
						|
   * @return bool True if it was successfully deleted. Could return false for
 | 
						|
   * a non-existent session key or if it was already deleted.
 | 
						|
   */
 | 
						|
  public function delete($session_key = null) {
 | 
						|
    $database = database::get_instance();
 | 
						|
    if($session_key === null) {
 | 
						|
      $session_key = $this->session['session_key'];
 | 
						|
    }
 | 
						|
 | 
						|
    $sessions = $database->read('cora\session', ['session_key' => $session_key]);
 | 
						|
    if(count($sessions) === 1) {
 | 
						|
      $database->delete('cora\session', $sessions[0]['session_id']);
 | 
						|
      // Remove these if the current session got logged out.
 | 
						|
      if($session_key === $this->session['session_key']) {
 | 
						|
        $this->session = null;
 | 
						|
      }
 | 
						|
      return true;
 | 
						|
    }
 | 
						|
 | 
						|
    return false;
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Get the user_id on this session. Useful for getting things like the
 | 
						|
   * user_id for the currently logged in user.
 | 
						|
   *
 | 
						|
   * @return int The current user_id.
 | 
						|
   */
 | 
						|
  public function get_user_id() {
 | 
						|
    if ($this->session !== null) {
 | 
						|
      return $this->session['user_id'];
 | 
						|
    }
 | 
						|
 | 
						|
    return null;
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Get the session.
 | 
						|
   *
 | 
						|
   * @return array The session.
 | 
						|
   */
 | 
						|
  public function get() {
 | 
						|
    return $this->session;
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Generate a random (enough) session key.
 | 
						|
   *
 | 
						|
   * @return string The generated session key.
 | 
						|
   */
 | 
						|
  private function generate_session_key() {
 | 
						|
    return bin2hex(random_bytes(20));
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Sets a cookie. If you want to set custom cookies, use the
 | 
						|
   * $additional_cookie_values argument on $session->create().
 | 
						|
   *
 | 
						|
   * @param string $name The name of the cookie.
 | 
						|
   * @param mixed $value The value of the cookie.
 | 
						|
   * @param int $expire When the cookie should expire.
 | 
						|
   * @param bool $httponly True if the cookie should only be accessible on the
 | 
						|
   * server.
 | 
						|
   *
 | 
						|
   * @throws \Exception If The cookie fails to set.
 | 
						|
   */
 | 
						|
  private function set_cookie($name, $value, $expire, $httponly = true) {
 | 
						|
    $this->setting = setting::get_instance();
 | 
						|
    $path = '/'; // The current directory that the cookie is being set in.
 | 
						|
    $secure = $this->setting->get('force_ssl');
 | 
						|
 | 
						|
    preg_match(
 | 
						|
      '/https?:\/\/(.*?)\//',
 | 
						|
      $this->setting->get('beestat_root_uri'),
 | 
						|
      $matches
 | 
						|
    );
 | 
						|
    $domain = $matches[1];
 | 
						|
 | 
						|
    $cookie_success = setcookie(
 | 
						|
      $name,
 | 
						|
      $value,
 | 
						|
      $expire,
 | 
						|
      $path,
 | 
						|
      $domain,
 | 
						|
      $secure,
 | 
						|
      $httponly
 | 
						|
    );
 | 
						|
 | 
						|
    if($cookie_success === false) {
 | 
						|
      throw new \Exception('Failed to set cookie.', 1400);
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Delete a cookie. This will remove the cookie value and set it to expire 1
 | 
						|
   * day ago.
 | 
						|
   *
 | 
						|
   * @param string $name The name of the cookie to delete.
 | 
						|
   */
 | 
						|
  private function delete_cookie($name) {
 | 
						|
    $this->set_cookie($name, '', time() - 86400);
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Check if the session is valid.
 | 
						|
   *
 | 
						|
   * @return bool Whether or not the session is valid.
 | 
						|
   */
 | 
						|
  public function is_valid() {
 | 
						|
    return $this->session !== null;
 | 
						|
  }
 | 
						|
}
 |