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)        ├── crypto.php (Newer sodium_crypto)
       ├── password_storage.php (Hash and Verify Hashed passwords)        ├── password_storage.php (Hash and Verify Hashed passwords)
       └── sodium_storage.php (Secure Cookies, Sessions, etc...)        └── sodium_storage.php (Secure Cookies, Sessions, etc...)
     ├── sessions (@todo Fix session destroy to erase old sessions)      ├── sessions
       ├── cookie_sessions.php
       ├── file_sessions.php        ├── file_sessions.php
       └── redis_sessions.php        └── redis_sessions.php
     ├── sessions.php (@todo Fix security to encrypt/decrypt sessions)      ├── sessions.php
     ├── simple_rest.php (demo REST API helper)      ├── simple_rest.php (demo REST API helper)
     └── twilio.php (Loads Twilio Vendor files into Name Space)      └── twilio.php (Loads Twilio Vendor files into Name Space)
   ├── session_management.php (Starts PHP secure Sessions)    ├── session_management.php (Starts PHP secure Sessions)
@ -83,12 +84,9 @@ tts_framework/src
│ ├── validator.php (validates HTML Forms) │ ├── validator.php (validates HTML Forms)
   └── view.php (Loads view files from common folders you defined)    └── view.php (Loads view files from common folders you defined)
├── main.inc.php (Bootstraps App, sets configure, registry, di, and name-spaces) ├── main.inc.php (Bootstraps App, sets configure, registry, di, and name-spaces)
├── templates
   └── dev_error.php (When NOT Live, show Exceptions/Errors)
└── views └── views
├── 404.php (Default 404 Page Not Found Page/Image) ├── 404_page.php (Default 404 Page Not Found Page/Image)
├── default    ├── dev_error.php (When NOT Live, show Exceptions/Errors)
   └── broken.php (Debug Trace) └── prod_errors.php (when Live, this: Sorry, we had an error... Page is used)
└── 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()) { if (\main_tts\is_live()) {
$resource = \main_tts\configure::get('tts', 'error_page'); if (\bs_tts\requires::secure_include('prod_error.php', 'on_error') === false) {
$exists = \bs_tts\requires::secure_include('views/errors', $resource); // Show Broken Page $exists = \bs_tts\requires::secure_include('views/on_error/prod_error.php', 'tts'); // Show Broken Page
}
if ($exists === false) { if ($exists === false) {
echo "<h1>Sorry, we had an error...</h1><p>We apologize for any inconvenience this may cause.</p>"; echo "<h1>Sorry, we had an error...</h1><p>We apologize for any inconvenience this may cause.</p>";
} }
} else { } else {
$view = new \tts\view(); echo "<h1>Error Page</h1><p>Please go back to another page...</p>";
$view->set_view('broken', 'tts');
$view->set('ex', $ex);
$view->fetch([]);
} }
} }
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.... @ob_end_clean(); // Wipe all HTML content before this....
if (!headers_sent()) { if (!headers_sent()) {
header('Content-Type: text/html; charset=utf-8', true, 200); 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 = new stdClass();
$mini->page_output = $msg; $mini->page_output = $msg;
$file = 'templates/' . $file; if (\bs_tts\requires::secure_include($file, 'on_error', $mini) === false) {
\bs_tts\requires::secure_include($file, $render, $mini); \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: // If you really must close all of your output buffers except one, this'll do it:
while (ob_get_level() > $saved_ob_level) { while (ob_get_level() > $saved_ob_level) {
@ -273,7 +272,7 @@ function tts_exception_handler(\Throwable $exception) {
if (\main_tts\is_live()) { if (\main_tts\is_live()) {
tts_global_error_handler(E_USER_ERROR, $err); tts_global_error_handler(E_USER_ERROR, $err);
} else { } else {
echo tts_mini_view('dev_error', 'tts', $msg); echo tts_mini_view('dev_error', $msg);
} }
exit(1); exit(1);
} }
@ -319,8 +318,8 @@ function tts_custom_error_checker(): void {
$mini .= '<div class="uk-alert uk-alert-danger">' . PHP_EOL; $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 .= "{$a_errors['message']}, in file: {$a_errors['file']}, on line #{$a_errors['line']}.";
$mini .= '</div>' . PHP_EOL; $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); 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) { if (\tts_is_on_error_page() === true) {
$resource = \main_tts\configure::get('tts', 'error_page'); if (\bs_tts\requires::secure_include('prod_error.php', 'on_error') === false) {
\bs_tts\requires::secure_include('views/errors', $resource); \bs_tts\requires::secure_include('views/on_error/prod_error.php', 'tts');
}
exit(1); // Prevent HTML Looping!!! exit(1); // Prevent HTML Looping!!!
} }
$http_response_code = 307; // 307 Temporary Redirect $http_response_code = 307; // 307 Temporary Redirect
$prj = \main_tts\configure::get('tts', 'default_project'); $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) { if (\bs_tts\common::is_string_found($ref, '/') === false) {
$ref .= '/'; $ref .= '/';
} }

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

@ -85,7 +85,7 @@ final class safer_io {
} }
// Allow anything to set_data_inputs is desired here // 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])) { if (! isset(self::$DATA_INPUTS[$var_name])) {
self::$DATA_INPUTS[$var_name] = $data_in; self::$DATA_INPUTS[$var_name] = $data_in;
} }

@ -166,6 +166,12 @@ class app {
$call_class = "\\prj\\" . rtrim($project_folder, "/") . '\\' . $test . 'controllers\\' . $class; $call_class = "\\prj\\" . rtrim($project_folder, "/") . '\\' . $test . 'controllers\\' . $class;
$controller = new $call_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 ($use_api) {
if (empty($method)) { if (empty($method)) {

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

@ -47,12 +47,13 @@ class page_not_found {
if ($use_api === true) { if ($use_api === true) {
self::api_method_not_found(); self::api_method_not_found();
} }
// Show 404, Page Not Found Error Page!
$loaded = \bs_tts\requires::secure_include('views/404', 'tts'); // Show 404, Page Not Found Error Page! if (\bs_tts\requires::secure_include('404_page.php', 'on_error') === false) {
if ($loaded === false) { $loaded = \bs_tts\requires::secure_include('views/on_error/404_page.php', 'tts');
echo "<h1>404 Page Not Found!</h1>"; if ($loaded === false) {
echo "<h1>404 Page Not Found!</h1>";
}
} }
exit(1); 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 { class file_sessions implements \tts\contracts\sessions_interface {
private $savePath; private $save_path;
private function filter_id(string $id): string { private function filter_id(string $id): string {
if (\bs_tts\requires::is_valid_file($id)) { 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!'); throw new \Exception('Bad ID for session!');
} }
public function open(string $savePath, string $sessionName): bool { public function open(string $save_path, string $session_name): bool {
$safer_dir = \bs_tts\requires::safer_dir_exists($savePath); $safer_dir = \bs_tts\requires::safer_dir_exists($save_path);
if ($safer_dir === false) { if ($safer_dir === false) {
return false; return false;
} }
$this->savePath = $safer_dir; $this->save_path = $safer_dir;
if (!is_dir($this->savePath)) { if (!is_dir($this->save_path)) {
mkdir($this->savePath, 0777); mkdir($this->save_path, 0777);
} }
return true; return true;
} }
@ -39,33 +38,31 @@ class file_sessions implements \tts\contracts\sessions_interface {
return true; return true;
} }
public function read(string $id): string { public function read(string $id): false|string {
$safer_id = $this->filter_id($id); $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 { public function write(string $id, string $data): bool {
$safer_id = $this->filter_id($id); $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 { public function destroy(string $id): bool {
$safer_id = $this->filter_id($id); $safer_id = $this->filter_id($id);
$file = "{$this->savePath}/sess_{$safer_id}"; $file = "{$this->save_path}/sess_{$safer_id}";
if (file_exists($file)) { if (file_exists($file)) {
unlink($file); unlink($file);
} }
return true; return true;
} }
public function gc(int $maxlifetime): int { public function gc(int $max_lifetime): int|false {
foreach (glob("{$this->savePath}/sess_*") as $file) { foreach (glob("{$this->save_path}/sess_*") as $file) {
if (filemtime($file) + $maxlifetime < time() && file_exists($file)) { if (filemtime($file) + $max_lifetime < time() && file_exists($file)) {
unlink($file); unlink($file);
} }
} }
return true; return true;
} }

@ -15,45 +15,38 @@ class redis_sessions implements \tts\contracts\sessions_interface {
protected $db; protected $db;
protected $prefix; protected $prefix;
public function __construct(PredisClient $db, $prefix = 'PHPSESSID:') { public function __construct(PredisClient $db, $prefix = 'SESS:') {
$this->db = $db; $this->db = $db;
$this->prefix = $prefix; $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 // No action necessary because connection is injected
// in constructor and arguments are not applicable. // in constructor and arguments are not applicable.
} }
public function close() { public function close(): bool {
$this->db = null; $this->db = null;
unset($this->db); unset($this->db);
} }
public function read($id) { public function read($id): false|string {
$id = $this->prefix . $id; $id = $this->prefix . $id;
$sessData = $this->db->get($id); $sessData = $this->db->get($id);
$this->db->expire($id, $this->ttl); $this->db->expire($id, $this->ttl);
return $sessData; return $sessData;
} }
public function write($id, $data) { public function write($id, $data): bool {
$id = $this->prefix . $id; $id = $this->prefix . $id;
$this->db->set($id, $data); $this->db->set($id, $data);
$this->db->expire($id, $this->ttl); $this->db->expire($id, $this->ttl);
} }
public function destroy($id) { public function destroy($id): bool {
$this->db->del($this->prefix . $id); $this->db->del($this->prefix . $id);
} }
public function gc($max_lifetime) { public function gc($max_lifetime): int|false {
// no action necessary because using EXPIRE // 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 * @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); $file_ext = \bs_tts\common::get_string_right($view_file, 4);
if (! \bs_tts\common::is_string_found($file_ext, '.')) { if (! \bs_tts\common::is_string_found($file_ext, '.')) {
$view_file .= '.php'; $view_file .= '.php';
} }
if ($render_path === 'tts') { $file = (empty($default)) ? "{$this->project_dir}/views/{$view_file}" : "{$this->project_dir}/views/{$default}/{$view_file}";
$file = (empty($default)) ? "views/{$view_file}" : "views/{$default}/{$view_file}"; $path = \bs_tts\site_helper::get_root();
$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();
}
$vf = $path . $file; $vf = $path . $file;
if ( \bs_tts\requires::safer_file_exists($vf) !== false) { if ( \bs_tts\requires::safer_file_exists($vf) !== false) {
return $file; return $file;
@ -60,18 +55,18 @@ final class view {
/** /**
* Alias to set_view * Alias to set_view
*/ */
public function include(string $file, string $render_path = 'project'): void { public function include(string $file): void {
$this->set_view($file, $render_path); $this->set_view($file);
} }
/** /**
* Alias to set_view * Alias to set_view
*/ */
public function add_view(string $file, string $render_path = 'project'): void { public function add_view(string $file): void {
$this->set_view($file, $render_path); $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; $found = false;
$default_paths = \main_tts\configure::get('view_mode', 'default_paths'); $default_paths = \main_tts\configure::get('view_mode', 'default_paths');
@ -84,7 +79,7 @@ final class view {
} }
foreach ($default_paths as $default) { foreach ($default_paths as $default) {
$file = $this->get_file($view_file, $default, $render_path); $file = $this->get_file($view_file, $default);
if ( ! empty($file) ) { if ( ! empty($file) ) {
$found = true; $found = true;
break; break;
@ -93,11 +88,10 @@ final class view {
return ($found) ? $file : false; 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}"; $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; $vf = $path . $file;
return \bs_tts\requires::safer_file_exists($vf); return \bs_tts\requires::safer_file_exists($vf);
} }
@ -107,7 +101,7 @@ final class view {
* @param string $render_path * @param string $render_path
* @throws Exception * @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) { if ($view_file === null) {
return; return;
} }
@ -121,16 +115,16 @@ final class view {
} }
if ($file_ext === '.php') { if ($file_ext === '.php') {
$file = $this->find_view_path($view_file, $render_path); $file = $this->find_view_path($view_file);
} else { } else {
$file = $this->find_template_path($view_file, $render_path); $file = $this->find_template_path($view_file);
} }
if ($file === false) { if ($file === false) {
echo "No view file exists for: {$view_file}!"; echo "No view file exists for: {$view_file}!";
throw new \Exception("View File does not exist: " . $view_file); throw new \Exception("View File does not exist: " . $view_file);
} else { } 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