new: [internal] Move REST client to new Api controller

pull/8269/head
Jakub Onderka 2022-04-10 10:18:28 +02:00
parent 2b61648184
commit 6431f286c6
12 changed files with 334 additions and 314 deletions

View File

@ -0,0 +1,301 @@
<?php
App::uses('AppController', 'Controller');
class ApiController extends AppController
{
public function beforeFilter()
{
parent::beforeFilter();
$this->Security->unlockedActions[] = 'getApiInfo';
}
public function openapi()
{
}
public function viewDeprecatedFunctionUse()
{
$server = ClassRegistry::init('Server');
$data = $this->Deprecation->getDeprecatedAccessList($server);
if ($this->_isRest()) {
return $this->RestResponse->viewData($data, $this->response->type());
} else {
$this->layout = false;
$this->set('data', $data);
}
}
public function getApiInfo()
{
$relative_path = $this->request->data['url'];
$result = $this->RestResponse->getApiInfo($relative_path);
if ($this->_isRest()) {
if (!empty($result)) {
$result['api_info'] = $result;
}
return $this->RestResponse->viewData($result, $this->response->type());
} else {
if (empty($result)) {
return $this->RestResponse->viewData('&nbsp;', $this->response->type());
}
$this->layout = false;
$this->autoRender = false;
$this->set('api_info', $result);
$this->render('ajax/get_api_info');
}
}
public function rest()
{
$allValidApis = $this->RestResponse->getAllApis($this->Auth->user());
$allValidApisFieldsContraint = $this->RestResponse->getAllApisFieldsConstraint($this->Auth->user());
if ($this->request->is('post')) {
$request = $this->request->data;
if (!empty($request['Server'])) {
$request = $this->request->data['Server'];
}
$curl = '';
$python = '';
try {
$result = $this->__doRestQuery($request, $curl, $python);
$this->set('curl', $curl);
$this->set('python', $python);
if (!$result) {
$this->Flash->error('Something went wrong. Make sure you set the http method, body (when sending POST requests) and URL correctly.');
} else {
$this->set('data', $result);
}
} catch (Exception $e) {
$this->Flash->error(__('Something went wrong. %s', $e->getMessage()));
}
}
$header = sprintf(
"Authorization: %s \nAccept: application/json\nContent-type: application/json",
__('YOUR_API_KEY')
);
$this->set('header', $header);
$this->set('allValidApis', $allValidApis);
// formating for optgroup
$allValidApisFormated = array();
foreach ($allValidApis as $endpoint_url => $endpoint_data) {
$allValidApisFormated[$endpoint_data['controller']][] = array('url' => $endpoint_url, 'action' => $endpoint_data['action']);
}
$this->set('allValidApisFormated', $allValidApisFormated);
$this->set('allValidApisFieldsContraint', $allValidApisFieldsContraint);
}
/**
* @param array $request
* @param string $curl
* @param string $python
* @return array|false
*/
private function __doRestQuery(array $request, &$curl = false, &$python = false)
{
App::uses('SyncTool', 'Tools');
$params = array();
$logHeaders = $request['header'];
if (!empty(Configure::read('Security.advanced_authkeys'))) {
$logHeaders = explode("\n", $request['header']);
foreach ($logHeaders as $k => $header) {
if (strpos($header, 'Authorization') !== false) {
$logHeaders[$k] = 'Authorization: ' . __('YOUR_API_KEY');
}
}
$logHeaders = implode("\n", $logHeaders);
}
if (empty($request['body'])) {
$historyBody = '';
} else if (strlen($request['body']) > 65535) {
$historyBody = ''; // body is too long to save into history table
} else {
$historyBody = $request['body'];
}
$rest_history_item = array(
'org_id' => $this->Auth->user('org_id'),
'user_id' => $this->Auth->user('id'),
'headers' => $logHeaders,
'body' => $historyBody,
'url' => $request['url'],
'http_method' => $request['method'],
'use_full_path' => empty($request['use_full_path']) ? false : $request['use_full_path'],
'show_result' => $request['show_result'],
'skip_ssl' => $request['skip_ssl_validation'],
'bookmark' => $request['bookmark'],
'bookmark_name' => $request['name'],
'timestamp' => time(),
);
if (!empty($request['url'])) {
if (empty($request['use_full_path']) || empty(Configure::read('Security.rest_client_enable_arbitrary_urls'))) {
$path = preg_replace('#^(://|[^/?])+#', '', $request['url']);
$url = empty(Configure::read('Security.rest_client_baseurl')) ? (Configure::read('MISP.baseurl') . $path) : (Configure::read('Security.rest_client_baseurl') . $path);
unset($request['url']);
} else {
$url = $request['url'];
}
} else {
throw new InvalidArgumentException('URL not set.');
}
if (!empty($request['skip_ssl_validation'])) {
$params['ssl_verify_peer'] = false;
$params['ssl_verify_host'] = false;
$params['ssl_verify_peer_name'] = false;
$params['ssl_allow_self_signed'] = true;
}
$params['timeout'] = 300;
App::uses('HttpSocket', 'Network/Http');
$HttpSocket = new HttpSocket($params);
$temp_headers = empty($request['header']) ? [] : explode("\n", $request['header']);
$request['header'] = array(
'Accept' => 'application/json',
'Content-Type' => 'application/json',
'User-Agent' => 'MISP REST Client',
);
foreach ($temp_headers as $header) {
$header = explode(':', $header);
$header[0] = trim($header[0]);
$header[1] = trim($header[1]);
$request['header'][$header[0]] = $header[1];
}
$start = microtime(true);
if (
!empty($request['method']) &&
$request['method'] === 'GET'
) {
if ($curl !== false) {
$curl = $this->__generateCurlQuery('get', $request, $url);
}
if ($python !== false) {
$python = $this->__generatePythonScript($request, $url);
}
$response = $HttpSocket->get($url, false, array('header' => $request['header']));
} elseif (
!empty($request['method']) &&
$request['method'] === 'POST' &&
!empty($request['body'])
) {
if ($curl !== false) {
$curl = $this->__generateCurlQuery('post', $request, $url);
}
if ($python !== false) {
$python = $this->__generatePythonScript($request, $url);
}
$response = $HttpSocket->post($url, $request['body'], array('header' => $request['header']));
} elseif (
!empty($request['method']) &&
$request['method'] === 'DELETE'
) {
if ($curl !== false) {
$curl = $this->__generateCurlQuery('delete', $request, $url);
}
if ($python !== false) {
$python = $this->__generatePythonScript($request, $url);
}
$response = $HttpSocket->delete($url, false, array('header' => $request['header']));
} else {
return false;
}
$viewData = [
'duration' => round((microtime(true) - $start) * 1000, 2) . ' ms',
'url' => $url,
'code' => $response->code,
'headers' => $response->headers,
];
if (!empty($request['show_result'])) {
$viewData['data'] = $response->body;
} else {
if ($response->isOk()) {
$viewData['data'] = 'Success.';
} else {
$viewData['data'] = 'Something went wrong.';
}
}
$rest_history_item['outcome'] = $response->code;
$this->loadModel('RestClientHistory');
$this->RestClientHistory->create();
$this->RestClientHistory->save($rest_history_item);
$this->RestClientHistory->cleanup($this->Auth->user('id'));
return $viewData;
}
private function __generatePythonScript(array $request, $url)
{
$slashCounter = 0;
$baseurl = '';
$relative = '';
$verifyCert = ($url[4] === 's') ? 'True' : 'False';
for ($i = 0; $i < strlen($url); $i++) {
//foreach ($url as $url[$i]) {
if ($url[$i] === '/') {
$slashCounter += 1;
if ($slashCounter == 3) {
continue;
}
}
if ($slashCounter < 3) {
$baseurl .= $url[$i];
} else {
$relative .= $url[$i];
}
}
$python_script =
sprintf(
'misp_url = \'%s\'
misp_key = \'%s\'
misp_verifycert = %s
relative_path = \'%s\'
body = %s
from pymisp import ExpandedPyMISP
misp = ExpandedPyMISP(misp_url, misp_key, misp_verifycert)
misp.direct_call(relative_path, body)
',
$baseurl,
$request['header']['Authorization'],
$verifyCert,
$relative,
(empty($request['body']) ? 'None' : $request['body'])
);
return $python_script;
}
private function __generateCurlQuery($type, array $request, $url)
{
if ($type === 'get') {
$curl = sprintf(
'curl \%s -H "Authorization: %s" \%s -H "Accept: %s" \%s -H "Content-type: %s" \%s %s',
PHP_EOL,
$request['header']['Authorization'],
PHP_EOL,
$request['header']['Accept'],
PHP_EOL,
$request['header']['Content-Type'],
PHP_EOL,
$url
);
} else {
$curl = sprintf(
'curl \%s -d \'%s\' \%s -H "Authorization: %s" \%s -H "Accept: %s" \%s -H "Content-type: %s" \%s -X POST %s',
PHP_EOL,
json_encode(json_decode($request['body'])),
PHP_EOL,
$request['header']['Authorization'],
PHP_EOL,
$request['header']['Accept'],
PHP_EOL,
$request['header']['Content-Type'],
PHP_EOL,
$url
);
}
return $curl;
}
}

View File

@ -19,6 +19,12 @@ class ACLComponent extends Component
'queryACL' => array(),
'restSearch' => array('*'),
),
'api' => [
'rest' => ['perm_auth'],
'viewDeprecatedFunctionUse' => [],
'openapi' => ['*'],
'getApiInfo' => ['*'],
],
'attributes' => array(
'add' => array('perm_add'),
'add_attachment' => array('perm_add'),
@ -505,7 +511,6 @@ class ACLComponent extends Component
'eventBlockRule' => array(),
'fetchServersForSG' => array('perm_sharing_group'),
'filterEventIndex' => array(),
'getApiInfo' => array('*'),
'getAvailableSyncFilteringRules' => array('*'),
'getInstanceUUID' => array('perm_sync'),
'getPyMISPVersion' => array('*'),
@ -531,8 +536,6 @@ class ACLComponent extends Component
'releaseUpdateLock' => array(),
'resetRemoteAuthKey' => array(),
'removeOrphanedCorrelations' => array(),
'rest' => array('perm_auth'),
'openapi' => array('*'),
'restartDeadWorkers' => array(),
'restartWorkers' => array(),
'serverSettings' => array(),
@ -549,7 +552,6 @@ class ACLComponent extends Component
'updateProgress' => array(),
'updateSubmodule' => array(),
'uploadFile' => array(),
'viewDeprecatedFunctionUse' => array(),
'killAllWorkers' => [],
'cspReport' => ['*'],
'pruneDuplicateUUIDs' => array(),

View File

@ -39,7 +39,6 @@ class ServersController extends AppController
$this->Auth->allow(['cspReport']); // cspReport must work without authentication
parent::beforeFilter();
$this->Security->unlockedActions[] = 'getApiInfo';
$this->Security->unlockedActions[] = 'cspReport';
// permit reuse of CSRF tokens on some pages.
switch ($this->request->params['action']) {
@ -1972,280 +1971,6 @@ class ServersController extends AppController
return $this->RestResponse->viewData(array('uuid' => Configure::read('MISP.uuid')), $this->response->type());
}
public function rest()
{
$allValidApis = $this->RestResponse->getAllApis($this->Auth->user());
$allValidApisFieldsContraint = $this->RestResponse->getAllApisFieldsConstraint($this->Auth->user());
if ($this->request->is('post')) {
$request = $this->request->data;
if (!empty($request['Server'])) {
$request = $this->request->data['Server'];
}
$curl = '';
$python = '';
try {
$result = $this->__doRestQuery($request, $curl, $python);
$this->set('curl', $curl);
$this->set('python', $python);
if (!$result) {
$this->Flash->error('Something went wrong. Make sure you set the http method, body (when sending POST requests) and URL correctly.');
} else {
$this->set('data', $result);
}
} catch (Exception $e) {
$this->Flash->error(__('Something went wrong. %s', $e->getMessage()));
}
}
$header = sprintf(
"Authorization: %s \nAccept: application/json\nContent-type: application/json",
__('YOUR_API_KEY')
);
$this->set('header', $header);
$this->set('allValidApis', $allValidApis);
// formating for optgroup
$allValidApisFormated = array();
foreach ($allValidApis as $endpoint_url => $endpoint_data) {
$allValidApisFormated[$endpoint_data['controller']][] = array('url' => $endpoint_url, 'action' => $endpoint_data['action']);
}
$this->set('allValidApisFormated', $allValidApisFormated);
$this->set('allValidApisFieldsContraint', $allValidApisFieldsContraint);
}
/**
* @param array $request
* @param string $curl
* @param string $python
* @return array|false
*/
private function __doRestQuery(array $request, &$curl = false, &$python = false)
{
App::uses('SyncTool', 'Tools');
$params = array();
$logHeaders = $request['header'];
if (!empty(Configure::read('Security.advanced_authkeys'))) {
$logHeaders = explode("\n", $request['header']);
foreach ($logHeaders as $k => $header) {
if (strpos($header, 'Authorization') !== false) {
$logHeaders[$k] = 'Authorization: ' . __('YOUR_API_KEY');
}
}
$logHeaders = implode("\n", $logHeaders);
}
if (empty($request['body'])) {
$historyBody = '';
} else if (strlen($request['body']) > 65535) {
$historyBody = ''; // body is too long to save into history table
} else {
$historyBody = $request['body'];
}
$rest_history_item = array(
'org_id' => $this->Auth->user('org_id'),
'user_id' => $this->Auth->user('id'),
'headers' => $logHeaders,
'body' => $historyBody,
'url' => $request['url'],
'http_method' => $request['method'],
'use_full_path' => empty($request['use_full_path']) ? false : $request['use_full_path'],
'show_result' => $request['show_result'],
'skip_ssl' => $request['skip_ssl_validation'],
'bookmark' => $request['bookmark'],
'bookmark_name' => $request['name'],
'timestamp' => time(),
);
if (!empty($request['url'])) {
if (empty($request['use_full_path']) || empty(Configure::read('Security.rest_client_enable_arbitrary_urls'))) {
$path = preg_replace('#^(://|[^/?])+#', '', $request['url']);
$url = empty(Configure::read('Security.rest_client_baseurl')) ? (Configure::read('MISP.baseurl') . $path) : (Configure::read('Security.rest_client_baseurl') . $path);
unset($request['url']);
} else {
$url = $request['url'];
}
} else {
throw new InvalidArgumentException('URL not set.');
}
if (!empty($request['skip_ssl_validation'])) {
$params['ssl_verify_peer'] = false;
$params['ssl_verify_host'] = false;
$params['ssl_verify_peer_name'] = false;
$params['ssl_allow_self_signed'] = true;
}
$params['timeout'] = 300;
App::uses('HttpSocket', 'Network/Http');
$HttpSocket = new HttpSocket($params);
$temp_headers = empty($request['header']) ? [] : explode("\n", $request['header']);
$request['header'] = array(
'Accept' => 'application/json',
'Content-Type' => 'application/json',
'User-Agent' => 'MISP REST Client',
);
foreach ($temp_headers as $header) {
$header = explode(':', $header);
$header[0] = trim($header[0]);
$header[1] = trim($header[1]);
$request['header'][$header[0]] = $header[1];
}
$start = microtime(true);
if (
!empty($request['method']) &&
$request['method'] === 'GET'
) {
if ($curl !== false) {
$curl = $this->__generateCurlQuery('get', $request, $url);
}
if ($python !== false) {
$python = $this->__generatePythonScript($request, $url);
}
$response = $HttpSocket->get($url, false, array('header' => $request['header']));
} elseif (
!empty($request['method']) &&
$request['method'] === 'POST' &&
!empty($request['body'])
) {
if ($curl !== false) {
$curl = $this->__generateCurlQuery('post', $request, $url);
}
if ($python !== false) {
$python = $this->__generatePythonScript($request, $url);
}
$response = $HttpSocket->post($url, $request['body'], array('header' => $request['header']));
} elseif (
!empty($request['method']) &&
$request['method'] === 'DELETE'
) {
if ($curl !== false) {
$curl = $this->__generateCurlQuery('delete', $request, $url);
}
if ($python !== false) {
$python = $this->__generatePythonScript($request, $url);
}
$response = $HttpSocket->delete($url, false, array('header' => $request['header']));
} else {
return false;
}
$viewData = [
'duration' => round((microtime(true) - $start) * 1000, 2) . ' ms',
'url' => $url,
'code' => $response->code,
'headers' => $response->headers,
];
if (!empty($request['show_result'])) {
$viewData['data'] = $response->body;
} else {
if ($response->isOk()) {
$viewData['data'] = 'Success.';
} else {
$viewData['data'] = 'Something went wrong.';
}
}
$rest_history_item['outcome'] = $response->code;
$this->loadModel('RestClientHistory');
$this->RestClientHistory->create();
$this->RestClientHistory->save($rest_history_item);
$this->RestClientHistory->cleanup($this->Auth->user('id'));
return $viewData;
}
private function __generatePythonScript($request, $url)
{
$slashCounter = 0;
$baseurl = '';
$relative = '';
$verifyCert = ($url[4] === 's') ? 'True' : 'False';
for ($i = 0; $i < strlen($url); $i++) {
//foreach ($url as $url[$i]) {
if ($url[$i] === '/') {
$slashCounter += 1;
if ($slashCounter == 3) {
continue;
}
}
if ($slashCounter < 3) {
$baseurl .= $url[$i];
} else {
$relative .= $url[$i];
}
}
$python_script =
sprintf(
'misp_url = \'%s\'
misp_key = \'%s\'
misp_verifycert = %s
relative_path = \'%s\'
body = %s
from pymisp import ExpandedPyMISP
misp = ExpandedPyMISP(misp_url, misp_key, misp_verifycert)
misp.direct_call(relative_path, body)
',
$baseurl,
$request['header']['Authorization'],
$verifyCert,
$relative,
(empty($request['body']) ? 'None' : $request['body'])
);
return $python_script;
}
private function __generateCurlQuery($type, $request, $url)
{
if ($type === 'get') {
$curl = sprintf(
'curl \%s -H "Authorization: %s" \%s -H "Accept: %s" \%s -H "Content-type: %s" \%s %s',
PHP_EOL,
$request['header']['Authorization'],
PHP_EOL,
$request['header']['Accept'],
PHP_EOL,
$request['header']['Content-Type'],
PHP_EOL,
$url
);
} else {
$curl = sprintf(
'curl \%s -d \'%s\' \%s -H "Authorization: %s" \%s -H "Accept: %s" \%s -H "Content-type: %s" \%s -X POST %s',
PHP_EOL,
json_encode(json_decode($request['body'])),
PHP_EOL,
$request['header']['Authorization'],
PHP_EOL,
$request['header']['Accept'],
PHP_EOL,
$request['header']['Content-Type'],
PHP_EOL,
$url
);
}
return $curl;
}
public function getApiInfo()
{
$relative_path = $this->request->data['url'];
$result = $this->RestResponse->getApiInfo($relative_path);
if ($this->_isRest()) {
if (!empty($result)) {
$result['api_info'] = $result;
}
return $this->RestResponse->viewData($result, $this->response->type());
} else {
if (empty($result)) {
return $this->RestResponse->viewData('&nbsp;', $this->response->type());
}
$this->layout = false;
$this->autoRender = false;
$this->set('api_info', $result);
$this->render('ajax/get_api_info');
}
}
public function cache($id = 'all')
{
if (Configure::read('MISP.background_jobs')) {
@ -2501,17 +2226,6 @@ misp.direct_call(relative_path, body)
return new CakeResponse(['status' => 204]);
}
public function viewDeprecatedFunctionUse()
{
$data = $this->Deprecation->getDeprecatedAccessList($this->Server);
if ($this->_isRest()) {
return $this->RestResponse->viewData($data, $this->response->type());
} else {
$this->layout = false;
$this->set('data', $data);
}
}
/**
* List all tags for the rule picker.
*
@ -2566,9 +2280,6 @@ misp.direct_call(relative_path, body)
return $this->RestResponse->viewData($syncFilteringRules);
}
public function openapi() {
}
public function pruneDuplicateUUIDs()
{
if (!$this->request->is('post')) {
@ -2738,4 +2449,13 @@ misp.direct_call(relative_path, body)
}
return $this->RestResponse->viewData($users, $this->response->type());
}
/**
* @deprecated
* @return void
*/
public function rest()
{
$this->redirect(['controller' => 'api', 'action' => 'rest']);
}
}

View File

@ -1,6 +1,6 @@
<div class="dashboard_element">
<h4 class="blue bold">API info</h4>
<?php
echo '<h4 class="blue bold">API info</h4>';
foreach ($api_info as $key => $value) {
if (!empty($value)) {
if (is_array($value)) {

View File

@ -340,10 +340,10 @@ $divider = $this->element('/genericElements/SideMenu/side_menu_divider');
),
'text' => __('Import from…')
));
if ($canAccess('servers', 'rest')) {
if ($canAccess('api', 'rest')) {
echo $this->element('/genericElements/SideMenu/side_menu_link', array(
'element_id' => 'rest',
'url' => $baseurl . '/servers/rest',
'url' => $baseurl . '/api/rest',
'text' => __('REST client')
));
}
@ -1609,17 +1609,15 @@ $divider = $this->element('/genericElements/SideMenu/side_menu_divider');
case 'api':
echo $this->element('/genericElements/SideMenu/side_menu_link', array(
'element_id' => 'openapi',
'url' => $baseurl . '/servers/openapi',
'url' => $baseurl . '/api/openapi',
'text' => __('OpenAPI')
));
if ($isAclAdd) {
if ($canAccess('servers', 'rest')) {
echo $this->element('/genericElements/SideMenu/side_menu_link', array(
'element_id' => 'rest',
'url' => $baseurl . '/servers/rest',
'text' => __('REST client')
));
}
if ($canAccess('api', 'rest')) {
echo $this->element('/genericElements/SideMenu/side_menu_link', array(
'element_id' => 'rest',
'url' => $baseurl . '/api/rest',
'text' => __('REST client')
));
}
break;
}

View File

@ -488,12 +488,12 @@
'children' => array(
array(
'text' => __('OpenAPI'),
'url' => $baseurl . '/servers/openapi'
'url' => $baseurl . '/api/openapi'
),
array(
'text' => __('REST client'),
'url' => $baseurl . '/servers/rest',
'requirement' => $canAccess('servers', 'rest')
'url' => $baseurl . '/api/rest',
'requirement' => $canAccess('api', 'rest')
)
)
)

View File

@ -6,7 +6,7 @@
<p class="bold"><?php echo __('Check out the OpenAPI spec of the MISP Automation API <a href="%s">here</a>.', $baseurl . '/servers/openapi');?></p>
<p><?php echo __('Automation functionality is designed to automatically feed other tools and systems with the data in your MISP repository.
To to make this functionality available for automated tools an authentication key is used.');?>
<br /><?php echo __('You can use the <a href="' . $baseurl . '/servers/rest">REST client</a> to test your API queries against your MISP and export the resulting tuned queries as curl or python scripts.');?>
<br /><?php echo __('You can use the <a href="' . $baseurl . '/api/rest">REST client</a> to test your API queries against your MISP and export the resulting tuned queries as curl or python scripts.');?>
<strong><?php echo __('Make sure you keep your API key secret as it gives access to the all of the data that you normally have access to in MISP.');?></strong>
<?php echo __('To view the old MISP automation page, click <a href="' . $baseurl . '/automation/1">here</a>.');?>
</p>

View File

@ -5057,12 +5057,12 @@ function checkRoleEnforceRateLimit() {
function queryDeprecatedEndpointUsage() {
$.ajax({
url: baseurl + '/servers/viewDeprecatedFunctionUse',
url: baseurl + '/api/viewDeprecatedFunctionUse',
type: 'GET',
success: function(data) {
$('#deprecationResults').html(data);
},
error: function(data) {
error: function() {
handleGenericAjaxResponse({'saved':false, 'errors':['Could not query the deprecation statistics.']});
}
});

View File

@ -19,9 +19,9 @@ function setApiInfoBox(isTyping) {
function() {
$.ajax({
type: "POST",
url: baseurl + '/servers/getApiInfo',
url: baseurl + '/api/getApiInfo',
data: payload,
success:function (data, textStatus) {
success:function (data) {
$('#apiInfo').html(data);
addHoverInfo($('#ServerUrl').data('urlWithoutParam'));
}