Added cookie sessions and changed error pages location.

main
Robert 3 years ago
parent 94364ac996
commit 3e12705ff6
  1. 16
      documents/folders.txt
  2. 35
      src/bootstrap/errors.php
  3. 3
      src/bootstrap/requires.php
  4. 2
      src/bootstrap/safer_io.php
  5. 6
      src/classes/app.php
  6. 2
      src/classes/contracts/sessions_interface.php
  7. 11
      src/classes/page_not_found.php
  8. 114
      src/classes/services/sessions/cookie_sessions.php
  9. 29
      src/classes/services/sessions/file_sessions.php
  10. 23
      src/classes/services/sessions/redis_sessions.php
  11. 36
      src/classes/view.php
  12. 7
      src/views/default/broken.php
  13. 0
      src/views/on_error/404_page.php
  14. 0
      src/views/on_error/dev_error.php
  15. 0
      src/views/on_error/prod_error.php

@ -63,10 +63,11 @@ tts_framework/src
       ├── crypto.php (Newer sodium_crypto)
       ├── password_storage.php (Hash and Verify Hashed passwords)
       └── sodium_storage.php (Secure Cookies, Sessions, etc...)
     ├── sessions (@todo Fix session destroy to erase old sessions)
     ├── sessions
       ├── cookie_sessions.php
       ├── file_sessions.php
       └── redis_sessions.php
     ├── sessions.php (@todo Fix security to encrypt/decrypt sessions)
     ├── sessions.php
     ├── simple_rest.php (demo REST API helper)
     └── twilio.php (Loads Twilio Vendor files into Name Space)
   ├── session_management.php (Starts PHP secure Sessions)
@ -83,12 +84,9 @@ tts_framework/src
│ ├── validator.php (validates HTML Forms)
   └── view.php (Loads view files from common folders you defined)
├── main.inc.php (Bootstraps App, sets configure, registry, di, and name-spaces)
├── templates
   └── dev_error.php (When NOT Live, show Exceptions/Errors)
└── views
├── 404.php (Default 404 Page Not Found Page/Image)
├── default
   └── broken.php (Debug Trace)
└── errors.php (when Live, this: Sorry, we had an error... Page is used)
├── 404_page.php (Default 404 Page Not Found Page/Image)
   ├── dev_error.php (When NOT Live, show Exceptions/Errors)
└── prod_errors.php (when Live, this: Sorry, we had an error... Page is used)
~72 files
~73 files

@ -33,20 +33,18 @@ function tts_broken_error($ex = ''): void {
}
if (\main_tts\is_live()) {
$resource = \main_tts\configure::get('tts', 'error_page');
$exists = \bs_tts\requires::secure_include('views/errors', $resource); // Show Broken Page
if (\bs_tts\requires::secure_include('prod_error.php', 'on_error') === false) {
$exists = \bs_tts\requires::secure_include('views/on_error/prod_error.php', 'tts'); // Show Broken Page
}
if ($exists === false) {
echo "<h1>Sorry, we had an error...</h1><p>We apologize for any inconvenience this may cause.</p>";
}
} else {
$view = new \tts\view();
$view->set_view('broken', 'tts');
$view->set('ex', $ex);
$view->fetch([]);
echo "<h1>Error Page</h1><p>Please go back to another page...</p>";
}
}
function tts_mini_view(string $file, string $render, string $msg): string {
function tts_mini_view(string $file, string $msg): string {
@ob_end_clean(); // Wipe all HTML content before this....
if (!headers_sent()) {
header('Content-Type: text/html; charset=utf-8', true, 200);
@ -58,8 +56,9 @@ function tts_mini_view(string $file, string $render, string $msg): string {
$mini = new stdClass();
$mini->page_output = $msg;
$file = 'templates/' . $file;
\bs_tts\requires::secure_include($file, $render, $mini);
if (\bs_tts\requires::secure_include($file, 'on_error', $mini) === false) {
\bs_tts\requires::secure_include("views/on_error/" . $file, 'tts', $mini);
}
// If you really must close all of your output buffers except one, this'll do it:
while (ob_get_level() > $saved_ob_level) {
@ -273,7 +272,7 @@ function tts_exception_handler(\Throwable $exception) {
if (\main_tts\is_live()) {
tts_global_error_handler(E_USER_ERROR, $err);
} else {
echo tts_mini_view('dev_error', 'tts', $msg);
echo tts_mini_view('dev_error', $msg);
}
exit(1);
}
@ -319,8 +318,8 @@ function tts_custom_error_checker(): void {
$mini .= '<div class="uk-alert uk-alert-danger">' . PHP_EOL;
$mini .= "{$a_errors['message']}, in file: {$a_errors['file']}, on line #{$a_errors['line']}.";
$mini .= '</div>' . PHP_EOL;
//$mini .= '<script type="text/javascript">alert("' . str_replace('"', '', $msg) . '");</script>';
echo tts_mini_view('dev_error', 'tts', $mini);
echo tts_mini_view('dev_error', $mini);
exit(1);
}
}
@ -368,15 +367,19 @@ function tts_global_error_handler(int $errno = 0, string $errstr = '', string $e
}
if (\tts_is_on_error_page() === true) {
$resource = \main_tts\configure::get('tts', 'error_page');
\bs_tts\requires::secure_include('views/errors', $resource);
if (\bs_tts\requires::secure_include('prod_error.php', 'on_error') === false) {
\bs_tts\requires::secure_include('views/on_error/prod_error.php', 'tts');
}
exit(1); // Prevent HTML Looping!!!
}
$http_response_code = 307; // 307 Temporary Redirect
$prj = \main_tts\configure::get('tts', 'default_project');
$ref = TTS_PROJECT_BASE_REF;
$ref = (defined("TTS_PROJECT_BASE_REF")) ? TTS_PROJECT_BASE_REF : false;
if (empty($prj) || $ref === false) {
echo "<h1>Sorry, we had an error...</h1><p>We apologize for any inconvenience this may cause.</p>";
exit(1);
}
if (\bs_tts\common::is_string_found($ref, '/') === false) {
$ref .= '/';
}

@ -105,7 +105,6 @@ final class requires {
} else {
$filtered_dir = rtrim(self::filter_dir_path($dir), '/') . '/';
$file_plus_dir = $filtered_dir . $filtered_file;
// $file_plus_dir = str_replace('_DS_', '/', $file_plus_dir);
}
$escaped_file = escapeshellcmd($file_plus_dir . $file_type);
return self::get_PHP_Version_for_file($escaped_file);
@ -123,9 +122,9 @@ final class requires {
$dir = match ($path) {
"dir_fixed" => "",
"framework", "tts" => \main_tts\TTS_FRAMEWORK,
"on_error" => \bs_tts\site_helper::get_root() . \bs_tts\site_helper::get_project() . "views/on_error/",
default => \bs_tts\site_helper::get_root(), // project
};
$versioned_file = self::safer_file_exists($file, $dir);
if ($versioned_file === false) {
return false;

@ -85,7 +85,7 @@ final class safer_io {
}
// Allow anything to set_data_inputs is desired here
public static function set_data_input(string $var_name, $data_in): void {
public static function set_data_input(string $var_name, mixed $data_in): void {
if (! isset(self::$DATA_INPUTS[$var_name])) {
self::$DATA_INPUTS[$var_name] = $data_in;
}

@ -166,6 +166,12 @@ class app {
$call_class = "\\prj\\" . rtrim($project_folder, "/") . '\\' . $test . 'controllers\\' . $class;
$controller = new $call_class();
if ($method === "error" && str_contains($class, "app") &&
method_exists($controller, $method) === false
) {
tts_broken_error();
return false;
}
if ($use_api) {
if (empty($method)) {

@ -3,7 +3,7 @@
declare(strict_types=1);
namespace tts\contacts;
use SessionHandlerInterface;
interface sessions_interface {
public function close(): bool;

@ -47,12 +47,13 @@ class page_not_found {
if ($use_api === true) {
self::api_method_not_found();
}
$loaded = \bs_tts\requires::secure_include('views/404', 'tts'); // Show 404, Page Not Found Error Page!
if ($loaded === false) {
echo "<h1>404 Page Not Found!</h1>";
// Show 404, Page Not Found Error Page!
if (\bs_tts\requires::secure_include('404_page.php', 'on_error') === false) {
$loaded = \bs_tts\requires::secure_include('views/on_error/404_page.php', 'tts');
if ($loaded === false) {
echo "<h1>404 Page Not Found!</h1>";
}
}
exit(1);
}

@ -0,0 +1,114 @@
<?php
declare(strict_types=1);
namespace tts\services\sessions;
/**
* @author Robert Strutts <Robert@TryingToScale.com>
* @copyright Copyright (c) 2022, Robert Strutts.
* @license https://mit-license.org/
*/
class cookie_sessions_handler_exception extends \Exception {}
class cookie_sessions implements \SessionHandlerInterface {
private static int $zlib_compression_level = 4;
public static string $cookie_domain;
public static string $cookie_name = 'SES';
public static string $cookie_path = '/';
public static bool $cookie_secure = true;
public static bool $cookie_HTTP_only = true;
private $enc;
public static function set_zlib_compression_level(int $level): void {
self::$zlib_compression_level = ($level >= 0 && $level <= 9) ?
$level : 4;
}
public function __construct($enc) {
self::$cookie_domain = $_SERVER['SERVER_NAME'] ?? '';
$this->enc = $enc;
}
private function encrypt(string & $data): void {
$data = $this->enc->encode("sess", $data);
}
private function decrypt(string & $data): void {
try {
$data = $this->enc->decode("sess", $data);
} catch (\Exception $e) {
$data = false; // Maybe it has no data to decode
}
}
private function compress(string & $data): void {
$data = gzdeflate($data, self::$zlib_compression_level);
if ($data === false) {
throw new \tts\services\sessions\cookie_sessions_handler_exception('Failed to compress session data');
}
}
private function decompress(string & $data): void {
$data = gzinflate($data);
}
public function open($save_path, $session_name):bool {
return true;
}
public function close(): bool {
return true;
}
public function read($id): false|string {
$data = isset($_COOKIE[self::$cookie_name]) ? $_COOKIE[self::$cookie_name] : null;
if ($data === null) {
return "";
}
$data = base64_decode($data);
if ($data === false) {
return "";
}
$this->decrypt($data);
if ($data === false) {
return "";
}
$this->decompress($data);
return ($data !== false) ? $data : '';
}
public function write($id, $data): bool {
if (headers_sent($file, $line)) {
throw new \tts\services\sessions\cookie_sessions_handler_exception("Error headers were already sent by $file on line # $line ");
}
$this->compress($data);
$this->encrypt($data);
$data = base64_encode($data);
/*
* Google Chrome - 4096 bytes confirming to RFC.
* If the data is over 4000 bytes, throw an exception
* as web browsers only support up to 4K of Cookie Data!
*/
if (strlen($data) > 4000) {
throw new \tts\services\sessions\cookie_sessions_handler_exception("Session data too big (over 4KB)");
}
$cookie_lifetime = (int) ini_get('session.cookie_lifetime');
return setcookie(self::$cookie_name, $data, $cookie_lifetime, self::$cookie_path, self::$cookie_domain, self::$cookie_secure, self::$cookie_HTTP_only);
}
public function destroy($id): bool {
$expiration_time = time() - 3600;
return setcookie(self::$cookie_name, "", $expiration_time, self::$cookie_path, self::$cookie_domain, self::$cookie_secure, self::$cookie_HTTP_only);
}
public function gc($max_lifetime): int|false {
return 0;
}
}

@ -12,7 +12,7 @@ namespace tts\services\sessions;
class file_sessions implements \tts\contracts\sessions_interface {
private $savePath;
private $save_path;
private function filter_id(string $id): string {
if (\bs_tts\requires::is_valid_file($id)) {
@ -21,17 +21,16 @@ class file_sessions implements \tts\contracts\sessions_interface {
throw new \Exception('Bad ID for session!');
}
public function open(string $savePath, string $sessionName): bool {
$safer_dir = \bs_tts\requires::safer_dir_exists($savePath);
public function open(string $save_path, string $session_name): bool {
$safer_dir = \bs_tts\requires::safer_dir_exists($save_path);
if ($safer_dir === false) {
return false;
}
$this->savePath = $safer_dir;
if (!is_dir($this->savePath)) {
mkdir($this->savePath, 0777);
$this->save_path = $safer_dir;
if (!is_dir($this->save_path)) {
mkdir($this->save_path, 0777);
}
return true;
}
@ -39,33 +38,31 @@ class file_sessions implements \tts\contracts\sessions_interface {
return true;
}
public function read(string $id): string {
public function read(string $id): false|string {
$safer_id = $this->filter_id($id);
return (string) @file_get_contents("{$this->savePath}/sess_{$safer_id}");
return (string) @file_get_contents("{$this->save_path}/sess_{$safer_id}");
}
public function write(string $id, string $data): bool {
$safer_id = $this->filter_id($id);
return file_put_contents("{$this->savePath}/sess_{$safer_id}", $data) === false ? false : true;
return file_put_contents("{$this->save_path}/sess_{$safer_id}", $data) === false ? false : true;
}
public function destroy(string $id): bool {
$safer_id = $this->filter_id($id);
$file = "{$this->savePath}/sess_{$safer_id}";
$file = "{$this->save_path}/sess_{$safer_id}";
if (file_exists($file)) {
unlink($file);
}
return true;
}
public function gc(int $maxlifetime): int {
foreach (glob("{$this->savePath}/sess_*") as $file) {
if (filemtime($file) + $maxlifetime < time() && file_exists($file)) {
public function gc(int $max_lifetime): int|false {
foreach (glob("{$this->save_path}/sess_*") as $file) {
if (filemtime($file) + $max_lifetime < time() && file_exists($file)) {
unlink($file);
}
}
return true;
}

@ -15,45 +15,38 @@ class redis_sessions implements \tts\contracts\sessions_interface {
protected $db;
protected $prefix;
public function __construct(PredisClient $db, $prefix = 'PHPSESSID:') {
public function __construct(PredisClient $db, $prefix = 'SESS:') {
$this->db = $db;
$this->prefix = $prefix;
}
public function open($save_path, $session_name) {
public function open($save_path, $session_name): bool{
// No action necessary because connection is injected
// in constructor and arguments are not applicable.
}
public function close() {
public function close(): bool {
$this->db = null;
unset($this->db);
}
public function read($id) {
public function read($id): false|string {
$id = $this->prefix . $id;
$sessData = $this->db->get($id);
$this->db->expire($id, $this->ttl);
return $sessData;
}
public function write($id, $data) {
public function write($id, $data): bool {
$id = $this->prefix . $id;
$this->db->set($id, $data);
$this->db->expire($id, $this->ttl);
}
public function destroy($id) {
public function destroy($id): bool {
$this->db->del($this->prefix . $id);
}
public function gc($max_lifetime) {
public function gc($max_lifetime): int|false {
// no action necessary because using EXPIRE
}
}
/*
$db = new PredisClient();
$sessHandler = new redis_sessions($db);
session_set_save_handler($sessHandler);
session_start();
*/
}

@ -38,18 +38,13 @@ final class view {
/**
* @todo Ignore render path tts, should go prj/,...,then tts
*/
private function get_file(string $view_file, string $default, string $render_path = 'project'): string {
private function get_file(string $view_file, string $default): string {
$file_ext = \bs_tts\common::get_string_right($view_file, 4);
if (! \bs_tts\common::is_string_found($file_ext, '.')) {
$view_file .= '.php';
}
if ($render_path === 'tts') {
$file = (empty($default)) ? "views/{$view_file}" : "views/{$default}/{$view_file}";
$path = \main_tts\TTS_FRAMEWORK;
} else {
$file = (empty($default)) ? "{$this->project_dir}/views/{$view_file}" : "{$this->project_dir}/views/{$default}/{$view_file}";
$path = \bs_tts\site_helper::get_root();
}
$file = (empty($default)) ? "{$this->project_dir}/views/{$view_file}" : "{$this->project_dir}/views/{$default}/{$view_file}";
$path = \bs_tts\site_helper::get_root();
$vf = $path . $file;
if ( \bs_tts\requires::safer_file_exists($vf) !== false) {
return $file;
@ -60,18 +55,18 @@ final class view {
/**
* Alias to set_view
*/
public function include(string $file, string $render_path = 'project'): void {
$this->set_view($file, $render_path);
public function include(string $file): void {
$this->set_view($file);
}
/**
* Alias to set_view
*/
public function add_view(string $file, string $render_path = 'project'): void {
$this->set_view($file, $render_path);
public function add_view(string $file): void {
$this->set_view($file);
}
private function find_view_path(string $view_file, string $render_path) {
private function find_view_path(string $view_file) {
$found = false;
$default_paths = \main_tts\configure::get('view_mode', 'default_paths');
@ -84,7 +79,7 @@ final class view {
}
foreach ($default_paths as $default) {
$file = $this->get_file($view_file, $default, $render_path);
$file = $this->get_file($view_file, $default);
if ( ! empty($file) ) {
$found = true;
break;
@ -93,11 +88,10 @@ final class view {
return ($found) ? $file : false;
}
private function find_template_path($tpl_file, $render_path) {
private function find_template_path($tpl_file) {
$file = "{$this->project_dir}/views/includes/{$tpl_file}";
$path = ($render_path === 'project') ? \bs_tts\site_helper::get_root() : \main_tts\TTS_FRAMEWORK;
$path = \bs_tts\site_helper::get_root();
$vf = $path . $file;
return \bs_tts\requires::safer_file_exists($vf);
}
@ -107,7 +101,7 @@ final class view {
* @param string $render_path
* @throws Exception
*/
public function set_view(string $view_file = null, string $render_path = 'project'): void {
public function set_view(string $view_file = null): void {
if ($view_file === null) {
return;
}
@ -121,16 +115,16 @@ final class view {
}
if ($file_ext === '.php') {
$file = $this->find_view_path($view_file, $render_path);
$file = $this->find_view_path($view_file);
} else {
$file = $this->find_template_path($view_file, $render_path);
$file = $this->find_template_path($view_file);
}
if ($file === false) {
echo "No view file exists for: {$view_file}!";
throw new \Exception("View File does not exist: " . $view_file);
} else {
$this->files[] = array('file'=>$file, 'path'=>$render_path, 'file_type'=>$file_ext);
$this->files[] = array('file'=>$file, 'path'=>"project", 'file_type'=>$file_ext);
}
}

@ -1,7 +0,0 @@
<?php
declare(strict_types=1);
?>
<pre>
<?= $ex ?>
<?php var_dump(debug_backtrace()); ?>
</pre>
Loading…
Cancel
Save