├── .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 | [](https://packagist.org/packages/leonjza/php-nessus-ng) [](https://packagist.org/packages/leonjza/php-nessus-ng) [](https://packagist.org/packages/leonjza/php-nessus-ng) [](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 | [](https://travis-ci.org/leonjza/PHPNessusNG)
4 | [](https://codeclimate.com/github/leonjza/PHPNessusNG)
5 | [](https://packagist.org/packages/leonjza/php-nessus-ng)
6 | [](https://packagist.org/packages/leonjza/php-nessus-ng)
7 | [](https://packagist.org/packages/leonjza/php-nessus-ng)
8 | [](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 | }
--------------------------------------------------------------------------------