API interface and first basic API request:

https://phpservermonitor.com/api/?&mod=server_status&key=123456789
https://phpservermonitor.com/api/?&mod=server_status&key=123456789&action=detail&id=14

Created api folder and booter. User database row enhanced by api_auth field, to store user hashcode. Updated router, to hold information whether this is an api request and if is, load specific controller. Then use standard executeMethod to get proper JsonResponse.

First implemented API to get list of servers / one server detail
pull/812/head
Matej Kminek 2019-11-24 21:48:19 +01:00
parent a5312265f8
commit b7c1c4a29e
6 changed files with 381 additions and 165 deletions

43
api/index.php Normal file
View File

@ -0,0 +1,43 @@
<?php
/**
* PHP Server Monitor
* Monitor your servers and websites.
*
* This file is part of PHP Server Monitor.
* PHP Server Monitor is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* PHP Server Monitor is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with PHP Server Monitor. If not, see <http://www.gnu.org/licenses/>.
*
* @package phpservermon
* @author Pepijn Over <pep@mailbox.org>
* @copyright Copyright (c) 2008-2017 Pepijn Over <pep@mailbox.org>
* @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);
}

View File

@ -0,0 +1,87 @@
<?php
/**
* PHP Server Monitor
* Monitor your servers and websites.
*
* This file is part of PHP Server Monitor.
* PHP Server Monitor is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* PHP Server Monitor is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with PHP Server Monitor. If not, see <http://www.gnu.org/licenses/>.
*
* @package phpservermon
* @author Pepijn Over <pep@mailbox.org>
* @copyright Copyright (c) 2008-2017 Pepijn Over <pep@mailbox.org>
* @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 <matej.kminek@attendees.eu>, 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);
}
}

View File

@ -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',
);
}

View File

@ -50,6 +50,12 @@ class Router {
*/
protected $container;
/**
* Indication whether this request comes from API
* @var boolean
*/
private $isApi = false;
public function __construct() {
$this->container = $this->buildServiceContainer();
@ -80,13 +86,17 @@ class Router {
}
$this->buildTwigEnvironment();
$controller = $this->getController($mod, $controller);
$controller = $this->isApi ? $this->getController($mod, $controller, true) : $this->getController($mod, $controller);
$action = null;
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;
@ -114,12 +124,17 @@ class Router {
* @return \psm\Module\ControllerInterface
* @throws \InvalidArgumentException
*/
public function getController($module_id, $controller_id = null) {
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])) {
@ -156,6 +171,10 @@ class Router {
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();
@ -189,6 +208,23 @@ class Router {
}
}
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
@ -234,4 +270,14 @@ class Router {
return $twig;
}
public function getIsApi() {
return $this->isApi;
}
public function setIsApi($isApi) {
$this->isApi = $isApi;
return $this;
}
}

View File

@ -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

View File

@ -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,