From 8968be1ffdd1440ed2f2411aa78a7bff50b67cf5 Mon Sep 17 00:00:00 2001 From: Mike Koch Date: Sat, 28 Jan 2017 22:35:42 -0500 Subject: [PATCH] More work on exception handling and user context building --- api/ApplicationContext.php | 8 ++ api/autoload.php | 14 +++ api/businesslogic/Helpers.php | 29 +++++ api/businesslogic/category/Category.php | 7 +- .../category/CategoryRetriever.php | 17 ++- api/businesslogic/category/retriever.php | 5 - .../InvalidAuthenticationTokenException.php | 11 ++ .../MissingAuthenticationTokenException.php | 17 +++ .../{ => exception}/ValidationException.php | 3 +- .../security/UserContextBuilder.php | 37 ++++-- api/businesslogic/security_retriever.php | 9 -- api/businesslogic/ticket/TicketCreator.php | 9 +- api/businesslogic/ticket/ticket_creator.php | 2 - api/businesslogic/ticket_retriever.php | 107 ------------------ api/businesslogic/user_retriever.php | 77 ------------- api/controllers/CategoryController.php | 4 +- api/core/database.inc.php | 17 ++- api/core/database_mysqli.inc.php | 13 ++- api/core/headers.php | 9 -- api/dao/category/CategoryGateway.php | 4 + api/dao/category_dao.php | 28 ----- api/dao/security/UserGateway.php | 33 +++--- api/exception/AccessException.php | 11 -- api/index.php | 47 +++++++- 24 files changed, 215 insertions(+), 303 deletions(-) create mode 100644 api/businesslogic/Helpers.php delete mode 100644 api/businesslogic/category/retriever.php create mode 100644 api/businesslogic/exception/InvalidAuthenticationTokenException.php create mode 100644 api/businesslogic/exception/MissingAuthenticationTokenException.php rename api/businesslogic/{ => exception}/ValidationException.php (87%) delete mode 100644 api/businesslogic/security_retriever.php delete mode 100644 api/businesslogic/ticket/ticket_creator.php delete mode 100644 api/businesslogic/ticket_retriever.php delete mode 100644 api/businesslogic/user_retriever.php delete mode 100644 api/core/headers.php delete mode 100644 api/dao/category_dao.php delete mode 100644 api/exception/AccessException.php diff --git a/api/ApplicationContext.php b/api/ApplicationContext.php index 64cf99a1..1c2c9307 100644 --- a/api/ApplicationContext.php +++ b/api/ApplicationContext.php @@ -5,8 +5,10 @@ namespace Core; // Responsible for loading in all necessary classes. AKA a poor man's DI solution. use BusinessLogic\Category\CategoryRetriever; use BusinessLogic\Security\BanRetriever; +use BusinessLogic\Security\UserContextBuilder; use DataAccess\CategoryGateway; use DataAccess\Security\BanGateway; +use DataAccess\Security\UserGateway; class ApplicationContext { public $get; @@ -14,10 +16,16 @@ class ApplicationContext { function __construct() { $this->get = array(); + // Categories $this->get['CategoryGateway'] = new CategoryGateway(); $this->get['CategoryRetriever'] = new CategoryRetriever($this->get['CategoryGateway']); + // Bans $this->get['BanGateway'] = new BanGateway(); $this->get['BanRetriever'] = new BanRetriever($this->get['BanGateway']); + + // User Context + $this->get['UserGateway'] = new UserGateway(); + $this->get['UserContextBuilder'] = new UserContextBuilder($this->get['UserGateway']); } } \ No newline at end of file diff --git a/api/autoload.php b/api/autoload.php index 5be0ed5a..b4b7d7da 100644 --- a/api/autoload.php +++ b/api/autoload.php @@ -12,6 +12,15 @@ require_once(__DIR__ . '/../hesk_settings.inc.php'); // Mods for HESK API Files require_once(__DIR__ . '/http_response_code.php'); +require_once(__DIR__ . '/dao/CommonDao.php'); +require_once(__DIR__ . '/businesslogic/Helpers.php'); + +// User Context +require_once(__DIR__ . '/dao/security/UserGateway.php'); +require_once(__DIR__ . '/businesslogic/security/UserContextBuilder.php'); +require_once(__DIR__ . '/businesslogic/security/UserContextNotifications.php'); +require_once(__DIR__ . '/businesslogic/security/UserContextPreferences.php'); +require_once(__DIR__ . '/businesslogic/security/UserContext.php'); // Categories require_once(__DIR__ . '/dao/category/CategoryGateway.php'); @@ -25,6 +34,11 @@ require_once(__DIR__ . '/businesslogic/security/BanRetriever.php'); require_once(__DIR__ . '/businesslogic/security/BannedEmail.php'); require_once(__DIR__ . '/businesslogic/security/BannedIp.php'); +// Exceptions +require_once(__DIR__ . '/businesslogic/exception/InvalidAuthenticationTokenException.php'); +require_once(__DIR__ . '/businesslogic/exception/MissingAuthenticationTokenException.php'); +require_once(__DIR__ . '/businesslogic/exception/ValidationException.php'); + hesk_load_api_database_functions(); // HESK files that require database access diff --git a/api/businesslogic/Helpers.php b/api/businesslogic/Helpers.php new file mode 100644 index 00000000..0df1d131 --- /dev/null +++ b/api/businesslogic/Helpers.php @@ -0,0 +1,29 @@ + $value) { + $uppercaseHeaders[strtoupper($header)] = $value; + } + + return isset($uppercaseHeaders[$key]) + ? $uppercaseHeaders[$key] + : NULL; + } + + static function hashToken($token) { + return hash('sha512', $token); + } +} \ No newline at end of file diff --git a/api/businesslogic/category/Category.php b/api/businesslogic/category/Category.php index 77a6be9c..e73a7888 100644 --- a/api/businesslogic/category/Category.php +++ b/api/businesslogic/category/Category.php @@ -39,7 +39,12 @@ class Category { public $priority; /** - * @var int? The manager for the category, if applicable + * @var int|null The manager for the category, if applicable */ public $manager; + + /** + * @var bool Indication if the user has access to the category + */ + public $accessible; } \ No newline at end of file diff --git a/api/businesslogic/category/CategoryRetriever.php b/api/businesslogic/category/CategoryRetriever.php index c45ee146..3c52f79a 100644 --- a/api/businesslogic/category/CategoryRetriever.php +++ b/api/businesslogic/category/CategoryRetriever.php @@ -2,6 +2,7 @@ namespace BusinessLogic\Category; +use BusinessLogic\Security\UserContext; use DataAccess\CategoryGateway; class CategoryRetriever { @@ -14,7 +15,19 @@ class CategoryRetriever { $this->categoryGateway = $categoryGateway; } - function getAllCategories($hesk_settings) { - return $this->categoryGateway->getAllCategories($hesk_settings); + /** + * @param $heskSettings array + * @param $userContext UserContext + * @return array + */ + function getAllCategories($heskSettings, $userContext) { + $categories = $this->categoryGateway->getAllCategories($heskSettings); + + foreach ($categories as $category) { + $category->accessible = $userContext->admin || + in_array($category->id, $userContext->categories); + } + + return $categories; } } \ No newline at end of file diff --git a/api/businesslogic/category/retriever.php b/api/businesslogic/category/retriever.php deleted file mode 100644 index d8e45267..00000000 --- a/api/businesslogic/category/retriever.php +++ /dev/null @@ -1,5 +0,0 @@ -userGateway = $userGateway; + } + + function buildUserContext($authToken, $heskSettings) { + $NULL_OR_EMPTY_STRING = 'cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e'; - $hashedToken = hash('sha512', $authToken); - return UserGateway::getUserForAuthToken($hashedToken, $hesk_settings); + $hashedToken = Helpers::hashToken($authToken); + + if ($hashedToken === $NULL_OR_EMPTY_STRING) { + throw new MissingAuthenticationTokenException(); + } + + $userRow = $this->userGateway->getUserForAuthToken($hashedToken, $heskSettings); + + if ($userRow === null) { + throw new InvalidAuthenticationTokenException(); + } + + return $this->fromDataRow($userRow); } /** @@ -18,11 +41,7 @@ class UserContextBuilder { * @param $dataRow array the $_SESSION superglobal or the hesk_users result set * @return UserContext the built user context */ - static function fromDataRow($dataRow) { - require_once(__DIR__ . '/UserContext.php'); - require_once(__DIR__ . '/UserContextPreferences.php'); - require_once(__DIR__ . '/UserContextNotifications.php'); - + function fromDataRow($dataRow) { $userContext = new UserContext(); $userContext->id = $dataRow['id']; $userContext->username = $dataRow['user']; diff --git a/api/businesslogic/security_retriever.php b/api/businesslogic/security_retriever.php deleted file mode 100644 index 742d97c7..00000000 --- a/api/businesslogic/security_retriever.php +++ /dev/null @@ -1,9 +0,0 @@ -errorKeys) > 0) { - require_once(__DIR__ . '/../ValidationException.php'); - // Validation failed throw new ValidationException($validationModel); } @@ -35,11 +33,6 @@ class TicketCreator { * @return ValidationModel If errorKeys is empty, validation successful. Otherwise invalid ticket */ function validate($ticketRequest, $staff, $heskSettings, $modsForHeskSettings) { - require_once(__DIR__ . '/../email_validators.php'); - require_once(__DIR__ . '/../category/CategoryRetriever.php'); - //require_once('../category/retriever.php'); - //require_once('../bans/retriever.php'); - $TICKET_PRIORITY_CRITICAL = 0; $validationModel = new ValidationModel(); diff --git a/api/businesslogic/ticket/ticket_creator.php b/api/businesslogic/ticket/ticket_creator.php deleted file mode 100644 index a4abe2da..00000000 --- a/api/businesslogic/ticket/ticket_creator.php +++ /dev/null @@ -1,2 +0,0 @@ -get['CategoryRetriever']; - return $categoryRetriever->getAllCategories($hesk_settings); + return $categoryRetriever->getAllCategories($hesk_settings, $userContext); } } \ No newline at end of file diff --git a/api/core/database.inc.php b/api/core/database.inc.php index c56381e6..08a20ae2 100755 --- a/api/core/database.inc.php +++ b/api/core/database.inc.php @@ -120,9 +120,8 @@ function hesk_dbConnect() { $message = $hesklang['contact_webmaster'] . $hesk_settings['webmaster_email']; } - header('Content-Type: application/json'); - print_error($hesklang['cant_connect_db'], $message); - return http_response_code(500); + //TODO Throw exception + //print_error($hesklang['cant_connect_db'], $message); } if ( ! @mysql_select_db($hesk_settings['db_name'], $hesk_db_link)) @@ -135,9 +134,8 @@ function hesk_dbConnect() { $message = $hesklang['contact_webmaster'] . $hesk_settings['webmaster_email']; } - header('Content-Type: application/json'); - print_error($hesklang['cant_connect_db'], $message); - die(); + //TODO Throw exception + //print_error($hesklang['cant_connect_db'], $message); } // Check MySQL/PHP version and set encoding to utf8 @@ -182,10 +180,9 @@ function hesk_dbQuery($query) { $message = $hesklang['contact_webmaster'] . $hesk_settings['webmaster_email']; } - header('Content-Type: application/json'); - print_error($hesklang['cant_sql'], $message); - die(); - + //TODO Throw exception + //print_error($hesklang['cant_sql'], $message); + return null; } // END hesk_dbQuery() diff --git a/api/core/database_mysqli.inc.php b/api/core/database_mysqli.inc.php index b52122aa..f25f3004 100755 --- a/api/core/database_mysqli.inc.php +++ b/api/core/database_mysqli.inc.php @@ -128,9 +128,9 @@ function hesk_dbConnect() { $message = $hesklang['contact_webmaster'] . $hesk_settings['webmaster_email']; } - header('Content-Type: application/json'); - print_error($hesklang['cant_connect_db'], $message); - http_response_code(500); + + //TODO Throw exception instead + //print_error($hesklang['cant_connect_db'], $message); } // Check MySQL/PHP version and set encoding to utf8 @@ -177,9 +177,10 @@ function hesk_dbQuery($query) { $message = $hesklang['contact_webmaster'] . $hesk_settings['webmaster_email']; } - header('Content-Type: application/json'); - print_error($hesklang['cant_sql'], $message); - die(http_response_code(500)); + + //TODO Throw exception instead + //print_error($hesklang['cant_sql'], $message); + return null; } // END hesk_dbQuery() diff --git a/api/core/headers.php b/api/core/headers.php deleted file mode 100644 index 892fafb0..00000000 --- a/api/core/headers.php +++ /dev/null @@ -1,9 +0,0 @@ -init(); diff --git a/api/dao/category_dao.php b/api/dao/category_dao.php deleted file mode 100644 index d92ea51c..00000000 --- a/api/dao/category_dao.php +++ /dev/null @@ -1,28 +0,0 @@ -init(); - if (!function_exists('hesk_dbConnect')) { - throw new Exception('Database not loaded!'); - } - hesk_dbConnect(); - - $rs = hesk_dbQuery('SELECT * FROM `' . hesk_dbEscape($hesk_settings['db_pfix']) . 'users` WHERE `id` = ( + $rs = hesk_dbQuery("SELECT * FROM `" . hesk_dbEscape($heskSettings['db_pfix']) . "users` WHERE `id` = ( SELECT `` - FROM `' . hesk_dbEscape($hesk_settings['db_pfix']) . 'user_api_tokens` - WHERE `token` = ' . hesk_dbEscape($hashedToken) . ' - )'); + FROM `" . hesk_dbEscape($heskSettings['db_pfix']) . "user_api_tokens` + WHERE `tokens`.`token` = " . hesk_dbEscape($hashedToken) . " + ) AND `active` = '1'"); + + if (hesk_dbNumRows($rs) === 0) { + return null; + } $row = hesk_dbFetchAssoc($rs); - return UserContextBuilder::fromDataRow($row); + $this->close(); + + return $row; } } \ No newline at end of file diff --git a/api/exception/AccessException.php b/api/exception/AccessException.php deleted file mode 100644 index 7931affe..00000000 --- a/api/exception/AccessException.php +++ /dev/null @@ -1,11 +0,0 @@ -get['UserContextBuilder']; + + $userContext = $userContextBuilder->buildUserContext($xAuthToken, $hesk_settings); } function errorHandler($errorNumber, $errorMessage, $errorFile, $errorLine) { - print_error(sprintf("Uncaught error in %s", $errorFile), $errorMessage); + throw new Exception(sprintf("%s:%d\n\n%s", $errorFile, $errorLine, $errorMessage)); +} + +/** + * @param $exception Exception + */ +function exceptionHandler($exception) { + if (exceptionIsOfType($exception, 'MissingAuthenticationTokenException')) { + print_error("Security Exception", $exception->getMessage(), 400); + } elseif (exceptionIsOfType($exception, 'InvalidAuthenticationTokenException')) { + print_error("Security Exception", $exception->getMessage(), 401); + } else { + print_error("Fought an uncaught exception", sprintf("%s\n\n%s", $exception->getMessage(), $exception->getTraceAsString())); + } + // Log more stuff to logging table if possible; we'll catch any exceptions from this die(); } +/** + * @param $exception Exception thrown exception + * @param $class string The name of the expected exception type + * @return bool + */ +function exceptionIsOfType($exception, $class) { + return strpos(get_class($exception), $class) !== false; +} + function fatalErrorShutdownHandler() { $last_error = error_get_last(); if ($last_error['type'] === E_ERROR) { @@ -27,8 +69,7 @@ function fatalErrorShutdownHandler() { } } -// Must use fully-qualified namespace to controllers -Link::before('assertApiIsEnabled'); +Link::before('before'); Link::all(array( // Categories