├── .gitignore ├── .travis.yml ├── tests ├── TestCase.php └── unit │ ├── ClientTest.php │ └── Nessus │ └── CallTest.php ├── examples ├── README.md ├── server.php ├── users.php └── scans.php ├── composer.json ├── phpunit.xml ├── LICENSE ├── src └── Nessus │ ├── Exception │ ├── InvalidUrl.php │ ├── ProxyError.php │ ├── InvalidMethod.php │ ├── FailedConnection.php │ └── FailedNessusRequest.php │ ├── Nessus │ └── Call.php │ └── Client.php └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | vendor/ 3 | composer.phar 4 | composer.lock 5 | 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 5.6 5 | - 7.0 6 | 7 | before_script: 8 | - travis_retry composer self-update 9 | - travis_retry composer install 10 | 11 | notifications: 12 | on_success: never 13 | on_failure: always 14 | 15 | script: vendor/bin/phpunit 16 | -------------------------------------------------------------------------------- /tests/TestCase.php: -------------------------------------------------------------------------------- 1 | 8 | * @license MIT 9 | * @link https://leonjza.github.io/ 10 | */ 11 | class TestCase extends \PHPUnit\Framework\TestCase 12 | { 13 | 14 | /** 15 | * Tears down the fixture, for example, close a network connection. 16 | * This method is called after a test is executed. 17 | */ 18 | public function tearDown() 19 | { 20 | 21 | Mockery::close(); 22 | 23 | parent::tearDown(); 24 | } 25 | } -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | PHPNessusNG Usage Examples 2 | ========================== 3 | [![Latest Stable Version](https://poser.pugx.org/leonjza/php-nessus-ng/v/stable.svg)](https://packagist.org/packages/leonjza/php-nessus-ng) [![Total Downloads](https://poser.pugx.org/leonjza/php-nessus-ng/downloads.svg)](https://packagist.org/packages/leonjza/php-nessus-ng) [![Latest Unstable Version](https://poser.pugx.org/leonjza/php-nessus-ng/v/unstable.svg)](https://packagist.org/packages/leonjza/php-nessus-ng) [![License](https://poser.pugx.org/leonjza/php-nessus-ng/license.svg)](https://packagist.org/packages/leonjza/php-nessus-ng) 4 | 5 | This directory contains a few examples for interfacing with the V6 Nessus API -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "leonjza/php-nessus-ng", 3 | "description": "PHP wrapper functions for interfacing with the Nessus V6.x API", 4 | "require": { 5 | "php": ">=5.6.0", 6 | "guzzlehttp/guzzle": "~6.0" 7 | }, 8 | "require-dev": { 9 | "phpunit/phpunit": "~5.0", 10 | "mockery/mockery": "0.9.*" 11 | }, 12 | "license": "MIT", 13 | "authors": [ 14 | { 15 | "name": "Leon Jacobs", 16 | "email": "leonja511@gmail.com" 17 | }, 18 | { 19 | "name": "Peter Scopes", 20 | "email": "peter.scopes@gmail.com" 21 | } 22 | ], 23 | "minimum-stability": "stable", 24 | "autoload": { 25 | "psr-0": { 26 | "Nessus": "src/" 27 | } 28 | }, 29 | "autoload-dev": { 30 | "classmap": [ 31 | "tests/TestCase.php" 32 | ] 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 13 | 14 | ./tests 15 | 16 | 17 | 18 | ./tests/unit 19 | 20 | 21 | 22 | 23 | 24 | ./app 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Leon Jacobs 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /examples/server.php: -------------------------------------------------------------------------------- 1 | server()->properties()->via('get'); 11 | 12 | echo '[+] Server Version: ' . $server_properties->server_version . PHP_EOL; 13 | echo '[+] Server Build: ' . $server_properties->server_build . PHP_EOL; 14 | echo '[+] UI Version: ' . $server_properties->nessus_ui_version . PHP_EOL; 15 | foreach ($server_properties->notifications as $notification) { 16 | echo '[+] Notification Type: ' . $notification->type . ' : ' . $notification->message . PHP_EOL; 17 | } 18 | 19 | // Get the server status 20 | // GET /server/status 21 | $server_status = $nessus->server()->status()->via('get'); 22 | echo '[+] Server Progress: ' . $server_status->progress . PHP_EOL; 23 | echo '[+] Server Status: ' . $server_status->status . PHP_EOL; 24 | 25 | // Sample output 26 | // λ git n6* → php server.php 27 | // [+] Server Version: 6.0.0 28 | // [+] Feed: ProFeed 29 | // [+] Notification Type: warning : Your plugin feed subscription will expire in 26 day(s). 30 | // [+] Server Progress: 31 | // [+] Server Status: ready 32 | -------------------------------------------------------------------------------- /src/Nessus/Exception/InvalidUrl.php: -------------------------------------------------------------------------------- 1 | 33 | * @license MIT 34 | * @link https://leonjza.github.io/ 35 | */ 36 | 37 | /** 38 | * InvalidUrl Exception. 39 | */ 40 | class InvalidUrl extends \Exception 41 | { 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/Nessus/Exception/ProxyError.php: -------------------------------------------------------------------------------- 1 | 33 | * @license MIT 34 | * @link https://leonjza.github.io/ 35 | */ 36 | 37 | /** 38 | * ProxyError Exception. 39 | */ 40 | class ProxyError extends \Exception 41 | { 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/Nessus/Exception/InvalidMethod.php: -------------------------------------------------------------------------------- 1 | 33 | * @license MIT 34 | * @link https://leonjza.github.io/ 35 | */ 36 | 37 | /** 38 | * InvalidMethod Exception. 39 | */ 40 | class InvalidMethod extends \Exception 41 | { 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/Nessus/Exception/FailedConnection.php: -------------------------------------------------------------------------------- 1 | 33 | * @license MIT 34 | * @link https://leonjza.github.io/ 35 | */ 36 | 37 | /** 38 | * FailedConnection Exception. 39 | */ 40 | class FailedConnection extends \Exception 41 | { 42 | 43 | } 44 | -------------------------------------------------------------------------------- /examples/users.php: -------------------------------------------------------------------------------- 1 | users()->via('get')->users; 11 | 12 | // ... and print some information 13 | foreach ($users as $user) { 14 | echo '[+] id:' . $user->id . ' - ' . $user->type . ' user ' . $user->username . ' last login: ' . $user->lastlogin . PHP_EOL; 15 | } 16 | 17 | // Create a new user 18 | // POST /users 19 | $new_user = $nessus->users() 20 | ->setFields( 21 | [ 22 | 'username' => 'apiuser', 23 | 'password' => 'apiuser', 24 | 'permissions' => 128, // Full permissions 25 | 'name' => 'API User', 26 | 'email' => 'api@hostname.local', 27 | 'type' => 'local', 28 | ] 29 | ) 30 | ->via('post'); 31 | echo '[+] Created new user ' . $new_user->name . ' with id ' . $new_user->id . PHP_EOL; 32 | 33 | // Edit the user 34 | // PUT /users/{user_id} 35 | //This API call appears to be broken? 36 | $user_edit = $nessus->users($new_user->id) 37 | ->setFields( 38 | [ 39 | 'permissions' => 128, 40 | 'name' => 'Edited API Name', 41 | 'email' => 'apiedit@hostname.local', 42 | ] 43 | ) 44 | ->via('put'); 45 | echo '[+] Edited user ' . $new_user->id . PHP_EOL; 46 | 47 | // Delete the user 48 | // DELETE /users/{user_id} 49 | $deleted_user = $nessus->users($new_user->id)->via('delete'); 50 | echo '[+] Deleted user ' . $new_user->id . PHP_EOL; 51 | 52 | // λ git n6* → php users.php 53 | // [+] id:3 - local user test last login: 1413804979 54 | // [+] id:4 - local user username last login: 1413876143 55 | // [+] Created new user apiuser with id 27 56 | // [+] Edited user 27 57 | // [+] Deleted user 27 58 | -------------------------------------------------------------------------------- /src/Nessus/Exception/FailedNessusRequest.php: -------------------------------------------------------------------------------- 1 | 33 | * @license MIT 34 | * @link https://leonjza.github.io/ 35 | */ 36 | 37 | use GuzzleHttp\Exception\BadResponseException; 38 | use Psr\Http\Message\RequestInterface; 39 | use Psr\Http\Message\ResponseInterface; 40 | 41 | /** 42 | * FailedNessusRequest Exception. 43 | */ 44 | class FailedNessusRequest extends BadResponseException 45 | { 46 | public static function exceptionFactory($message, RequestInterface $request, ResponseInterface $response = null) 47 | { 48 | 49 | $exceptionClass = __CLASS__; 50 | 51 | /** @var BadResponseException $exception */ 52 | $exception = new $exceptionClass($message, $request, $response); 53 | 54 | return $exception; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /tests/unit/ClientTest.php: -------------------------------------------------------------------------------- 1 | 8 | * @license MIT 9 | * @link https://leonjza.github.io/ 10 | */ 11 | class ClientTest extends TestCase 12 | { 13 | 14 | /** 15 | * @var \Mockery\Mock|\Nessus\Client 16 | */ 17 | private $mockClient; 18 | 19 | /** 20 | * @var \Mockery\Mock|\Nessus\Nessus\Call 21 | */ 22 | private $mockCall; 23 | 24 | /** 25 | * Sets up the fixture, for example, open a network connection. 26 | * This method is called before a test is executed. 27 | */ 28 | public function setUp() 29 | { 30 | 31 | parent::setUp(); 32 | 33 | $this->mockClient = Mockery::mock('\Nessus\Client')->makePartial(); 34 | $this->mockCall = Mockery::mock('\Nessus\Nessus\Call'); 35 | } 36 | 37 | /** 38 | * Test the via wrapper for making an API call. 39 | */ 40 | public function testVia() 41 | { 42 | 43 | $this->mockClient 44 | ->shouldReceive('makeApiCall')->with(Mockery::type('Nessus\Nessus\Call'), 'get', true)->andReturn(null); 45 | 46 | $this->assertNull($this->mockClient->via('get', true)); 47 | } 48 | 49 | /** 50 | * Test receiving a valid response from a Nessus Call. 51 | */ 52 | public function testMakeApiCall() 53 | { 54 | 55 | $this->mockClient 56 | ->shouldReceive('makeNessusCall')->withNoArgs()->andReturn($this->mockCall); 57 | $this->mockCall 58 | ->shouldReceive('call')->with('get', $this->mockClient)->andReturn(null); 59 | 60 | $this->assertNull($this->mockClient->makeApiCall($this->mockCall, 'get')); 61 | } 62 | 63 | /** 64 | * Test sending a request with a bad method. 65 | * 66 | * @expectedException \Nessus\Exception\InvalidMethod 67 | */ 68 | public function testMakeApiCallInvalidMethod() 69 | { 70 | 71 | $this->mockClient->via('bad_method'); 72 | } 73 | 74 | /** 75 | * Test sending a request that returns a BadResponseException. 76 | * 77 | * @expectedException \GuzzleHttp\Exception\BadResponseException 78 | */ 79 | public function testMakeApiCallBadResponse() 80 | { 81 | $mockHttpBadResponseException = \Mockery::mock('\GuzzleHttp\Exception\BadResponseException'); 82 | 83 | $this->mockCall 84 | ->shouldReceive('call')->with('get', $this->mockClient)->andThrow($mockHttpBadResponseException); 85 | 86 | $this->mockClient->makeApiCall($this->mockCall, 'get', true); 87 | } 88 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | PHPNessusNG 2 | =========== 3 | [![Build Status](https://travis-ci.org/leonjza/PHPNessusNG.svg?branch=master)](https://travis-ci.org/leonjza/PHPNessusNG) 4 | [![Code Climate](https://codeclimate.com/github/leonjza/PHPNessusNG/badges/gpa.svg)](https://codeclimate.com/github/leonjza/PHPNessusNG) 5 | [![Latest Stable Version](https://poser.pugx.org/leonjza/php-nessus-ng/v/stable.svg)](https://packagist.org/packages/leonjza/php-nessus-ng) 6 | [![Total Downloads](https://poser.pugx.org/leonjza/php-nessus-ng/downloads.svg)](https://packagist.org/packages/leonjza/php-nessus-ng) 7 | [![Latest Unstable Version](https://poser.pugx.org/leonjza/php-nessus-ng/v/unstable.svg)](https://packagist.org/packages/leonjza/php-nessus-ng) 8 | [![License](https://poser.pugx.org/leonjza/php-nessus-ng/license.svg)](https://packagist.org/packages/leonjza/php-nessus-ng) 9 | 10 | PHP wrapper functions for interfacing with the Nessus **V6.x** API. 11 | 12 | If you are looking for the Nessus V5.x capable XMLRPC API Class, please see the `n5` branch [here](https://github.com/leonjza/PHPNessusNG/tree/n5) 13 | 14 | Information: 15 | ----------- 16 | The Nessus 6 Vulnerability Scanner provides a RESTful API interface. This library aims to be a wrapper around this API, allowing you to query it directly, as detailed in the API documentation. 17 | 18 | One major advantage of this library is that it does not necessarily have to update when new endpoints are made available. It is simply a wrapper. Calls to the API can be made exactly as it is documented in the API reference found at the `/api` resource of your local scanner. For eg. If a API endpoint is defined as: 19 | 20 | ```text 21 | DELETE /scans/{scan_id} 22 | ``` 23 | 24 | Then you can call it with: 25 | 26 | ```php 27 | $client->scans($id)->via('delete'); 28 | ``` 29 | 30 | The latest version of this wrapper has only been tested against a Nessus **6.1** scanner. 31 | 32 | Concepts: 33 | --------- 34 | There are a fair number of ways to use this library. All methods start the same way though; Instantiating a new instance. The library will handle the authentication cookie automatically internally. 35 | 36 | Some examples of calling the API: 37 | 38 | ```php 39 | scans($scan_id)->export()->setFields(array('format' => 'nessus'))->via('post'); 52 | ``` 53 | 54 | The same using the `call()` method would be: 55 | 56 | ```php 57 | // Get a file ID for a new report export 58 | $t->setFields(array('format' => 'nessus')); 59 | $t->call('scans/5/export/'); 60 | $file_id = $t->via('post'); 61 | ``` 62 | 63 | **NOTE**: All calls should end with a `via($method)`, where $method is the HTTP method to use. `via()` accepts a second argument, which specifies if a response should be returned raw if true, or a parsed JSON object if not set (false). 64 | 65 | Installation: 66 | ------------ 67 | The easiest way by far would be to install the library via composer. Add the following line to your `composer.json`: 68 | 69 | ```json 70 | "leonjza/php-nessus-ng": "~1.0" 71 | ``` 72 | 73 | Run `php composer.phar update`. You should now have the `\Nessus` class available to use. 74 | 75 | This will give you the Nessus V6 compatible library. As previously mentioned, should you require the V5 compatile version, its details can be found in the `n5` branch. 76 | 77 | Usage example: 78 | --------------- 79 | Include the Composer Autoloader, instantiate a new instance, and start using it. Below is an example script that will download the first available report in the `.nessus` format: 80 | 81 | ```php 82 | scans()->via('get')->scans[0]->id; 90 | 91 | // Request the export, taking note of the returned file_id that we need. 92 | $file_id = $t->scans($scan_id)->export()->setFields(array('format' => 'nessus'))->via('post')->file; 93 | 94 | // Set a status that will update as we poll for a status 95 | $export_status = 'waiting'; 96 | 97 | // If the export status is ready, break. 98 | while ($export_status != 'ready') { 99 | 100 | // Poll for a status update 101 | $export_status = $t->scans($scan_id)->export($file_id)->status()->via('get')->status; 102 | 103 | // Wait 1 second before another poll 104 | sleep(1); 105 | } 106 | 107 | // Get the .nessus report export, specifying that we want it via a raw get 108 | $file = $t->scans($scan_id)->export($file_id)->download()->via('get', true); 109 | ``` 110 | 111 | Contact 112 | ------- 113 | Twitter: [@leonjza](https://twitter.com/leonjza) 114 | -------------------------------------------------------------------------------- /examples/scans.php: -------------------------------------------------------------------------------- 1 | configureProxy('127.0.0.1', 8081)->useProxy(); 10 | 11 | // Get the Server properties 12 | // GET /scans 13 | $scans = $nessus->scans()->via('get'); 14 | echo '[+] Scans Timestamp: ' . $scans->timestamp . PHP_EOL; 15 | 16 | // Loop over the scans printing some information 17 | $scan_id = null; 18 | if (null !== $scans->scans) { 19 | foreach ($scans->scans as $scan) { 20 | echo '[+] Scan ' . $scan->id . ': (' . $scan->name . ') status: ' . $scan->status . PHP_EOL; 21 | if ('completed' == $scan->status) { 22 | $scan_id = $scan->id; 23 | } 24 | } 25 | } 26 | 27 | // Prepare a scan for download. To do this we need to first 28 | // schedule a export job. Once this is done, we can download the 29 | // report in the requested format. 30 | 31 | if (null !== $scan_id) { 32 | // Lets take the first scan from the previous request 33 | echo '[+] Using scan_id: ' . $scan_id . ' for export.' . PHP_EOL; 34 | 35 | // Schedule the export in .nessus format, taking note of 36 | // the returned file_id 37 | // POST /scans/{scan_id}/export 38 | $file_id = $nessus->scans($scan_id)->export()->setFields(['format' => 'nessus'])->via('post')->file; 39 | echo '[+] Got file_id: ' . $file_id . ' for export job.' . PHP_EOL; 40 | 41 | // We now have to wait for the export to complete. We are 42 | // just going to check the status of our export every 1 second 43 | $export_status = 'waiting'; 44 | while ($export_status != 'ready') { 45 | 46 | // Poll for a status update 47 | $export_status = $nessus->scans($scan_id)->export($file_id)->status()->via('get')->status; 48 | echo '[+] Export status is: ' . $export_status . PHP_EOL; 49 | 50 | // Wait 1 second before another poll 51 | sleep(1); 52 | } 53 | 54 | // Once the export == 'ready', download it! 55 | $file = $nessus->scans($scan_id)->export($file_id)->download()->via('get', true); 56 | echo '[+] Report downloaded.' . PHP_EOL; 57 | echo '[+] Start report sample (first 250 chars): ' . PHP_EOL; 58 | echo substr($file, 0, 250) . PHP_EOL; 59 | echo '[+] End report sample: ' . PHP_EOL; 60 | 61 | // Get the scan details for $scan_id 62 | // GET /scans/{scan_id} 63 | $scan_details = $nessus->scans($scan_id)->via('get'); 64 | echo '[+] Report name ' . $scan_id . ': ' . $scan_details->info->name . PHP_EOL; 65 | echo '[+] Report targets ' . $scan_id . ': ' . $scan_details->info->targets . PHP_EOL; 66 | echo '[+] Scanner name ' . $scan_id . ': ' . $scan_details->info->scanner_name . PHP_EOL; 67 | 68 | echo '[+] Hosts for report ' . $scan_id . PHP_EOL; 69 | foreach ($scan_details->hosts as $host) { 70 | echo '[+] ' . $host->host_id . ', ' . $host->hostname . ', Severity rating: ' . $host->severity . PHP_EOL; 71 | } 72 | } 73 | 74 | // Create a new scan. For a new scan, we can specify a policy ID 75 | // as the uuid of the scan. Either that or a template UUID is fine. 76 | // For this example we will use a policy, so lets grab the policies. 77 | // Obviously, this requires us to have already set a policy up :> 78 | // GET /policies 79 | $policies = $nessus->policies()->via('get'); 80 | if (null !== $policies->policies) { 81 | foreach ($policies->policies as $policy) { 82 | echo '[+] Policy name ' . $policy->name . ' with UUID ' . $policy->template_uuid . PHP_EOL; 83 | } 84 | } 85 | 86 | // Just take the first policies template uuid 87 | $templates = $nessus->editor('policy')->templates()->via('get'); 88 | $template_uuid = $templates->templates[0]->uuid; 89 | echo '[+] Will use template_uuid' . $template_uuid . PHP_EOL; 90 | 91 | // Add a new scan 92 | // POST /scans 93 | $new_scan = $nessus->scans() 94 | ->setFields( 95 | [ 96 | 'uuid' => $template_uuid, 97 | 'settings' => [ 98 | 'launch_now' => false, 99 | 'name' => 'PHPNessusNG API Test Scan', 100 | 'text_targets' => '127.0.0.1', 101 | ], 102 | ] 103 | ) 104 | ->via('post')->scan; 105 | 106 | echo '[+] Configured a new scan ' . $new_scan->id . ' with name ' . $new_scan->name . ' and UUID ' . $new_scan->uuid . PHP_EOL; 107 | echo '[+] Scan ' . $new_scan->uuid . ' will scan ' . $new_scan->custom_targets . PHP_EOL; 108 | 109 | // Get some scan detials 110 | $scan_details = $nessus->scans($new_scan->id)->via('get'); 111 | echo '[+] Scan ' . $new_scan->id . ' is for scanner ' . $scan_details->info->scanner_name . ' and is ' . $scan_details->info->status . PHP_EOL; 112 | 113 | // Launch the scan we have configured and gve it 2 seconds to run. 114 | $launch_scan = $nessus->scans($new_scan->id)->launch()->via('post'); 115 | echo '[+] Scan ' . $new_scan->id . ' started with UUID ' . $launch_scan->scan_uuid . PHP_EOL; 116 | 117 | // Wait 2 seconds, and re-request the status 118 | while ('running' !== $scan_details->info->status) { 119 | sleep(0.5); 120 | $scan_details = $nessus->scans($new_scan->id)->via('get'); 121 | } 122 | echo '[+] Scan ' . $new_scan->id . ' is for scanner ' . $scan_details->info->scanner_name . ' and is ' . $scan_details->info->status . PHP_EOL; 123 | 124 | // Pause the scan 125 | $pause_scan = $nessus->scans($new_scan->id)->pause()->via('post'); 126 | 127 | // Wait 5 seconds, and re-request the status 128 | while ('paused' !== $scan_details->info->status) { 129 | sleep(0.5); 130 | $scan_details = $nessus->scans($new_scan->id)->via('get'); 131 | } 132 | echo '[+] Scan ' . $new_scan->id . ' is for scanner ' . $scan_details->info->scanner_name . ' and is ' . $scan_details->info->status . PHP_EOL; 133 | 134 | // stop the scan 135 | $stop_scan = $nessus->scans($new_scan->id)->stop()->via('post'); 136 | 137 | // Wait 5 seconds, and re-request the status 138 | while ('canceled' !== $scan_details->info->status) { 139 | sleep(0.5); 140 | $scan_details = $nessus->scans($new_scan->id)->via('get'); 141 | } 142 | $scan_details = $nessus->scans($new_scan->id)->via('get'); 143 | echo '[+] Scan ' . $new_scan->id . ' is for scanner ' . $scan_details->info->scanner_name . ' and is ' . $scan_details->info->status . PHP_EOL; 144 | 145 | // Delete the scan 146 | $deleted_scan = $nessus->scans($new_scan->id)->via('delete'); 147 | echo '[+] Deleted scan ' . $new_scan->id . PHP_EOL; 148 | 149 | // Sample output 150 | 151 | // λ git n6* → php scans.php 152 | // [+] Scans Timestamp: 1413913738 153 | // [+] Scan 63: (123) status: canceled 154 | // [+] Scan 58: (broken scan test) status: completed 155 | // [+] Scan 55: (local scan) status: completed 156 | // [+] Using scan_id: 63 for export. 157 | // [+] Got file_id: 1279697780 for export job. 158 | // [+] Export status is: ready 159 | // [+] Report downloaded. 160 | // [+] Start report sample (first 250 chars): 161 | // 162 | // 163 | // API Test Policy 164 | // plugin_set 165 | // 25451;16066;16775;54191;55364;60374;61980;78528;12878;33572;68768;24384;43101;697 166 | // [+] End report sample: 167 | // [+] Report name 63: 123 168 | // [+] Report targets 63: 127.0.0.1 169 | // [+] Scanner name 63: Local Scanner 170 | // [+] Hosts for report 63 171 | // [+] 2, 127.0.0.1, Severity rating: 25 172 | // [+] Policy name API Test Policy with UUID 731a8e52-3ea6-a291-ec0a-d2ff0619c19d7bd788d6be818b65 173 | // [+] Will use template_uuid731a8e52-3ea6-a291-ec0a-d2ff0619c19d7bd788d6be818b65 174 | // [+] Configured a new scan 132 with name PHPNessusNG API Test Scan and UUID template-8cec9169-56f0-9fc9-663f-309bb3114a9397cd4cc487025ba8 175 | // [+] Scan template-8cec9169-56f0-9fc9-663f-309bb3114a9397cd4cc487025ba8 will scan 127.0.0.1 176 | // [+] Scan 132 is for scanner Local Scanner and is empty 177 | // [+] Scan 132 started with UUID e04319b1-c135-efed-113d-19bc0fcfa81cecbd24f9f3b4d23c 178 | // [+] Scan 132 is for scanner Local Scanner and is running 179 | // [+] Scan 132 is for scanner Local Scanner and is paused 180 | // [+] Scan 132 is for scanner Local Scanner and is canceled 181 | // [+] Deleted scan 132 182 | -------------------------------------------------------------------------------- /src/Nessus/Nessus/Call.php: -------------------------------------------------------------------------------- 1 | 33 | * @license MIT 34 | * @link https://leonjza.github.io/ 35 | */ 36 | 37 | use GuzzleHttp\Client as HttpClient; 38 | use GuzzleHttp\Exception\BadResponseException; 39 | use GuzzleHttp\Exception\ClientException; 40 | use GuzzleHttp\Exception\ConnectException; 41 | use GuzzleHttp\Exception\ServerException; 42 | use Nessus\Exception; 43 | 44 | /** 45 | * Class Call. 46 | */ 47 | class Call 48 | { 49 | /** 50 | * Authenticates to a Nessus Scanner, saving the token that was received. 51 | * 52 | * @param object $scope The scope injected from a \Nessus\Client 53 | * 54 | * @return string 55 | */ 56 | public function token($scope) 57 | { 58 | 59 | // If the token is not defined, authenticate and get a new one 60 | if (is_null($scope->token)) { 61 | 62 | // Specify the no_token arg as true as this is kinda what 63 | // we are doing here. 64 | $response = $this->call('post', $scope, true); 65 | 66 | // Set the determined token 67 | $scope->token = $response->token; 68 | } 69 | 70 | return $scope->token; 71 | } 72 | 73 | /** 74 | * Makes an API call to a Nessus Scanner. 75 | * 76 | * @param string $method The method that should be used in the HTTP request 77 | * @param object $scope The scope injected from a \Nessus\Client 78 | * @param bool $no_token Should a token be used in this request 79 | * 80 | * @return null|object[]|object|string 81 | */ 82 | public function call($method, $scope, $no_token = false) 83 | { 84 | 85 | // Prepare the configuration for a new Guzzle client 86 | $config = [ 87 | 'base_uri' => $scope->url, 88 | 'verify' => $scope->validate_cert, 89 | 'timeout' => $scope->timeout, 90 | 'headers' => [ 91 | 'User-Agent' => 'PHPNessusNG/' . $scope->version, 92 | ], 93 | ]; 94 | 95 | // Detect if we have a proxy configured 96 | if ($scope->use_proxy) { 97 | 98 | // If we have a username or password, add it to the proxy 99 | // setting 100 | if (! is_null($scope->proxy_user) || ! is_null($scope->proxy_pass)) { 101 | $config['proxy'] = 'tcp://' . $scope->proxy_user . ':' . $scope->proxy_pass . 102 | '@' . $scope->proxy_host . ':' . $scope->proxy_port; 103 | } else { 104 | $config['proxy'] = 'tcp://' . $scope->proxy_host . ':' . $scope->proxy_port; 105 | } 106 | } 107 | 108 | $client = new HttpClient($config); 109 | 110 | return $this->request($client, $method, $scope, $no_token); 111 | } 112 | 113 | /** 114 | * Makes an API call to a Nessus Scanner. 115 | * 116 | * @param HttpClient $client The HttpClient that should be used to make the request 117 | * @param string $method The method that should be used in the HTTP request 118 | * @param object $scope The scope injected from a \Nessus\Client 119 | * @param bool $no_token Should a token be used in this request 120 | * 121 | * @return null|object[]|object|string 122 | * 123 | * @throws Exception\FailedNessusRequest 124 | * @throws Exception\FailedConnection 125 | * @throws \InvalidArgumentException 126 | */ 127 | public function request(HttpClient $client, $method, $scope, $no_token = false) 128 | { 129 | // Define the uri and default options 130 | $method = strtoupper($method); 131 | $uri = $scope->call; 132 | $options = ['headers' => ['Accept' => 'application/json']]; 133 | 134 | // If we have $no_token set, we assume that this is the login request 135 | // that we have received. So, we will override the body with the 136 | // username and password 137 | if (! $no_token) { 138 | // Only really needed by $this->token() method. Otherwise we have 139 | // a cyclic dependency trying to setup a token 140 | $options['headers']['X-Cookie'] = 'token=' . $this->token($scope); 141 | 142 | // Methods such as PUT, DELETE and POST require us to set a body. We will 143 | // json encode this and set it 144 | if (in_array($method, ['PUT', 'POST', 'DELETE'])) { 145 | $options['json'] = $scope->fields; 146 | } else { 147 | $options['query'] = $scope->fields; 148 | } 149 | } else { 150 | // $no_token may mean a token request 151 | $uri = 'session/'; 152 | $options['json'] = [ 153 | 'username' => $scope->username, 154 | 'password' => $scope->password, 155 | ]; 156 | } 157 | 158 | // Attempt the actual response that has been built thus far 159 | try { 160 | $response = $client->request($method, $uri, $options); 161 | } 162 | catch (ClientException $clientException) { 163 | // If a endpoint is called that does not exist, give a slightly easier to 164 | // understand error. 165 | if ($clientException->getResponse()->getStatusCode() == 404) { 166 | throw Exception\FailedNessusRequest::exceptionFactory( 167 | 'Nessus responded with a 404 for ' . $scope->url . $scope->call . ' via ' . $method . '. Check your call.', 168 | $clientException->getRequest(), 169 | $clientException->getResponse() 170 | ); 171 | } 172 | 173 | throw Exception\FailedNessusRequest::exceptionFactory( 174 | $clientException->getMessage(), $clientException->getRequest(), $clientException->getResponse() 175 | ); 176 | } 177 | catch (ServerException $serverException) { 178 | throw Exception\FailedNessusRequest::exceptionFactory( 179 | $serverException->getMessage(), $serverException->getRequest(), $serverException->getResponse() 180 | ); 181 | } 182 | catch (BadResponseException $badResponseException) { 183 | throw Exception\FailedNessusRequest::exceptionFactory( 184 | 'Unsuccessful Request to [' . $method . '] ' . $scope->call, 185 | $badResponseException->getRequest(), 186 | $badResponseException->getResponse() 187 | ); 188 | } 189 | catch (ConnectException $connectException) { 190 | throw new Exception\FailedConnection($connectException->getMessage(), $connectException->getCode()); 191 | } 192 | 193 | // If the response is requested in raw format, return it. We need 194 | // to be careful to not return raw to a token request too. 195 | if ($scope->raw && ! $no_token) { 196 | return (string) $response->getBody(); 197 | } 198 | 199 | // Check that the response is not empty. Looks like Nessus returns 200 | // "null" on empty response :s 201 | if (is_null($response->getBody()) || trim($response->getBody()) == 'null') { 202 | return null; 203 | } 204 | 205 | // We assume that Nessus can return empty bodies and that Nessus will 206 | // use HTTP status codes to inform us whether the request failed. 207 | if (trim($response->getBody()) == '') { 208 | return null; 209 | } 210 | 211 | // Attempt to convert the response to a JSON Object 212 | return \GuzzleHttp\json_decode($response->getBody()); 213 | } 214 | } 215 | -------------------------------------------------------------------------------- /src/Nessus/Client.php: -------------------------------------------------------------------------------- 1 | 33 | * @license MIT 34 | * @link https://leonjza.github.io/ 35 | */ 36 | 37 | /** 38 | * Class Client. 39 | */ 40 | class Client 41 | { 42 | 43 | /** 44 | * @var string 45 | */ 46 | public $version = '1.0.8'; 47 | 48 | /** 49 | * @var string 50 | */ 51 | public $username; 52 | 53 | /** 54 | * @var string 55 | */ 56 | public $password; 57 | 58 | /** 59 | * @var string 60 | */ 61 | public $host; 62 | 63 | /** 64 | * @var string 65 | */ 66 | public $port; 67 | 68 | /** 69 | * @var bool 70 | */ 71 | public $https; 72 | 73 | /** 74 | * @var bool 75 | */ 76 | public $validate_cert = false; 77 | 78 | /** 79 | * @var string 80 | */ 81 | public $url; 82 | 83 | /** 84 | * @var array 85 | */ 86 | public $fields = []; 87 | 88 | /** 89 | * @var int 90 | */ 91 | public $timeout = 10; 92 | 93 | /** 94 | * @var string 95 | */ 96 | public $token = null; 97 | 98 | /** 99 | * @var string 100 | */ 101 | public $call; 102 | 103 | /** 104 | * @var bool 105 | */ 106 | public $raw = false; 107 | 108 | /** 109 | * @var bool 110 | */ 111 | public $use_proxy = false; 112 | 113 | /** 114 | * @var string 115 | */ 116 | public $proxy_host = null; 117 | 118 | /** 119 | * @var int 120 | */ 121 | public $proxy_port = null; 122 | 123 | /** 124 | * @var string 125 | */ 126 | public $proxy_user; 127 | 128 | /** 129 | * @var string 130 | */ 131 | public $proxy_pass; 132 | 133 | /** 134 | * Creates a new \Nessus\Client Object. 135 | * 136 | * @param string $user The username to authenticate with 137 | * @param string $pass The password to authenticate with 138 | * @param string $host The Nessus Scanner 139 | * @param int $port The The port the Nessus Scanner is listening on 140 | * @param bool $https Should the connection be via HTTPs 141 | * 142 | * @throws Exception\InvalidUrl If the constructed URL is invalid 143 | */ 144 | public function __construct($user, $pass, $host, $port = 8834, $https = true) 145 | { 146 | 147 | // Set the values we have received 148 | $this->username = $user; 149 | $this->password = $pass; 150 | $this->host = $host; 151 | $this->port = $port; 152 | $this->https = $https; 153 | 154 | // Construct the Base Url to use 155 | $this->url = ($this->https ? 'https://' : 'http://'); 156 | $this->url .= $this->host; 157 | $this->url .= ':' . $this->port . '/'; 158 | 159 | // Check that we have a valid host 160 | if (! filter_var($this->url, FILTER_VALIDATE_URL)) { 161 | throw new Exception\InvalidUrl($this->url . ' appears to be unparsable.'); 162 | } 163 | } 164 | 165 | /** 166 | * Mutator method to change the certificate validation rule. 167 | * 168 | * @param bool $validate True if the server SSL certificate should be validated. False if not. 169 | * 170 | * @return $this 171 | */ 172 | public function validateCert($validate = true) 173 | { 174 | 175 | $this->validate_cert = $validate; 176 | 177 | return $this; 178 | } 179 | 180 | /** 181 | * Set the configuration options needed to use a proxy server for 182 | * requests to the Nessus API. 183 | * 184 | * @param string $host The proxy server 185 | * @param int $port The port the proxy server is listening on 186 | * @param string $username The username to authenticate with if needed 187 | * @param string $password The password to authenticate with if needed 188 | * 189 | * @return $this 190 | * 191 | * @throws Exception\ProxyError If the port is invalid or the host is null 192 | */ 193 | public function configureProxy($host, $port, $username = null, $password = null) 194 | { 195 | 196 | // Check port validity 197 | if (! is_int($port) || $port <= 0 || $port > 65535) { 198 | throw new Exception\ProxyError('Invalid proxy port of ' . $port . ' specified.'); 199 | } 200 | 201 | // Ensure that we have proxy host:port defined 202 | if (is_null($host) || is_null($port)) { 203 | throw new Exception\ProxyError('A host and port specification is required for proxy use.'); 204 | } 205 | 206 | $this->proxy_host = $host; 207 | $this->proxy_port = $port; 208 | $this->proxy_user = $username; 209 | $this->proxy_pass = $password; 210 | 211 | return $this; 212 | } 213 | 214 | /** 215 | * Mutator method to set the proxy server usage. 216 | * 217 | * @param bool $use Specify the use of the proxy server via true 218 | * 219 | * @return $this 220 | * 221 | * @throws Exception\ProxyError if the host or port is null 222 | */ 223 | public function useProxy($use = true) 224 | { 225 | 226 | // Ensure that we have proxy host:port defined 227 | if (is_null($this->proxy_host) || is_null($this->proxy_port)) { 228 | throw new Exception\ProxyError('A host and port specification is required for proxy use.'); 229 | } 230 | 231 | $this->use_proxy = $use; 232 | 233 | return $this; 234 | } 235 | 236 | /** 237 | * Set the API call location. 238 | * 239 | * @param string $location The api endpoint to call. 240 | * 241 | * @return $this 242 | */ 243 | public function call($location) 244 | { 245 | 246 | // Remove the first slash if its there 247 | $location = ltrim($location, '/'); 248 | $this->call = $location; 249 | 250 | return $this; 251 | } 252 | 253 | /** 254 | * Magic method to allow API calls to be constructed via 255 | * method chaining. ie: $call->server()->properties() will 256 | * result in a endpoint location of BASE_URL/server/properties/. 257 | * 258 | * Magic method arguments will also be parsed as part of the call. 259 | * ie: $call->make('server', 'properties') will result in a 260 | * endpoint location of BASE_URL/server/properties/ 261 | * 262 | * @param string $location The api endpoint to call. 263 | * @param string[] $slug Any arguments to parse as part of the location 264 | * 265 | * @return $this 266 | */ 267 | public function __call($location, $slug) 268 | { 269 | 270 | // Ensure the location is lowercase 271 | $this->call .= strtolower($location) . '/'; 272 | 273 | if (count($slug) > 0) { 274 | foreach ($slug as $slug_value) { 275 | $this->call .= $slug_value . '/'; 276 | } 277 | } 278 | 279 | return $this; 280 | } 281 | 282 | /** 283 | * Specify any fields that should form part of say a POST 284 | * request. 285 | * 286 | * @param array $fields The key=>value's of the fields to send with 287 | * 288 | * @return $this 289 | */ 290 | public function setFields($fields = []) 291 | { 292 | 293 | $this->fields = array_merge($this->fields, $fields); 294 | 295 | return $this; 296 | } 297 | 298 | /** 299 | * Make a API call using the $method described. This is the final method 300 | * that should be called to make requests. Unless $raw is set to true, 301 | * the response will be a PHP \Object. 302 | * 303 | * @param string $method The HTTP method that should be used for the call 304 | * @param bool $raw Should the response be raw JSON 305 | * 306 | * @throws \Exception 307 | * 308 | * @return null|object[]|object|string NULL if empty response body was empty, string if $raw = true 309 | */ 310 | public function via($method = 'get', $raw = false) 311 | { 312 | 313 | // Make the call 314 | return $this->makeApiCall(new Nessus\Call(), $method, $raw); 315 | } 316 | 317 | /** 318 | * Make a API call using the $method described. This is the final method 319 | * that should be called to make requests. Unless $raw is set to true, 320 | * the response will be a PHP \Object. 321 | * 322 | * @param Nessus\Call $api_call Call object to perform the request with 323 | * @param string $method The HTTP method that should be used for the call 324 | * @param bool $raw Should the response be raw JSON 325 | * 326 | * @return null|object|\object[]|string 327 | * 328 | * @throws Exception\InvalidMethod If $method is invalid 329 | * @throws \Exception If $api_call throws an exception 330 | */ 331 | public function makeApiCall(Nessus\Call $api_call, $method, $raw = false) 332 | { 333 | 334 | $method = strtolower($method); 335 | 336 | if ($raw) { 337 | $this->raw = true; 338 | } 339 | 340 | $valid_requests = ['get', 'post', 'put', 'delete']; 341 | if (! in_array($method, $valid_requests)) 342 | throw new Exception\InvalidMethod(sprintf('Invalid HTTP method "%s" specified.', $method)); 343 | 344 | try { 345 | 346 | $api_response = $api_call->call($method, $this); 347 | 348 | } catch (\Exception $error) { 349 | 350 | // Catch and re-throw this exception to allow us to reset the request 351 | // so that the client can continue to be used even after this failed request. 352 | $this->resetRequest(); 353 | throw $error; 354 | } 355 | 356 | $this->resetRequest(); 357 | 358 | return $api_response; 359 | } 360 | 361 | /** 362 | * Method resets the outgoing request so that future requests are not appended. 363 | * 364 | * @return void 365 | */ 366 | protected function resetRequest() 367 | { 368 | 369 | // Clear call, raw & fields so a new request is fresh 370 | $this->call = null; 371 | $this->fields = []; 372 | $this->raw = false; 373 | } 374 | } 375 | -------------------------------------------------------------------------------- /tests/unit/Nessus/CallTest.php: -------------------------------------------------------------------------------- 1 | 8 | * @license MIT 9 | * @link https://leonjza.github.io/ 10 | */ 11 | class CallTest extends TestCase 12 | { 13 | 14 | /** 15 | * @var \Mockery\Mock|\Nessus\Client 16 | */ 17 | private $mockClient; 18 | 19 | /** 20 | * @var \Mockery\Mock|\Nessus\Nessus\Call 21 | */ 22 | private $mockCall; 23 | 24 | /** 25 | * Sets up the fixture, for example, open a network connection. 26 | * This method is called before a test is executed. 27 | */ 28 | public function setUp() 29 | { 30 | 31 | parent::setUp(); 32 | 33 | $this->mockClient = Mockery::mock('\Nessus\Client'); 34 | $this->mockCall = Mockery::mock('\Nessus\Nessus\Call')->makePartial(); 35 | } 36 | 37 | /** 38 | * Test token retrieval from the Nessus Scanner. 39 | */ 40 | public function testTokenNoToken() 41 | { 42 | 43 | $response = new stdClass(); 44 | $response->token = 'foobar'; 45 | 46 | $this->mockClient->token = null; 47 | $this->mockCall 48 | ->shouldReceive('call')->with('post', $this->mockClient, true)->andReturn($response); 49 | 50 | $token = $this->mockCall->token($this->mockClient); 51 | 52 | $this->assertEquals($response->token, $token); 53 | $this->assertEquals($response->token, $this->mockClient->token); 54 | } 55 | 56 | /** 57 | * Test token retrieval from Nessus Scanner - bad response exception. 58 | * 59 | * @expectedException \GuzzleHttp\Exception\BadResponseException 60 | */ 61 | public function testTokenNoTokenBadResponse() 62 | { 63 | 64 | $response = new stdClass(); 65 | $response->token = 'foobar'; 66 | 67 | $mockHttpBadResponseException = \Mockery::mock('\GuzzleHttp\Exception\BadResponseException'); 68 | 69 | $this->mockClient->token = null; 70 | $this->mockCall 71 | ->shouldReceive('call')->with('post', $this->mockClient, true)->andThrow($mockHttpBadResponseException); 72 | 73 | $this->mockCall->token($this->mockClient); 74 | 75 | } 76 | 77 | /** 78 | * Test token retrieval once token has been retrieved already. 79 | */ 80 | public function testTokenWithToken() 81 | { 82 | 83 | $this->mockClient->token = 'foobar'; 84 | $this->mockCall 85 | ->shouldNotReceive('call'); 86 | 87 | $token = $this->mockCall->token($this->mockClient); 88 | 89 | $this->assertEquals($this->mockClient->token, $token); 90 | } 91 | 92 | /** 93 | * Test call creates a Guzzle Http client and makes a request. 94 | */ 95 | public function testCall() 96 | { 97 | 98 | $this->mockCall 99 | ->shouldReceive('request')->with(Mockery::type('GuzzleHttp\Client'), 'get', $this->mockClient, false); 100 | 101 | $this->mockCall->call('get', $this->mockClient); 102 | } 103 | 104 | /** 105 | * Test successful GET request. 106 | */ 107 | public function testGetRequest() 108 | { 109 | $this->mockClient->call = 'foobar/'; 110 | $headers = ['X-Cookie' => 'token=X-Cookie-Token', 'Accept' => 'application/json']; 111 | $options = ['headers' => $headers, 'query' => $this->mockClient->fields]; 112 | 113 | /** @var \Mockery\Mock|Psr\Http\Message\ResponseInterface $mockHttpResponse */ 114 | $mockHttpResponse = Mockery::mock('Psr\Http\Message\ResponseInterface'); 115 | $mockHttpResponse 116 | ->shouldReceive('getStatusCode')->withNoArgs()->andReturn(200) 117 | ->shouldReceive('getBody')->withNoArgs()->andReturn(json_encode(['1', '2', '3'])); 118 | 119 | /** @var \Mockery\Mock|\GuzzleHttp\Client $mockHttpClient */ 120 | $mockHttpClient = Mockery::mock('\GuzzleHttp\Client'); 121 | $mockHttpClient 122 | ->shouldReceive('request')->with('GET', $this->mockClient->call, $options)->andReturn($mockHttpResponse); 123 | 124 | $this->mockCall 125 | ->shouldReceive('token')->with($this->mockClient)->andReturn('X-Cookie-Token'); 126 | 127 | $this->assertEquals(['1', '2', '3'], $this->mockCall->request($mockHttpClient, 'get', $this->mockClient)); 128 | } 129 | 130 | /** 131 | * Test successful GET request - empty response. 132 | */ 133 | public function testGetRequestEmptyResponse() 134 | { 135 | $this->mockClient->call = 'foobar/'; 136 | $headers = ['X-Cookie' => 'token=X-Cookie-Token', 'Accept' => 'application/json']; 137 | $options = ['headers' => $headers, 'query' => $this->mockClient->fields]; 138 | 139 | /** @var \Mockery\Mock|Psr\Http\Message\ResponseInterface $mockHttpResponse */ 140 | $mockHttpResponse = Mockery::mock('Psr\Http\Message\ResponseInterface'); 141 | $mockHttpResponse 142 | ->shouldReceive('getStatusCode')->withNoArgs()->andReturn(200) 143 | ->shouldReceive('getBody')->withNoArgs()->andReturn(''); 144 | 145 | /** @var \Mockery\Mock|\GuzzleHttp\Client $mockHttpClient */ 146 | $mockHttpClient = Mockery::mock('\GuzzleHttp\Client'); 147 | $mockHttpClient 148 | ->shouldReceive('request')->with('GET', $this->mockClient->call, $options)->andReturn($mockHttpResponse); 149 | 150 | $this->mockCall 151 | ->shouldReceive('token')->with($this->mockClient)->andReturn('X-Cookie-Token'); 152 | 153 | $this->assertNull($this->mockCall->request($mockHttpClient, 'get', $this->mockClient)); 154 | } 155 | 156 | /** 157 | * Test successful POST request. 158 | */ 159 | public function testPostRequest() 160 | { 161 | $this->mockClient->call = 'foobar/'; 162 | $headers = ['X-Cookie' => 'token=X-Cookie-Token', 'Accept' => 'application/json']; 163 | $options = ['headers' => $headers, 'json' => $this->mockClient->fields]; 164 | 165 | /** @var \Mockery\Mock|Psr\Http\Message\ResponseInterface $mockHttpResponse */ 166 | $mockHttpResponse = Mockery::mock('Psr\Http\Message\ResponseInterface'); 167 | $mockHttpResponse 168 | ->shouldReceive('getStatusCode')->withNoArgs()->andReturn(200) 169 | ->shouldReceive('getBody')->withNoArgs()->andReturn(json_encode(['1', '2', '3'])); 170 | 171 | /** @var \Mockery\Mock|\GuzzleHttp\Client $mockHttpClient */ 172 | $mockHttpClient = Mockery::mock('\GuzzleHttp\Client'); 173 | $mockHttpClient 174 | ->shouldReceive('request')->with('POST', $this->mockClient->call, $options)->andReturn($mockHttpResponse); 175 | 176 | $this->mockCall 177 | ->shouldReceive('token')->with($this->mockClient)->andReturn('X-Cookie-Token'); 178 | 179 | $this->assertEquals(['1', '2', '3'], $this->mockCall->request($mockHttpClient, 'post', $this->mockClient)); 180 | } 181 | 182 | /** 183 | * Test successful PUT request with an empty response. 184 | */ 185 | public function testPutRequestEmptyResponse() 186 | { 187 | $this->mockClient->call = 'foobar/'; 188 | $headers = ['X-Cookie' => 'token=X-Cookie-Token', 'Accept' => 'application/json']; 189 | $options = ['headers' => $headers, 'json' => $this->mockClient->fields]; 190 | 191 | /** @var \Mockery\Mock|Psr\Http\Message\ResponseInterface $mockHttpResponse */ 192 | $mockHttpResponse = Mockery::mock('Psr\Http\Message\ResponseInterface'); 193 | $mockHttpResponse 194 | ->shouldReceive('getStatusCode')->withNoArgs()->andReturn(200) 195 | ->shouldReceive('getBody')->withNoArgs()->andReturn(''); 196 | 197 | /** @var \Mockery\Mock|\GuzzleHttp\Client $mockHttpClient */ 198 | $mockHttpClient = Mockery::mock('\GuzzleHttp\Client'); 199 | $mockHttpClient 200 | ->shouldReceive('request')->with('PUT', $this->mockClient->call, $options)->andReturn($mockHttpResponse); 201 | 202 | $this->mockCall 203 | ->shouldReceive('token')->with($this->mockClient)->andReturn('X-Cookie-Token'); 204 | 205 | $this->assertNull($this->mockCall->request($mockHttpClient, 'put', $this->mockClient)); 206 | } 207 | 208 | /** 209 | * Test successful POST request with an empty response. 210 | */ 211 | public function testPostRequestEmptyResponse() 212 | { 213 | $this->mockClient->call = 'foobar/'; 214 | $headers = ['X-Cookie' => 'token=X-Cookie-Token', 'Accept' => 'application/json']; 215 | $options = ['headers' => $headers, 'json' => $this->mockClient->fields]; 216 | 217 | /** @var \Mockery\Mock|Psr\Http\Message\ResponseInterface $mockHttpResponse */ 218 | $mockHttpResponse = Mockery::mock('Psr\Http\Message\ResponseInterface'); 219 | $mockHttpResponse 220 | ->shouldReceive('getStatusCode')->withNoArgs()->andReturn(200) 221 | ->shouldReceive('getBody')->withNoArgs()->andReturn(''); 222 | 223 | /** @var \Mockery\Mock|\GuzzleHttp\Client $mockHttpClient */ 224 | $mockHttpClient = Mockery::mock('\GuzzleHttp\Client'); 225 | $mockHttpClient 226 | ->shouldReceive('request')->with('POST', $this->mockClient->call, $options)->andReturn($mockHttpResponse); 227 | 228 | $this->mockCall 229 | ->shouldReceive('token')->with($this->mockClient)->andReturn('X-Cookie-Token'); 230 | 231 | $this->assertNull($this->mockCall->request($mockHttpClient, 'post', $this->mockClient)); 232 | } 233 | 234 | /** 235 | * Test successful PUT request with an empty response. 236 | */ 237 | public function testDeleteRequestEmptyResponse() 238 | { 239 | $this->mockClient->call = 'foobar/'; 240 | $headers = ['X-Cookie' => 'token=X-Cookie-Token', 'Accept' => 'application/json']; 241 | $options = ['headers' => $headers, 'json' => $this->mockClient->fields]; 242 | 243 | /** @var \Mockery\Mock|Psr\Http\Message\ResponseInterface $mockHttpResponse */ 244 | $mockHttpResponse = Mockery::mock('Psr\Http\Message\ResponseInterface'); 245 | $mockHttpResponse 246 | ->shouldReceive('getStatusCode')->withNoArgs()->andReturn(200) 247 | ->shouldReceive('getBody')->withNoArgs()->andReturn(''); 248 | 249 | /** @var \Mockery\Mock|\GuzzleHttp\Client $mockHttpClient */ 250 | $mockHttpClient = Mockery::mock('\GuzzleHttp\Client'); 251 | $mockHttpClient 252 | ->shouldReceive('request')->with('DELETE', $this->mockClient->call, $options)->andReturn($mockHttpResponse); 253 | 254 | $this->mockCall 255 | ->shouldReceive('token')->with($this->mockClient)->andReturn('X-Cookie-Token'); 256 | 257 | $this->assertNull($this->mockCall->request($mockHttpClient, 'delete', $this->mockClient)); 258 | } 259 | 260 | /** 261 | * Test successful session/ request. 262 | */ 263 | public function testRequestToken() 264 | { 265 | 266 | $this->mockClient->username = 'foo'; 267 | $this->mockClient->password = 'bar'; 268 | $postBody = ['username' => $this->mockClient->username, 'password' => $this->mockClient->password]; 269 | $headers = ['Accept' => 'application/json']; 270 | $options = ['headers' => $headers, 'json' => $postBody]; 271 | 272 | /** @var \Mockery\Mock|Psr\Http\Message\ResponseInterface $mockHttpResponse */ 273 | $mockHttpResponse = Mockery::mock('Psr\Http\Message\ResponseInterface'); 274 | $mockHttpResponse 275 | ->shouldReceive('getStatusCode')->withNoArgs()->andReturn(200) 276 | ->shouldReceive('getBody')->withNoArgs()->andReturn(json_encode(['1', '2', '3'])); 277 | 278 | /** @var \Mockery\Mock|\GuzzleHttp\Client $mockHttpClient */ 279 | $mockHttpClient = Mockery::mock('\GuzzleHttp\Client'); 280 | $mockHttpClient 281 | ->shouldReceive('request')->with('POST', 'session/', $options)->andReturn($mockHttpResponse); 282 | 283 | $this->mockCall 284 | ->shouldReceive('token')->with($this->mockClient)->andReturn('X-Cookie-Token'); 285 | 286 | $this->assertEquals(['1', '2', '3'], $this->mockCall->request($mockHttpClient, 'post', $this->mockClient, true)); 287 | } 288 | 289 | /** 290 | * Test failed request - bad response. 291 | * 292 | * @expectedException \Nessus\Exception\FailedNessusRequest 293 | */ 294 | public function testRequestBadResponse() 295 | { 296 | 297 | $mockPsrHttpResponse = Mockery::mock('Psr\Http\Message\ResponseInterface'); 298 | $mockPsrHttpResponse->shouldReceive('getStatusCode')->withNoArgs()->andReturn(403); 299 | 300 | $mockHttpBadResponseException = Mockery::mock('\GuzzleHttp\Exception\BadResponseException'); 301 | $mockHttpBadResponseException 302 | ->shouldReceive('getRequest')->withNoArgs()->andReturn(Mockery::mock('Psr\Http\Message\RequestInterface')) 303 | ->shouldReceive('getResponse')->withNoArgs()->andReturn($mockPsrHttpResponse); 304 | 305 | $this->mockClient->call = 'foobar/'; 306 | $headers = ['X-Cookie' => 'token=X-Cookie-Token', 'Accept' => 'application/json']; 307 | $options = ['headers' => $headers, 'query' => $this->mockClient->fields]; 308 | 309 | /** @var \Mockery\Mock|Psr\Http\Message\ResponseInterface $mockHttpResponse */ 310 | $mockHttpResponse = Mockery::mock('Psr\Http\Message\ResponseInterface'); 311 | $mockHttpResponse 312 | ->shouldReceive('getStatusCode')->withNoArgs()->andReturn(200); 313 | 314 | /** @var \Mockery\Mock|\GuzzleHttp\Client $mockHttpClient */ 315 | $mockHttpClient = Mockery::mock('\GuzzleHttp\Client'); 316 | $mockHttpClient 317 | ->shouldReceive('request')->with('GET', $this->mockClient->call, $options)->andThrow($mockHttpBadResponseException); 318 | 319 | $this->mockCall 320 | ->shouldReceive('token')->with($this->mockClient)->andReturn('X-Cookie-Token'); 321 | 322 | $this->assertEquals(['1', '2', '3'], $this->mockCall->request($mockHttpClient, 'get', $this->mockClient)); 323 | } 324 | 325 | /** 326 | * Test failed request - 404 not found. 327 | * 328 | * @expectedException \Nessus\Exception\FailedNessusRequest 329 | */ 330 | public function testRequestNotFound() 331 | { 332 | 333 | $this->mockClient->call = 'foobar/'; 334 | $headers = ['X-Cookie' => 'token=X-Cookie-Token', 'Accept' => 'application/json']; 335 | $options = ['headers' => $headers, 'query' => $this->mockClient->fields]; 336 | 337 | $mockPsrHttpResponse = Mockery::mock('Psr\Http\Message\ResponseInterface'); 338 | $mockPsrHttpResponse 339 | ->shouldReceive('getStatusCode')->withNoArgs()->andReturn(404); 340 | 341 | $mockHttpBadResponseException = Mockery::mock('\GuzzleHttp\Exception\ClientException'); 342 | $mockHttpBadResponseException 343 | ->shouldReceive('getRequest')->withNoArgs()->andReturn(Mockery::mock('Psr\Http\Message\RequestInterface')) 344 | ->shouldReceive('getResponse')->withNoArgs()->andReturn($mockPsrHttpResponse); 345 | 346 | /** @var \Mockery\Mock|\GuzzleHttp\Client $mockHttpClient */ 347 | $mockHttpClient = Mockery::mock('\GuzzleHttp\Client'); 348 | $mockHttpClient 349 | ->shouldReceive('request')->with('GET', $this->mockClient->call, $options)->andThrow($mockHttpBadResponseException); 350 | 351 | $this->mockCall 352 | ->shouldReceive('token')->with($this->mockClient)->andReturn('X-Cookie-Token'); 353 | 354 | $this->assertEquals(['1', '2', '3'], $this->mockCall->request($mockHttpClient, 'get', $this->mockClient)); 355 | } 356 | 357 | /** 358 | * Test failed request - client exception. 359 | * 360 | * @expectedException \Nessus\Exception\FailedNessusRequest 361 | */ 362 | public function testRequestClientException() 363 | { 364 | 365 | $this->mockClient->call = 'foobar/'; 366 | $headers = ['X-Cookie' => 'token=X-Cookie-Token', 'Accept' => 'application/json']; 367 | $options = ['headers' => $headers, 'query' => $this->mockClient->fields]; 368 | 369 | $mockPsrHttpResponse = Mockery::mock('Psr\Http\Message\ResponseInterface'); 370 | $mockPsrHttpResponse 371 | ->shouldReceive('getStatusCode')->withNoArgs()->andReturn(400); 372 | 373 | $mockHttpBadResponseException = Mockery::mock('\GuzzleHttp\Exception\ClientException'); 374 | $mockHttpBadResponseException 375 | ->shouldReceive('getRequest')->withNoArgs()->andReturn(Mockery::mock('Psr\Http\Message\RequestInterface')) 376 | ->shouldReceive('getResponse')->withNoArgs()->andReturn($mockPsrHttpResponse); 377 | 378 | /** @var \Mockery\Mock|\GuzzleHttp\Client $mockHttpClient */ 379 | $mockHttpClient = Mockery::mock('\GuzzleHttp\Client'); 380 | $mockHttpClient 381 | ->shouldReceive('request')->with('GET', $this->mockClient->call, $options)->andThrow($mockHttpBadResponseException); 382 | 383 | $this->mockCall 384 | ->shouldReceive('token')->with($this->mockClient)->andReturn('X-Cookie-Token'); 385 | 386 | $this->assertEquals(['1', '2', '3'], $this->mockCall->request($mockHttpClient, 'get', $this->mockClient)); 387 | } 388 | 389 | /** 390 | * Test failed request - server exception. 391 | * 392 | * @expectedException \Nessus\Exception\FailedNessusRequest 393 | */ 394 | public function testRequestServerException() 395 | { 396 | 397 | $this->mockClient->call = 'foobar/'; 398 | $headers = ['X-Cookie' => 'token=X-Cookie-Token', 'Accept' => 'application/json']; 399 | $options = ['headers' => $headers, 'query' => $this->mockClient->fields]; 400 | 401 | $mockPsrHttpResponse = Mockery::mock('Psr\Http\Message\ResponseInterface'); 402 | $mockPsrHttpResponse 403 | ->shouldReceive('getStatusCode')->withNoArgs()->andReturn(400); 404 | 405 | $mockHttpBadResponseException = Mockery::mock('\GuzzleHttp\Exception\ServerException'); 406 | $mockHttpBadResponseException 407 | ->shouldReceive('getRequest')->withNoArgs()->andReturn(Mockery::mock('Psr\Http\Message\RequestInterface')) 408 | ->shouldReceive('getResponse')->withNoArgs()->andReturn($mockPsrHttpResponse); 409 | 410 | /** @var \Mockery\Mock|\GuzzleHttp\Client $mockHttpClient */ 411 | $mockHttpClient = Mockery::mock('\GuzzleHttp\Client'); 412 | $mockHttpClient 413 | ->shouldReceive('request')->with('GET', $this->mockClient->call, $options)->andThrow($mockHttpBadResponseException); 414 | 415 | $this->mockCall 416 | ->shouldReceive('token')->with($this->mockClient)->andReturn('X-Cookie-Token'); 417 | 418 | $this->assertEquals(['1', '2', '3'], $this->mockCall->request($mockHttpClient, 'get', $this->mockClient)); 419 | } 420 | 421 | /** 422 | * Test failed request - connect exception. 423 | * 424 | * @expectedException \Nessus\Exception\FailedConnection 425 | */ 426 | public function testRequestConnectionException() 427 | { 428 | 429 | $this->mockClient->call = 'foobar/'; 430 | $headers = ['X-Cookie' => 'token=X-Cookie-Token', 'Accept' => 'application/json']; 431 | $options = ['headers' => $headers, 'query' => $this->mockClient->fields]; 432 | 433 | $mockPsrHttpResponse = Mockery::mock('Psr\Http\Message\ResponseInterface'); 434 | $mockPsrHttpResponse 435 | ->shouldReceive('getStatusCode')->withNoArgs()->andReturn(400); 436 | 437 | $mockHttpBadResponseException = Mockery::mock('\GuzzleHttp\Exception\ConnectException'); 438 | 439 | /** @var \Mockery\Mock|\GuzzleHttp\Client $mockHttpClient */ 440 | $mockHttpClient = Mockery::mock('\GuzzleHttp\Client'); 441 | $mockHttpClient 442 | ->shouldReceive('request')->with('GET', $this->mockClient->call, $options)->andThrow($mockHttpBadResponseException); 443 | 444 | $this->mockCall 445 | ->shouldReceive('token')->with($this->mockClient)->andReturn('X-Cookie-Token'); 446 | 447 | $this->assertEquals(['1', '2', '3'], $this->mockCall->request($mockHttpClient, 'get', $this->mockClient)); 448 | } 449 | 450 | /** 451 | * Test failed request - JSON parse error (syntax error). 452 | * 453 | * @expectedException \InvalidArgumentException 454 | */ 455 | public function testRequestFailedJsonParse() 456 | { 457 | $this->mockClient->call = 'foobar/'; 458 | $headers = ['X-Cookie' => 'token=X-Cookie-Token', 'Accept' => 'application/json']; 459 | $options = ['headers' => $headers, 'query' => $this->mockClient->fields]; 460 | 461 | /** @var \Mockery\Mock|Psr\Http\Message\ResponseInterface $mockHttpResponse */ 462 | $mockHttpResponse = Mockery::mock('Psr\Http\Message\ResponseInterface'); 463 | $mockHttpResponse 464 | ->shouldReceive('getStatusCode')->withNoArgs()->andReturn(200) 465 | ->shouldReceive('getBody')->withNoArgs()->andReturn('}INVALID JSON{'); 466 | 467 | /** @var \Mockery\Mock|\GuzzleHttp\Client $mockHttpClient */ 468 | $mockHttpClient = Mockery::mock('\GuzzleHttp\Client'); 469 | $mockHttpClient 470 | ->shouldReceive('request')->with('GET', $this->mockClient->call, $options)->andReturn($mockHttpResponse); 471 | 472 | $this->mockCall 473 | ->shouldReceive('token')->with($this->mockClient)->andReturn('X-Cookie-Token'); 474 | 475 | $this->mockCall->request($mockHttpClient, 'get', $this->mockClient); 476 | } 477 | } --------------------------------------------------------------------------------