diff --git a/api/index.php b/api/index.php new file mode 100644 index 00000000..ac5333bb --- /dev/null +++ b/api/index.php @@ -0,0 +1,43 @@ +. + * + * @package phpservermon + * @author Pepijn Over + * @copyright Copyright (c) 2008-2017 Pepijn Over + * @license http://www.gnu.org/licenses/gpl.txt GNU GPL v3 + * @version Release: @package_version@ + * @link http://www.phpservermonitor.org/ + **/ + +require __DIR__.'/../src/bootstrap.php'; + +$router->setIsApi(true); + +psm_no_cache(); + +$mod = psm_GET('mod'); + +try { + $router->run($mod); +} catch (\InvalidArgumentException $e) { + // invalid module, try the default one + // it that somehow also doesnt exist, we have a bit of an issue + // and we really have no reason catch it + $router->run(PSM_MODULE_DEFAULT); +} \ No newline at end of file diff --git a/src/psm/Module/Server/Controller/ApiStatusController.php b/src/psm/Module/Server/Controller/ApiStatusController.php new file mode 100644 index 00000000..cf910623 --- /dev/null +++ b/src/psm/Module/Server/Controller/ApiStatusController.php @@ -0,0 +1,87 @@ +. + * + * @package phpservermon + * @author Pepijn Over + * @copyright Copyright (c) 2008-2017 Pepijn Over + * @license http://www.gnu.org/licenses/gpl.txt GNU GPL v3 + * @version Release: @package_version@ + * @link http://www.phpservermonitor.org/ + * */ +/** + * Server module. List all servers, return list as JSON. + */ + +namespace psm\Module\Server\Controller; + +use psm\Service\Database; +use Symfony\Component\HttpFoundation\JsonResponse; +use Symfony\Component\HttpFoundation\Response; +use Twig_Environment; + +/** + * Description of ApiStatusController + * + * @author Matej Kminek , 24. 11. 2019 + */ +class ApiStatusController extends AbstractServerController { + + /** + * Current server id + * @var int|\PDOStatement $server_id + */ + protected $server_id; + + function __construct(Database $db, Twig_Environment $twig) { + parent::__construct($db, $twig); + + $this->server_id = isset($_GET['id']) ? intval($_GET['id']) : 0; + + $this->setActions(array('detail', 'list'), 'list'); + } + + /** + * Prepare the view template + */ + protected function executeList() { + $server = $this->getServers(); + + return new JsonResponse($server, Response::HTTP_OK); + } + + /** + * Prepare the view template + */ + protected function executeDetail() { + var_dump($this->server_id); + if(empty($this->server_id)){ + return new JsonResponse("Not found", Response::HTTP_NOT_FOUND); + } + + $server = $this->getServers($this->server_id); + + if (empty($server)) { + return $this->runAction('index'); + } + + return new JsonResponse($server, Response::HTTP_OK); + } + +} diff --git a/src/psm/Module/Server/ServerModule.php b/src/psm/Module/Server/ServerModule.php index 44a5b06e..d78a156a 100644 --- a/src/psm/Module/Server/ServerModule.php +++ b/src/psm/Module/Server/ServerModule.php @@ -43,6 +43,7 @@ class ServerModule implements ModuleInterface { 'log' => __NAMESPACE__.'\Controller\LogController', 'status' => __NAMESPACE__.'\Controller\StatusController', 'update' => __NAMESPACE__.'\Controller\UpdateController', + 'api.status' => __NAMESPACE__.'\Controller\ApiStatusController', ); } diff --git a/src/psm/Router.php b/src/psm/Router.php index 140d95fb..c732aaee 100644 --- a/src/psm/Router.php +++ b/src/psm/Router.php @@ -44,194 +44,240 @@ use Symfony\Component\DependencyInjection\Loader\XmlFileLoader; */ class Router { - /** - * Service container - * @var \Symfony\Component\DependencyInjection\ContainerBuilder $container - */ - protected $container; + /** + * Service container + * @var \Symfony\Component\DependencyInjection\ContainerBuilder $container + */ + protected $container; - public function __construct() { - $this->container = $this->buildServiceContainer(); + /** + * Indication whether this request comes from API + * @var boolean + */ + private $isApi = false; - $mods = $this->container->getParameter('modules'); + public function __construct() { + $this->container = $this->buildServiceContainer(); - foreach ($mods as $mod) { - $mod_loader = $this->container->get($mod); - $mod_loader->load($this->container); - } - } + $mods = $this->container->getParameter('modules'); - /** - * Run a module. - * - * The $mod param is in the format $module_$controller. - * If the "_$controller" part is omitted, it will attempt to load - * the controller with the same name as the module. - * - * @param string $mod - * @throws \InvalidArgumentException - * @throws \LogicException - */ - public function run($mod) { - if (strpos($mod, '_') !== false) { - list($mod, $controller) = explode('_', $mod); - } else { - $controller = $mod; - } - $this->buildTwigEnvironment(); + foreach ($mods as $mod) { + $mod_loader = $this->container->get($mod); + $mod_loader->load($this->container); + } + } - $controller = $this->getController($mod, $controller); - $action = null; + /** + * Run a module. + * + * The $mod param is in the format $module_$controller. + * If the "_$controller" part is omitted, it will attempt to load + * the controller with the same name as the module. + * + * @param string $mod + * @throws \InvalidArgumentException + * @throws \LogicException + */ + public function run($mod) { + if (strpos($mod, '_') !== false) { + list($mod, $controller) = explode('_', $mod); + } else { + $controller = $mod; + } + $this->buildTwigEnvironment(); - try { - $this->validateRequest($controller); - } catch (\InvalidArgumentException $ex) { - switch ($ex->getMessage()) { - case 'login_required': - $controller = $this->getController('user', 'login'); - break; - case 'invalid_csrf_token': - case 'invalid_user_level': - default: - $controller = $this->getController('error'); - $action = '401'; - break; - } - } + $controller = $this->isApi ? $this->getController($mod, $controller, true) : $this->getController($mod, $controller); + $action = null; - $response = $controller->run($action); + try { + $this->validateRequest($controller); + } catch (\InvalidArgumentException $ex) { + switch ($ex->getMessage()) { + case 'invalid_api_key': + $controller = $this->getController('error'); + $action = '403'; + break; + case 'login_required': + $controller = $this->getController('user', 'login'); + break; + case 'invalid_csrf_token': + case 'invalid_user_level': + default: + $controller = $this->getController('error'); + $action = '401'; + break; + } + } - if (!($response instanceof Response)) { - throw new \LogicException('Controller did not return a Response object.'); - } - $response->send(); - } + $response = $controller->run($action); - /** - * Get an instance of the requested controller. - * @param string $module_id - * @param string $controller_id if NULL, default controller will be used - * @return \psm\Module\ControllerInterface - * @throws \InvalidArgumentException - */ - public function getController($module_id, $controller_id = null) { - if ($controller_id === null) { - // by default, we use the controller with the same id as the module. - $controller_id = $module_id; - } + if (!($response instanceof Response)) { + throw new \LogicException('Controller did not return a Response object.'); + } + $response->send(); + } + + /** + * Get an instance of the requested controller. + * @param string $module_id + * @param string $controller_id if NULL, default controller will be used + * @return \psm\Module\ControllerInterface + * @throws \InvalidArgumentException + */ + public function getController($module_id, $controller_id = null, $isApi = false) { + if ($controller_id === null) { + // by default, we use the controller with the same id as the module. + $controller_id = $module_id; + } + + if ($isApi) { + // if this is an api request, serve api.controller + $controller_id = "api." . $controller_id; + } $module = $this->container->get('module.'.$module_id); - $controllers = $module->getControllers(); - if (!isset($controllers[$controller_id]) || !class_exists($controllers[$controller_id])) { + $controllers = $module->getControllers(); + if (!isset($controllers[$controller_id]) || !class_exists($controllers[$controller_id])) { throw new \InvalidArgumentException('Controller "'.$controller_id.'" is not registered or does not exist.'); - } - $controller = new $controllers[$controller_id]( - $this->container->get('db'), - $this->container->get('twig') - ); + } + $controller = new $controllers[$controller_id]( + $this->container->get('db'), + $this->container->get('twig') + ); - if (!$controller instanceof \psm\Module\ControllerInterface) { - throw new \Exception('Controller does not implement ControllerInterface'); - } - $controller->setContainer($this->container); + if (!$controller instanceof \psm\Module\ControllerInterface) { + throw new \Exception('Controller does not implement ControllerInterface'); + } + $controller->setContainer($this->container); - return $controller; - } + return $controller; + } - /** - * Get service from container - * @param string $id - * @return mixed FALSE on failure, service otherwise - * @throws \InvalidArgumentException - */ - public function getService($id) { - return $this->container->get($id); - } + /** + * Get service from container + * @param string $id + * @return mixed FALSE on failure, service otherwise + * @throws \InvalidArgumentException + */ + public function getService($id) { + return $this->container->get($id); + } - /** - * Validate requets before heading to a controller - * @param \psm\Module\ControllerInterface $controller - * @throws \InvalidArgumentException - */ - protected function validateRequest(\psm\Module\ControllerInterface $controller) { - $request = Request::createFromGlobals(); + /** + * Validate requets before heading to a controller + * @param \psm\Module\ControllerInterface $controller + * @throws \InvalidArgumentException + */ + protected function validateRequest(\psm\Module\ControllerInterface $controller) { + $request = Request::createFromGlobals(); + + if($this->isApi){ + return $this->validateApiRequest($controller, $request); + } - if ($request->getMethod() == 'POST') { - // require CSRF token for all POST calls - $session = $this->container->get('user')->getSession(); - $token_in = $request->request->get('csrf', ''); - $csrf_key = $controller->getCSRFKey(); + if ($request->getMethod() == 'POST') { + // require CSRF token for all POST calls + $session = $this->container->get('user')->getSession(); + $token_in = $request->request->get('csrf', ''); + $csrf_key = $controller->getCSRFKey(); - if (empty($csrf_key)) { - if (!hash_equals($session->get('csrf_token'), $token_in)) { - throw new \InvalidArgumentException('invalid_csrf_token'); - } - } else { - if (!hash_equals( - hash_hmac('sha256', $csrf_key, $session->get('csrf_token2')), - $token_in - )) { - throw new \InvalidArgumentException('invalid_csrf_token'); - } - } - } + if (empty($csrf_key)) { + if (!hash_equals($session->get('csrf_token'), $token_in)) { + throw new \InvalidArgumentException('invalid_csrf_token'); + } + } else { + if (!hash_equals( + hash_hmac('sha256', $csrf_key, $session->get('csrf_token2')), + $token_in + )) { + throw new \InvalidArgumentException('invalid_csrf_token'); + } + } + } - // get min required level for this controller and make sure the user matches - $min_lvl = $controller->getMinUserLevelRequired(); + // get min required level for this controller and make sure the user matches + $min_lvl = $controller->getMinUserLevelRequired(); - if ($min_lvl < PSM_USER_ANONYMOUS) { - // if user is not logged in, load login module - if (!$this->container->get('user')->isUserLoggedIn()) { - throw new \InvalidArgumentException('login_required'); - } elseif ($this->container->get('user')->getUserLevel() > $min_lvl) { - throw new \InvalidArgumentException('invalid_user_level'); - } - } - } + if ($min_lvl < PSM_USER_ANONYMOUS) { + // if user is not logged in, load login module + if (!$this->container->get('user')->isUserLoggedIn()) { + throw new \InvalidArgumentException('login_required'); + } elseif ($this->container->get('user')->getUserLevel() > $min_lvl) { + throw new \InvalidArgumentException('invalid_user_level'); + } + } + } + + private function validateApiRequest(\psm\Module\ControllerInterface $controller, Request $request) { + $result = $controller->getUser()->loginWithApiKey($request->query->get("key")); + + if (!$result) { + throw new \InvalidArgumentException('invalid_api_key'); + } + + // get min required level for this controller and make sure the user matches + $min_lvl = $controller->getMinUserLevelRequired(); + if ($min_lvl < PSM_USER_ANONYMOUS) { + // if user is not logged in, load login module + if ($this->container->get('user')->getUserLevel() > $min_lvl) { + throw new \InvalidArgumentException('invalid_user_level'); + } + } + } - /** - * Build a new service container - * @return \Symfony\Component\DependencyInjection\ContainerBuilder - * @throws \InvalidArgumentException - */ - protected function buildServiceContainer() { - $builder = new ContainerBuilder(); - $loader = new XmlFileLoader($builder, new FileLocator(PSM_PATH_CONFIG)); - $loader->load('services.xml'); + /** + * Build a new service container + * @return \Symfony\Component\DependencyInjection\ContainerBuilder + * @throws \InvalidArgumentException + */ + protected function buildServiceContainer() { + $builder = new ContainerBuilder(); + $loader = new XmlFileLoader($builder, new FileLocator(PSM_PATH_CONFIG)); + $loader->load('services.xml'); - return $builder; - } + return $builder; + } - /** - * Prepare twig environment - * @return \Twig_Environment - */ - protected function buildTwigEnvironment() { - $twig = $this->container->get('twig'); - $session = $this->container->get('user')->getSession(); - if (!$session->has('csrf_token')) { - $session->set('csrf_token', bin2hex(random_bytes(32))); - } - if (!$session->has('csrf_token2')) { - $session->set('csrf_token2', random_bytes(32)); - } + /** + * Prepare twig environment + * @return \Twig_Environment + */ + protected function buildTwigEnvironment() { + $twig = $this->container->get('twig'); + $session = $this->container->get('user')->getSession(); + if (!$session->has('csrf_token')) { + $session->set('csrf_token', bin2hex(random_bytes(32))); + } + if (!$session->has('csrf_token2')) { + $session->set('csrf_token2', random_bytes(32)); + } - $twig->addFunction( - new \Twig_SimpleFunction( - 'csrf_token', - function($lock_to = null) use ($session) { - if (empty($lock_to)) { - return $session->get('csrf_token'); - } - return hash_hmac('sha256', $lock_to, $session->get('csrf_token2')); - } - ) - ); - $twig->addGlobal('direction_current', psm_get_lang('locale_dir')); - $twig->addGlobal('language_current', psm_get_lang('locale_tag')); - $twig->addGlobal('language', psm_get_lang('locale')[1]); + $twig->addFunction( + new \Twig_SimpleFunction( + 'csrf_token', + function($lock_to = null) use ($session) { + if (empty($lock_to)) { + return $session->get('csrf_token'); + } + return hash_hmac('sha256', $lock_to, $session->get('csrf_token2')); + } + ) + ); + $twig->addGlobal('direction_current', psm_get_lang('locale_dir')); + $twig->addGlobal('language_current', psm_get_lang('locale_tag')); + $twig->addGlobal('language', psm_get_lang('locale')[1]); - return $twig; - } -} \ No newline at end of file + return $twig; + } + + public function getIsApi() { + return $this->isApi; + } + + public function setIsApi($isApi) { + $this->isApi = $isApi; + return $this; + } + +} diff --git a/src/psm/Service/User.php b/src/psm/Service/User.php index 6906fd93..7a85e73f 100644 --- a/src/psm/Service/User.php +++ b/src/psm/Service/User.php @@ -147,6 +147,23 @@ class User { return $query_user->fetchObject(); } + /** + * Search into database for the user data of api key specified as parameter + * @return object|boolean user data as an object if existing user + */ + public function getUserByApiKey($key) { + if(empty($key)){ + return null; + } + + // database query, getting all the info of the selected user + $query_user = $this->db_connection->prepare('SELECT * FROM '.PSM_DB_PREFIX.'users WHERE api_hash = :api_hash'); + $query_user->bindValue(':api_hash', $key, \PDO::PARAM_STR); + $query_user->execute(); + // get result row (as an object) + return $query_user->fetchObject(); + } + /** * Logs in with SESSION data. * @@ -169,6 +186,27 @@ class User { } } + /** + * Logs in via the api key + * @return bool success state of cookie login + */ + public function loginWithApiKey($key) { + $apiKey = trim($key); + + if (empty($apiKey)) { + return false; + } + $user = $this->getUserByApiKey($apiKey); + + if (empty($user)) { + return false; + } + + $this->setUserLoggedIn($user->user_id, true); + + return true; + } + /** * Logs in via the Cookie * @return bool success state of cookie login diff --git a/src/psm/Util/Install/Installer.php b/src/psm/Util/Install/Installer.php index 7420c367..9de3ec6f 100644 --- a/src/psm/Util/Install/Installer.php +++ b/src/psm/Util/Install/Installer.php @@ -186,6 +186,7 @@ class Installer { `password_reset_hash` char(40) DEFAULT NULL COMMENT 'user''s password reset code', `password_reset_timestamp` bigint(20) DEFAULT NULL COMMENT 'timestamp of the password reset request', `rememberme_token` varchar(64) DEFAULT NULL COMMENT 'user''s remember-me cookie token', + `api_hash` varchar(255) DEFAULT NULL COMMENT 'user''s hash key to validate API requests', `level` tinyint(2) unsigned NOT NULL DEFAULT '20', `name` varchar(255) NOT NULL, `mobile` varchar(15) NOT NULL,