├── nexpose
├── __init__.py
├── nexpose_criteria_constants.py
├── nexpose_status.py
├── nexpose_backup.py
├── nexpose_userauthenticator.py
├── python_utils.py
├── json_utils.py
├── nexpose_node.py
├── nexpose_privileges.py
├── xml_utils.py
├── nexpose_assetgroup.py
├── nexpose_criteria_operators.py
├── nexpose_assetfilter.py
├── nexpose_enginepool.py
├── nexpose_engine.py
├── nexpose_vulnerability.py
├── nexpose_report.py
├── nexpose_discoveryconnection.py
├── nexpose_vulnerabilityexception.py
├── nexpose_role.py
├── nexpose_tag.py
├── nexpose_ticket.py
├── nexpose_criteria.py
├── nexpose_asset.py
├── nexpose_scansummary.py
├── nexpose_sharedcredential.py
├── nexpose_user.py
├── nexpose_criteria_fields.py
├── nexpose_site.py
└── nexpose_credential.py
├── MANIFEST.in
├── requirements.txt
├── docs
├── modules.rst
├── index.rst
├── nexpose.rst
├── Makefile
├── make.bat
└── conf.py
├── requirements_dev.txt
├── setup.cfg
├── requirements_test.txt
├── demo
├── demo.cfg.default
└── sslfix.py
├── .travis.yml
├── test_fixtures
├── ReportHistoryResponse.xml
├── UserAuthenticatorListingResponse.xml
├── xml_fixtures.py
├── ReportListingResponse.xml
├── custom_tag_example.json
├── ReportConfigResponse_Samples.xml
├── data-scan_complete-assets_Nexpose6.json
└── default_tags.json
├── contributors.md
├── .flake8
├── tests
├── __init__.py
├── load_unittest.py
├── test_NexposeNode.py
├── test_LoadFixture.py
├── LoadFixture.py
├── test_NexposeUserAuthenticatorSummary.py
├── NexposeSessionSpy.py
├── test_NexposeReportConfigurationSummary.py
├── test_NexposeSession.py
├── test_NexposeTag.py
└── test_NexposeVulnerability.py
├── setup.py
├── .github
├── PULL_REQUEST_TEMPLATE.md
├── ISSUE_TEMPLATE.md
└── CONTRIBUTING.md
├── LICENSE
├── .gitignore
├── README.md
├── README.rst
└── CHANGELOG.md
/nexpose/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | include README.rst LICENSE
2 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | lxml>=3.6.0
2 | future>=0.16.0
--------------------------------------------------------------------------------
/docs/modules.rst:
--------------------------------------------------------------------------------
1 | nexpose
2 | =======
3 |
4 | .. toctree::
5 | :maxdepth: 4
6 |
7 | nexpose
8 |
--------------------------------------------------------------------------------
/requirements_dev.txt:
--------------------------------------------------------------------------------
1 | lxml>=3.6.0
2 | py>=1.4.31
3 | pytest>=2.9.2
4 | Sphinx>=1.4.5
5 | future>=0.16.0
--------------------------------------------------------------------------------
/setup.cfg:
--------------------------------------------------------------------------------
1 | [bdist_wheel]
2 | # Indicate that this library can be packaged universally for python 2 and 3.
3 | universal=1
--------------------------------------------------------------------------------
/requirements_test.txt:
--------------------------------------------------------------------------------
1 | flake8>=3.4.1; python_version >= '2.7'
2 | future>=0.16.0
3 | lxml>=3.6.0
4 | py>=1.4.31
5 | pytest>=2.9.2
6 | unittest2>=1.1.0; python_version == '2.6'
7 |
--------------------------------------------------------------------------------
/demo/demo.cfg.default:
--------------------------------------------------------------------------------
1 | # This file contains the Nexpose login information used by "run_demo.py".
2 | # Lines started with a #-character are ignored.
3 | # Copy "demo.cfg.default" to "demo.cfg".
4 | # Then edit "demo.cfg" as needed, keeping the format intact.
5 | # The username and/or password should not contain spaces or tabs.
6 | localhost 3780 nxadmin nxpassword
7 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | sudo: false
2 | branches:
3 | only:
4 | - master
5 | language: python
6 | cache: pip
7 | python:
8 | - 2.6
9 | - 2.7
10 | - 3.4
11 | - 3.5
12 | - 3.6
13 | install:
14 | - pip install -r requirements.txt
15 | - pip install -r requirements_test.txt
16 | script:
17 | - if [[ $TRAVIS_PYTHON_VERSION != 2.6 ]]; then flake8; fi
18 | - py.test tests
19 |
--------------------------------------------------------------------------------
/test_fixtures/ReportHistoryResponse.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/test_fixtures/UserAuthenticatorListingResponse.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/contributors.md:
--------------------------------------------------------------------------------
1 | # Project Contributors
2 |
3 | Original authors of the Nexpose client library for Python (Davinsi Labs):
4 |
5 | - [@dLabsPeterL](https://github.com/dLabsPeterL)
6 | - [@kbossaert](https://github.com/kbossaert)
7 |
8 | Rapid7 authors contributing to initial public open source release:
9 |
10 | - [@gschneider-r7](https://github.com/gschneider-r7)
11 | - [@bglass-r7](https://github.com/bglass-r7)
12 |
13 | Additional contributors:
14 |
15 |
--------------------------------------------------------------------------------
/test_fixtures/xml_fixtures.py:
--------------------------------------------------------------------------------
1 | # TODO: decide wether or not to use this class (currently unused)
2 |
3 | class NexposeXmlFixtures:
4 | LoginRequest = ''
5 | LoginResponse = ''
6 | LogoutRequest = ''
7 | LogoutResponse = ''
8 |
--------------------------------------------------------------------------------
/.flake8:
--------------------------------------------------------------------------------
1 | [flake8]
2 | exclude =
3 | .cache,
4 | .git,
5 | .github,
6 | *.txt,
7 | __pycache__,
8 | build,
9 | demo,
10 | dist,
11 | docs,
12 | test_fixtures
13 |
14 | ignore=
15 | # too many long lines to worry about for now, revisit later
16 | E501,
17 | # lots of unused imports, revisit later
18 | F401,
19 | # lambda assignment, revisit later
20 | E731,
21 | # need to clean up all imports, revisit later
22 | E402,
23 | F403,
24 | F405
25 |
--------------------------------------------------------------------------------
/tests/__init__.py:
--------------------------------------------------------------------------------
1 | # Future Imports for py2/3 backwards compat.
2 | from __future__ import (absolute_import, division, print_function,
3 | unicode_literals)
4 | from os import path
5 | from .load_unittest import *
6 | from future import standard_library
7 | standard_library.install_aliases()
8 |
9 | NexposeTestSuite = unittest.TestLoader().discover(path.dirname(__file__), "test_*.py")
10 |
11 |
12 | def main():
13 | runner = unittest.TextTestRunner(verbosity=2)
14 | runner.run(NexposeTestSuite)
15 |
--------------------------------------------------------------------------------
/nexpose/nexpose_criteria_constants.py:
--------------------------------------------------------------------------------
1 | # Future Imports for py2/3 backwards compat.
2 | from __future__ import (absolute_import, division, print_function,
3 | unicode_literals)
4 | from builtins import object
5 | from future import standard_library
6 | standard_library.install_aliases()
7 |
8 |
9 | class NexposeCriteriaConstant(object):
10 | class __metaclass__(type):
11 | @property
12 | def Name(cls):
13 | return cls.__name__
14 |
15 | def __str__(cls):
16 | return cls.Name
17 |
--------------------------------------------------------------------------------
/docs/index.rst:
--------------------------------------------------------------------------------
1 | .. nexpose-client-python documentation master file, created by
2 | sphinx-quickstart on Fri Jul 22 22:46:51 2016.
3 | You can adapt this file completely to your liking, but it should at least
4 | contain the root `toctree` directive.
5 |
6 | Welcome to nexpose-client-python's documentation!
7 | =================================================
8 |
9 | Contents:
10 |
11 | .. toctree::
12 | :maxdepth: 2
13 |
14 | .. automodule:: nexpose
15 |
16 | .. autoclass:: NexposeSessionBase
17 |
18 | Indices and tables
19 | ==================
20 |
21 | * :ref:`genindex`
22 | * :ref:`modindex`
23 | * :ref:`search`
24 |
25 |
--------------------------------------------------------------------------------
/test_fixtures/ReportListingResponse.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/tests/load_unittest.py:
--------------------------------------------------------------------------------
1 | # Future Imports for py2/3 backwards compat.
2 | from __future__ import (absolute_import, division, print_function,
3 | unicode_literals)
4 | import sys
5 | from future import standard_library
6 | standard_library.install_aliases()
7 |
8 |
9 | def _assertIsInstance(self, obj, class_or_type_or_tuple):
10 | self.assertTrue(isinstance(obj, class_or_type_or_tuple))
11 |
12 |
13 | if sys.version_info[0] == 2 and sys.version_info[1] <= 6:
14 | try:
15 | import unittest2 as unittest
16 | except ImportError:
17 | import unittest
18 |
19 | unittest.TestCase.assertIsInstance = _assertIsInstance
20 | else:
21 | import unittest
22 |
--------------------------------------------------------------------------------
/tests/test_NexposeNode.py:
--------------------------------------------------------------------------------
1 | # Future Imports for py2/3 backwards compat.
2 | from __future__ import (absolute_import, division, print_function,
3 | unicode_literals)
4 | from .load_unittest import unittest
5 | from .LoadFixture import LoadFixture, JSON
6 | from nexpose.nexpose_node import Node
7 | from future import standard_library
8 | standard_library.install_aliases()
9 |
10 |
11 | class NexposeNodeTestCase(unittest.TestCase):
12 | def testCreateFromJSON(self):
13 | fixture = LoadFixture('data-scan_complete-assets_Nexpose6.json')
14 | records = fixture['records']
15 | self.assertEqual(len(records), 3)
16 |
17 | record_empty = Node.CreateFromJSON(records[2]) # noqa F841
18 |
--------------------------------------------------------------------------------
/test_fixtures/custom_tag_example.json:
--------------------------------------------------------------------------------
1 | {
2 | "asset_ids":[
3 | 1
4 | ],
5 | "url":"https://localhost/api/2.0/tags/12",
6 | "tag_id":12,
7 | "tag_name":"example",
8 | "attributes":[
9 | {
10 | "tag_attribute_value":"Nexpose",
11 | "tag_attribute_name":"SOURCE",
12 | "tag_attribute_id":61
13 | },
14 | {
15 | "tag_attribute_value":"#de7200",
16 | "tag_attribute_name":"COLOR",
17 | "tag_attribute_id":63
18 | }
19 | ],
20 | "tag_config":{
21 | "asset_group_ids":[2,3],
22 | "site_ids":[4,5],
23 | "tag_associated_asset_ids":[6,7],
24 | "search_criteria":null
25 | },
26 | "tag_type":"CUSTOM"
27 | }
--------------------------------------------------------------------------------
/test_fixtures/ReportConfigResponse_Samples.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | vulnerable-exploited
7 | vulnerable-version
8 | potential
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/demo/sslfix.py:
--------------------------------------------------------------------------------
1 | # Future Imports for py2/3 backwards compat.
2 | from __future__ import (absolute_import, division, print_function,
3 | unicode_literals)
4 |
5 | # SOURCE: http://stackoverflow.com/questions/11772847/error-urlopen-error-errno-8-ssl-c504-eof-occurred-in-violation-of-protoco
6 | import ssl
7 | from functools import wraps
8 | from future import standard_library
9 | standard_library.install_aliases()
10 |
11 |
12 | def sslwrap(func):
13 | @wraps(func)
14 | def bar(*args, **kw):
15 | kw['ssl_version'] = ssl.PROTOCOL_TLSv1
16 | return func(*args, **kw)
17 | return bar
18 |
19 |
20 | def patch():
21 | if hasattr(ssl, '_create_unverified_context'):
22 | # Python >=2.7.9
23 | ssl._create_default_https_context = ssl._create_unverified_context
24 | else:
25 | # Python <2.7.9
26 | ssl.wrap_socket = sslwrap(ssl.wrap_socket)
27 |
--------------------------------------------------------------------------------
/tests/test_LoadFixture.py:
--------------------------------------------------------------------------------
1 | # Future Imports for py2/3 backwards compat.
2 | from __future__ import (absolute_import, division, print_function,
3 | unicode_literals)
4 | from .load_unittest import unittest
5 | from .LoadFixture import LoadFixture
6 | from future import standard_library
7 | standard_library.install_aliases()
8 |
9 |
10 | class LoadFixtureTestCase(unittest.TestCase):
11 | def testThatOurFixturesWillLoadCorrectly(self):
12 | fixture = LoadFixture("default_tags.json")
13 | self.assertEqual(5, fixture["total_available"])
14 | self.assertEqual(5, len(fixture["resources"]))
15 |
16 | def testThatLoadingNonExistingFixtureResultsInAnException(self):
17 | self.assertRaises(Exception, lambda: LoadFixture("should_not_exist.json"))
18 |
19 | def testThatLoadingInvalidFixtureTypeResultsInAnException(self):
20 | self.assertRaises(ValueError, lambda: LoadFixture("xml_fixtures.py"))
21 |
--------------------------------------------------------------------------------
/test_fixtures/data-scan_complete-assets_Nexpose6.json:
--------------------------------------------------------------------------------
1 | {"records":[{"operatingSystem":"Ubuntu Linux 14.04","nodeID":18,"scanStatus":"C","isMobile":false,"ipAddress":"172.16.254.138","scanStatusTranslation":"Completed","idType":"database","scanEngineName":"Local scan engine","duration":111334,"scanID":7,"assetID":"3","vulnerabilityCount":59,"id":"18","hostName":"nexpose"},{"operatingSystem":"Microsoft Windows","nodeID":17,"scanStatus":"C","isMobile":false,"ipAddress":"172.16.254.129","scanStatusTranslation":"Completed","idType":"database","scanEngineName":"Local scan engine","duration":85550,"scanID":7,"assetID":"4","vulnerabilityCount":2,"id":"17","hostName":"MAC_VM-WIN10"},{"operatingSystem":null,"nodeID":16,"scanStatus":"C","isMobile":false,"ipAddress":"172.16.254.1","scanStatusTranslation":"Completed","idType":"database","scanEngineName":"Local scan engine","duration":20923,"scanID":7,"assetID":"1","vulnerabilityCount":0,"id":"16","hostName":null}],"rowsPerPage":10,"sortColumn":"ipAddress","recordOffset":0,"sortDirection":"DESC","totalRecords":3}
--------------------------------------------------------------------------------
/nexpose/nexpose_status.py:
--------------------------------------------------------------------------------
1 | # Future Imports for py2/3 backwards compat.
2 | from __future__ import (absolute_import, division, print_function,
3 | unicode_literals)
4 | from builtins import object
5 | from future import standard_library
6 | standard_library.install_aliases()
7 |
8 |
9 | class NexposeStatus(object):
10 | STARTING = 'starting'
11 | NORMAL_MODE = 'normal_mode'
12 | MAINTENANCE_MODE = 'maintenance_mode'
13 | UNKNOWN = 'unknown'
14 |
15 | # To be compatible with older Nexpose versions; do not remove items from the list below, only add!
16 | _URL_TO_STATUS = {
17 | 'starting.html': STARTING,
18 | 'login.html': NORMAL_MODE,
19 | 'login.jsp': NORMAL_MODE,
20 | 'maintenance-login.html': MAINTENANCE_MODE,
21 | }
22 |
23 | @staticmethod
24 | def GetStatusFromURL(url):
25 | path = url.split('/')[-1] # get the last part of the URL
26 | status = NexposeStatus._URL_TO_STATUS.get(path, NexposeStatus.UNKNOWN)
27 | return status
28 |
--------------------------------------------------------------------------------
/nexpose/nexpose_backup.py:
--------------------------------------------------------------------------------
1 | # Future Imports for py2/3 backwards compat.
2 | from __future__ import (absolute_import, division, print_function,
3 | unicode_literals)
4 | from builtins import object
5 | from future import standard_library
6 | standard_library.install_aliases()
7 |
8 |
9 | class Backup(object):
10 | @staticmethod
11 | def CreateFromJSON(json_dict):
12 | backup = Backup()
13 | backup.name = json_dict["Download"]
14 | backup.date = json_dict["Date"] # Time ... / 1000 ? Timezone ?
15 | backup.description = json_dict["Description"]
16 | backup.nexpose_version = json_dict["Version"]
17 | backup.platform_independent = json_dict["Platform-Independent"]
18 | backup.size = int(json_dict["Size"])
19 | return backup
20 |
21 | def __init__(self):
22 | self.name = ''
23 | self.date = ''
24 | self.description = ''
25 | self.nexpose_version = ''
26 | self.platform_independent = False
27 | self.size = 0
28 |
--------------------------------------------------------------------------------
/nexpose/nexpose_userauthenticator.py:
--------------------------------------------------------------------------------
1 | # Future Imports for py2/3 backwards compat.
2 | from __future__ import (absolute_import, division, print_function,
3 | unicode_literals)
4 | from builtins import object
5 | from .xml_utils import get_attribute
6 | from future import standard_library
7 | standard_library.install_aliases()
8 |
9 |
10 | class UserAuthenticatorSummary(object):
11 | @staticmethod
12 | def CreateFromXML(xml_data):
13 | summary = UserAuthenticatorSummary()
14 | summary.id = int(get_attribute(xml_data, 'id', summary.id))
15 | summary.source = get_attribute(xml_data, 'authSource', summary.source)
16 | summary.module = get_attribute(xml_data, 'authModule', summary.module)
17 | summary.is_external = get_attribute(xml_data, 'external') == '1'
18 | return summary
19 |
20 | def __init__(self):
21 | self.id = 0
22 | self.source = ''
23 | self.module = ''
24 | self.is_external = False
25 |
26 | def AsXML(self):
27 | raise NotImplementedError(__func__) # noqa F821
28 |
--------------------------------------------------------------------------------
/tests/LoadFixture.py:
--------------------------------------------------------------------------------
1 | # Future Imports for py2/3 backwards compat.
2 | from __future__ import (absolute_import, division, print_function,
3 | unicode_literals)
4 | import json
5 | import lxml
6 | from os import path
7 | from future import standard_library
8 | standard_library.install_aliases()
9 |
10 | _SCRIPT_PATH = path.dirname(path.abspath(__file__))
11 | _FIXTURES_PATH = path.join(_SCRIPT_PATH, "..", "test_fixtures")
12 |
13 | XML = 'xml'
14 | JSON = 'json'
15 |
16 |
17 | def LoadFixture(filename):
18 | _, _, fixture_type = filename.rpartition('.')
19 | fixture_path = path.join(_FIXTURES_PATH, filename)
20 | with open(fixture_path) as data_file:
21 | if fixture_type == JSON:
22 | return json.load(data_file)
23 | if fixture_type == XML:
24 | return lxml.etree.fromstring(data_file.read())
25 | raise ValueError("unknown fixture type")
26 |
27 |
28 | def CreateEmptyFixture(fixture_type):
29 | if fixture_type == XML:
30 | return lxml.etree.fromstring('<_ />')
31 | raise ValueError("unknown fixture type")
32 |
--------------------------------------------------------------------------------
/nexpose/python_utils.py:
--------------------------------------------------------------------------------
1 | # Future Imports for py2/3 backwards compat.
2 | from __future__ import (absolute_import, division, print_function,
3 | unicode_literals)
4 | import inspect
5 | from future import standard_library
6 | standard_library.install_aliases()
7 |
8 |
9 | def is_subclass_of(variable, required_class):
10 | return inspect.isclass(variable) and issubclass(variable, required_class)
11 |
12 |
13 | def is_iterable(variable):
14 | return hasattr(variable, '__iter__')
15 |
16 |
17 | def remove_front_slash(uri):
18 | if uri.startswith('/'):
19 | uri = uri[1:]
20 | return uri
21 |
22 |
23 | # based on : http://stackoverflow.com/questions/6480723/urllib-urlencode-doesnt-like-unicode-values-how-about-this-workaround
24 | def utf8_encoded(data):
25 | if isinstance(data, str):
26 | return data.encode('utf8')
27 |
28 | if isinstance(data, str):
29 | # ensure now it can be decoded aka 'is valid UTF-8?'
30 | data.decode('utf8')
31 | return data
32 |
33 | if isinstance(data, dict):
34 | return dict(zip(iter(data.keys()), map(utf8_encoded, iter(data.values()))))
35 |
36 | if is_iterable(data):
37 | return list(map(utf8_encoded, data))
38 |
39 | # not sure how to handle this data type, return as-is
40 | return data
41 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | from setuptools import setup
3 |
4 |
5 | def readme():
6 | with open('README.rst', 'r') as f:
7 | return f.read()
8 |
9 |
10 | packages = [
11 | 'nexpose',
12 | ]
13 |
14 | requires = [
15 | 'lxml',
16 | 'future'
17 | ]
18 |
19 | setup(
20 | name='nexpose',
21 | packages=packages,
22 | package_data={'': ['LICENSE']},
23 | package_dir={'nexpose': 'nexpose'},
24 | include_package_data=True,
25 | version='0.1.7',
26 | license='BSD',
27 | description='The official Python Nexpose API client library',
28 | long_description=readme(),
29 | install_requires=requires,
30 | author='Davinsi Labs',
31 | url='https://github.com/rapid7/nexpose-client-python',
32 | download_url='https://github.com/rapid7/nexpose-client-python/releases',
33 | keywords=['nexpose', 'insightvm', 'rapid7'],
34 | classifiers=(
35 | 'Development Status :: 3 - Alpha',
36 | 'Intended Audience :: Developers',
37 | 'Natural Language :: English',
38 | 'Programming Language :: Python',
39 | 'Programming Language :: Python :: 2.6',
40 | 'Programming Language :: Python :: 2.7',
41 | 'Programming Language :: Python :: 3',
42 | 'Programming Language :: Python :: 3.4',
43 | 'Programming Language :: Python :: 3.5',
44 | 'Programming Language :: Python :: 3.6',
45 | ),
46 | )
47 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | ## Description
4 |
5 |
6 | ## Motivation and Context
7 |
8 |
9 |
10 | ## How Has This Been Tested?
11 |
12 |
13 |
14 |
15 | ## Screenshots (if appropriate):
16 |
17 |
18 | ## Types of changes
19 |
20 | - Bug fix (non-breaking change which fixes an issue)
21 | - New feature (non-breaking change which adds functionality)
22 | - Breaking change (fix or feature that would cause existing functionality to change)
23 |
24 | ## Checklist:
25 |
26 |
27 | - [ ] I have updated the documentation accordingly (if changes are required).
28 | - [ ] I have added tests to cover my changes.
29 | - [ ] All new and existing tests passed.
30 |
31 |
--------------------------------------------------------------------------------
/nexpose/json_utils.py:
--------------------------------------------------------------------------------
1 | # Future Imports for py2/3 backwards compat.
2 | from __future__ import (absolute_import, division, print_function,
3 | unicode_literals)
4 |
5 | from builtins import object
6 | from future import standard_library
7 | standard_library.install_aliases()
8 |
9 |
10 | class JSON(object):
11 | @staticmethod
12 | def CreateFromJSON(json_dict):
13 | raise NotImplementedError
14 |
15 | def as_json(self):
16 | raise NotImplementedError
17 |
18 |
19 | class HasID(object):
20 | pass
21 |
22 |
23 | def get_id(data, id_field_name):
24 | if isinstance(data, HasID):
25 | return data.id
26 | if isinstance(data, dict):
27 | return data.get(id_field_name, 0)
28 | return data # assume the data is the id
29 |
30 |
31 | def load_urls(json_dict, url_loader, ignore_error=False):
32 | from urllib.error import HTTPError
33 | assert isinstance(json_dict, dict)
34 | for key in list(json_dict.keys()):
35 | if isinstance(json_dict[key], dict):
36 | if json_dict[key].get('json', None) is not None:
37 | raise ValueError('json_dict[' + key + '] already contains a json-element')
38 | url = json_dict[key].get('url', None)
39 | if url is not None:
40 | try:
41 | json_dict[key]['json'] = url_loader(url)
42 | except HTTPError:
43 | if not ignore_error:
44 | raise
45 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | BSD 3-Clause License
2 |
3 | Copyright (c) 2016-2018, Davinsi Labs, Rapid7, Inc.
4 | All rights reserved.
5 |
6 | Redistribution and use in source and binary forms, with or without
7 | modification, are permitted provided that the following conditions are met:
8 |
9 | * Redistributions of source code must retain the above copyright notice, this
10 | list of conditions and the following disclaimer.
11 |
12 | * Redistributions in binary form must reproduce the above copyright notice,
13 | this list of conditions and the following disclaimer in the documentation
14 | and/or other materials provided with the distribution.
15 |
16 | * Neither the name of the copyright holder nor the names of its
17 | contributors may be used to endorse or promote products derived from
18 | this software without specific prior written permission.
19 |
20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | ## Expected Behavior
6 |
7 |
8 |
9 | ## Current Behavior
10 |
11 |
12 |
13 | ## Possible Solution
14 |
15 |
16 |
17 | ## Steps to Reproduce (for bugs)
18 |
19 |
20 |
21 |
22 | Python code that reproduces the issue:
23 | ```python
24 | print("this is only an example Python code block")
25 | ```
26 |
27 | ## Context
28 |
29 |
30 |
31 | ## Your Environment
32 |
33 |
34 | * Nexpose-client-python version:
35 | * Python version:
36 | * Operating System and version:
37 | * Nexpose product version:
38 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # original entries
2 | /wingide/dl_nexpose.wpu
3 | /demo/demo.cfg
4 |
5 | # Byte-compiled / optimized / DLL files
6 | __pycache__/
7 | *.py[cod]
8 | *$py.class
9 |
10 | # C extensions
11 | *.so
12 |
13 | # Distribution / packaging
14 | .Python
15 | env/
16 | build/
17 | develop-eggs/
18 | dist/
19 | downloads/
20 | eggs/
21 | .eggs/
22 | lib/
23 | lib64/
24 | parts/
25 | sdist/
26 | var/
27 | *.egg-info/
28 | .installed.cfg
29 | *.egg
30 |
31 | # PyInstaller
32 | # Usually these files are written by a python script from a template
33 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
34 | *.manifest
35 | *.spec
36 |
37 | # Installer logs
38 | pip-log.txt
39 | pip-delete-this-directory.txt
40 |
41 | # Unit test / coverage reports
42 | htmlcov/
43 | .tox/
44 | .coverage
45 | .coverage.*
46 | .cache
47 | nosetests.xml
48 | coverage.xml
49 | *,cover
50 | .hypothesis/
51 |
52 | # Translations
53 | *.mo
54 | *.pot
55 |
56 | # Django stuff:
57 | *.log
58 | local_settings.py
59 |
60 | # Flask stuff:
61 | instance/
62 | .webassets-cache
63 |
64 | # Scrapy stuff:
65 | .scrapy
66 |
67 | # Sphinx documentation
68 | docs/_build/
69 |
70 | # PyBuilder
71 | target/
72 |
73 | # IPython Notebook
74 | .ipynb_checkpoints
75 |
76 | # pyenv
77 | .python-version
78 |
79 | # celery beat schedule file
80 | celerybeat-schedule
81 |
82 | # dotenv
83 | .env
84 |
85 | # virtualenv
86 | venv/
87 | ENV/
88 |
89 | # Spyder project settings
90 | .spyderproject
91 |
92 | # Rope project settings
93 | .ropeproject
94 |
95 | # Idea files
96 | /.idea/
97 |
98 | # Linux files
99 | *.swp
100 |
101 | # Editor Specific
102 | .vscode
--------------------------------------------------------------------------------
/nexpose/nexpose_node.py:
--------------------------------------------------------------------------------
1 | # Future Imports for py2/3 backwards compat.
2 | from __future__ import (absolute_import, division, print_function,
3 | unicode_literals)
4 | from builtins import object
5 | from future import standard_library
6 | standard_library.install_aliases()
7 |
8 |
9 | class AssetHostTypes(object):
10 | Empty = ''
11 | Guest = 'GUEST'
12 | Hypervisor = 'HYPERVISOR'
13 | Physical = 'PHYSICAL'
14 | Mobile = 'MOBILE'
15 |
16 |
17 | class NodeScanStatus(object):
18 | UNKNOWN = ''
19 | COMPLETE = 'C'
20 |
21 |
22 | # NOTE: the tags below are available but are currently not copied to a NodeBase object
23 | # id
24 | # idType
25 | # isMobile
26 | # scanEngineName
27 | # scanStatusTranslation
28 | # vulnerabilityCount
29 | class NodeBase(object):
30 | def InitializeFromJSON(self, json_dict):
31 | self.id = json_dict['nodeID']
32 | self.asset_id = json_dict['assetID']
33 | self.host_name = json_dict['hostName']
34 | self.os_name = json_dict['operatingSystem']
35 | self.ip_address = json_dict['ipAddress']
36 | self.scan_id = json_dict['scanID']
37 | self.scan_status = json_dict['scanStatus']
38 | self.scan_duration = json_dict['duration'] # TODO: is this in ms?
39 |
40 | def __init__(self):
41 | self.id = 0
42 | self.asset_id = 0
43 | self.ip_address = ''
44 | self.host_name = ''
45 | self.os_name = ''
46 | self.scan_id = 0
47 | self.scan_status = NodeScanStatus.UNKNOWN
48 | self.scan_duration = 0
49 |
50 |
51 | class Node(NodeBase):
52 | @staticmethod
53 | def CreateFromJSON(json_dict):
54 | node = Node()
55 | NodeBase.InitializeFromJSON(node, json_dict)
56 |
--------------------------------------------------------------------------------
/tests/test_NexposeUserAuthenticatorSummary.py:
--------------------------------------------------------------------------------
1 | # Future Imports for py2/3 backwards compat.
2 | from __future__ import (absolute_import, division, print_function,
3 | unicode_literals)
4 | from .load_unittest import unittest
5 | from .LoadFixture import CreateEmptyFixture, LoadFixture, XML
6 | from nexpose.nexpose_userauthenticator import UserAuthenticatorSummary
7 | from future import standard_library
8 | standard_library.install_aliases()
9 |
10 |
11 | class NexposeUserAuthenticatorSummaryTestCase(unittest.TestCase):
12 | def testCreateFromXML(self):
13 | fixture = CreateEmptyFixture(XML)
14 | authenticator = UserAuthenticatorSummary.CreateFromXML(fixture)
15 | self.assertEquals(0, authenticator.id)
16 | self.assertEquals('', authenticator.source)
17 | self.assertEquals('', authenticator.module)
18 | self.assertEquals(False, authenticator.is_external)
19 |
20 | fixture = LoadFixture('UserAuthenticatorListingResponse.xml')
21 |
22 | authenticator = UserAuthenticatorSummary.CreateFromXML(fixture[0])
23 | self.assertEquals(1, authenticator.id)
24 | self.assertEquals('Builtin Administrators', authenticator.source)
25 | self.assertEquals('XML', authenticator.module)
26 | self.assertEquals(False, authenticator.is_external)
27 |
28 | authenticator = UserAuthenticatorSummary.CreateFromXML(fixture[1])
29 | self.assertEquals(2, authenticator.id)
30 | self.assertEquals('Builtin Users', authenticator.source)
31 | self.assertEquals('DataStore', authenticator.module)
32 | self.assertEquals(False, authenticator.is_external)
33 |
34 | authenticator = UserAuthenticatorSummary.CreateFromXML(fixture[2])
35 | self.assertEquals(3, authenticator.id)
36 | self.assertEquals('My Users', authenticator.source)
37 | self.assertEquals('LDAP', authenticator.module)
38 | self.assertEquals(True, authenticator.is_external)
39 |
--------------------------------------------------------------------------------
/tests/NexposeSessionSpy.py:
--------------------------------------------------------------------------------
1 | # Future Imports for py2/3 backwards compat.
2 | from __future__ import (absolute_import, division, print_function,
3 | unicode_literals)
4 | from builtins import object
5 | from nexpose.nexpose import NexposeSession, NexposeConnectionException
6 | from nexpose.xml_utils import as_xml
7 | from future import standard_library
8 | standard_library.install_aliases()
9 |
10 |
11 | class NexposeSessionSpy(NexposeSession):
12 | def __init__(self, host, port, username, password):
13 | NexposeSession.__init__(self, host, port, username, password)
14 | self.XmlStringToReturnOnExecute = None
15 |
16 | def GetURI_APIv1d1(self):
17 | return self._URI_APIv1d1
18 |
19 | def GetLoginRequest(self):
20 | return self._login_request
21 |
22 | def GetSessionID(self):
23 | return self._session_id
24 |
25 | def SetSessionID(self, session_id):
26 | self._session_id = session_id
27 |
28 | def _Execute_Fake(self, request):
29 | try:
30 | if self.XmlStringToReturnOnExecute:
31 | return as_xml(self.XmlStringToReturnOnExecute)
32 | return request # return the request as an answer
33 | except Exception as ex:
34 | raise NexposeConnectionException("Unable to execute the fake request: {0}!".format(ex), ex)
35 |
36 | def _Execute_APIv1d1(self, request):
37 | return self._Execute_Fake(request)
38 |
39 | def _Execute_APIv1d2(self, request): # TODO: write tests that use this function ? TDD ?
40 | return self._Execute_Fake(request)
41 |
42 |
43 | class SpyFactory(object):
44 | @staticmethod
45 | def CreateEmpty():
46 | return NexposeSessionSpy(host="", port=0, username="", password="")
47 |
48 | @staticmethod
49 | def CreateWithDefaultLogin(host, port=3780):
50 | return NexposeSessionSpy(host, port, "nxadmin", "nxpassword")
51 |
52 | @staticmethod
53 | def CreateOpenSession(session_id):
54 | session = SpyFactory.CreateEmpty()
55 | session.SetSessionID(session_id)
56 | return session
57 |
--------------------------------------------------------------------------------
/nexpose/nexpose_privileges.py:
--------------------------------------------------------------------------------
1 | # Future Imports for py2/3 backwards compat.
2 | from __future__ import (absolute_import, division, print_function,
3 | unicode_literals)
4 | from builtins import object
5 | from future import standard_library
6 | standard_library.install_aliases()
7 |
8 |
9 | class AssetGroupPrivileges(object):
10 | ConfigureAssets = 'ConfigureAssets'
11 | ConfigureAssets = 'ViewAssetData'
12 |
13 |
14 | class GlobalPrivileges(object):
15 | AddUsersToGroup = 'AddUsersToGroup'
16 | AddUsersToReport = 'AddUsersToReport'
17 | AddUsersToSite = 'AddUsersToSite'
18 | ApproveVulnExceptions = 'ApproveVulnExceptions'
19 | ApproveVulnerabilityExceptions = ApproveVulnExceptions
20 | CloseTickets = 'CloseTickets'
21 | ConfigureGlobalSettings = 'ConfigureGlobalSettings'
22 | CreateReports = 'CreateReports'
23 | CreateTickets = 'CreateTickets'
24 | DeleteVulnExceptions = 'DeleteVulnExceptions'
25 | DeleteVulnerabilityExceptions = DeleteVulnExceptions
26 | GenerateRestrictedReports = 'GenerateRestrictedReports'
27 | ManageAssetGroups = 'ManageAssetGroups'
28 | ManageDynamicAssetGroups = 'ManageDynamicAssetGroups'
29 | ManagePolicies = 'ManagePolicies'
30 | ManageReportTemplates = 'ManageReportTemplates'
31 | ManageScanEngines = 'ManageScanEngines'
32 | ManageScanTemplates = 'ManageScanTemplates'
33 | ManageSites = 'ManageSites'
34 | ManageTags = 'ManageTags'
35 | SubmitVulnExceptions = 'SubmitVulnExceptions'
36 | SubmitVulnerabilityExceptions = SubmitVulnExceptions
37 | TicketAssignee = 'TicketAssignee'
38 |
39 |
40 | class SitePrivileges(object):
41 | ConfigureAlerts = 'ConfigureAlerts'
42 | ConfigureCredentials = 'ConfigureCredentials'
43 | ConfigureEngines = 'ConfigureEngines'
44 | ConfigureScanTemplates = 'ConfigureScanTemplates'
45 | ConfigureScheduleScans = 'ConfigureScheduleScans'
46 | ConfigureSiteSettings = 'ConfigureSiteSettings'
47 | ConfigureTargets = 'ConfigureTargets'
48 | ManualScans = 'ManualScans'
49 | PurgeData = 'PurgeData'
50 | ViewAssetData = 'ViewAssetData'
51 |
--------------------------------------------------------------------------------
/nexpose/xml_utils.py:
--------------------------------------------------------------------------------
1 | # Future Imports for py2/3 backwards compat.
2 | from __future__ import (absolute_import, division, print_function,
3 | unicode_literals)
4 | from lxml import etree
5 | from io import StringIO
6 | from future import standard_library
7 | standard_library.install_aliases()
8 |
9 |
10 | def create_element(tag, optional_attributes=None):
11 | request = etree.Element(tag)
12 | if optional_attributes:
13 | for tag, value in iter(optional_attributes.items()):
14 | request.attrib[tag] = "{0}".format(value)
15 | return request
16 |
17 |
18 | def get_attribute(xml_data, attribute_name, default_value=None):
19 | if xml_data is None:
20 | return default_value
21 | return xml_data.attrib.get(attribute_name, default_value)
22 |
23 |
24 | def get_children_of(xml_data, element_name):
25 | element = get_element(xml_data, element_name, default_value=None)
26 | return element.getchildren() if element is not None else ()
27 |
28 |
29 | def get_element(xml_data, element_name, default_value=None):
30 | if xml_data is None:
31 | return default_value
32 | return xml_data.find(element_name)
33 |
34 |
35 | def get_content_of(xml_data, element_name, default_value=None):
36 | if xml_data is None:
37 | return default_value
38 | element = xml_data.find(element_name)
39 | if element is None:
40 | return default_value
41 | if element.text is None:
42 | return default_value
43 | return element.text
44 |
45 |
46 | def as_string(xml_data):
47 | return etree.tostring(xml_data)
48 |
49 |
50 | def from_large_string(s):
51 | return etree.XML(s.encode('utf-8'))
52 |
53 |
54 | def as_xml(s):
55 | # Note:
56 | # There is a bug in the StartUpdateResponse, in case of a failure (no internet connection),
57 | # two StartUpdateResponse XML objects are returned, one indicating failure, one indicating success.
58 | # We handle this bug here (wrong place?!), by embedding the returned XML in a single object
59 | # and returning the first element after conversion.
60 | if s.startswith(''):
61 | return from_large_string(s)
62 | s = '<_>' + s + ''
63 | return from_large_string(s).getchildren()[0]
64 |
--------------------------------------------------------------------------------
/nexpose/nexpose_assetgroup.py:
--------------------------------------------------------------------------------
1 | # Future Imports for py2/3 backwards compat.
2 | from __future__ import (absolute_import, division, print_function,
3 | unicode_literals)
4 | from builtins import object
5 | from .xml_utils import get_attribute, get_element, as_string
6 | from .nexpose_asset import AssetSummary
7 | from future import standard_library
8 | standard_library.install_aliases()
9 |
10 |
11 | class _AssetGroupBase(object):
12 | def InitializeFromXML(self, xml_data):
13 | self.id = int(get_attribute(xml_data, 'id', self.id))
14 | self.name = get_attribute(xml_data, 'name', self.name)
15 | self.short_description = get_attribute(xml_data, 'description', self.short_description)
16 |
17 | def __init__(self):
18 | self.id = 0
19 | self.name = 0
20 | self.short_description = '' # API 1.1 removes newlines that were added through the UI
21 |
22 |
23 | class AssetGroupSummary(_AssetGroupBase):
24 | @staticmethod
25 | def CreateFromXML(xml_data):
26 | asset_group = AssetGroupSummary()
27 | asset_group.InitializeFromXML(xml_data)
28 | asset_group.risk_score = float(get_attribute(xml_data, 'riskscore', asset_group.risk_score))
29 | return asset_group
30 |
31 | def __init__(self):
32 | _AssetGroupBase.__init__(self)
33 | self.risk_score = 0.0
34 |
35 |
36 | class AssetGroupConfiguration(_AssetGroupBase):
37 | @staticmethod
38 | def CreateFromXML(xml_data):
39 | xml_devices = get_element(xml_data, 'Devices', None)
40 | print(as_string(xml_data))
41 | config = AssetGroupConfiguration()
42 | config.InitializeFromXML(xml_data)
43 | config.description = config.short_description
44 | if xml_devices is not None:
45 | config.asset_summaries = [AssetSummary.CreateFromXML(xml_device) for xml_device in xml_devices.getchildren() if xml_device.tag == 'device']
46 | return config
47 |
48 | @staticmethod
49 | def Create():
50 | config = AssetGroupConfiguration()
51 | config.id = -1
52 | return config
53 |
54 | def __init__(self):
55 | _AssetGroupBase.__init__(self)
56 | self.description = None
57 | self.asset_summaries = []
58 |
--------------------------------------------------------------------------------
/nexpose/nexpose_criteria_operators.py:
--------------------------------------------------------------------------------
1 | # Future Imports for py2/3 backwards compat.
2 | from __future__ import (absolute_import, division, print_function,
3 | unicode_literals)
4 | from builtins import object
5 | from future import standard_library
6 | from future.utils import with_metaclass
7 |
8 | standard_library.install_aliases()
9 |
10 |
11 | class MetaNexposeCriteriaOperator(type):
12 | @property
13 | def Code(cls):
14 | return cls.__name__
15 |
16 | def __str__(cls):
17 | return cls.Code
18 |
19 |
20 | class NexposeCriteriaOperator(with_metaclass(MetaNexposeCriteriaOperator, object)):
21 | pass
22 |
23 |
24 | class AND(NexposeCriteriaOperator):
25 | pass
26 |
27 |
28 | class OR(NexposeCriteriaOperator):
29 | pass
30 |
31 |
32 | class ARE(NexposeCriteriaOperator):
33 | pass
34 |
35 |
36 | class IS(NexposeCriteriaOperator):
37 | pass
38 |
39 |
40 | class IS_NOT(NexposeCriteriaOperator):
41 | pass
42 |
43 |
44 | class STARTS_WITH(NexposeCriteriaOperator):
45 | pass
46 |
47 |
48 | class ENDS_WITH(NexposeCriteriaOperator):
49 | pass
50 |
51 |
52 | class IS_EMPTY(NexposeCriteriaOperator):
53 | pass
54 |
55 |
56 | class IS_NOT_EMPTY(NexposeCriteriaOperator):
57 | pass
58 |
59 |
60 | class IS_APPLIED(NexposeCriteriaOperator):
61 | pass
62 |
63 |
64 | class IS_NOT_APPLIED(NexposeCriteriaOperator):
65 | pass
66 |
67 |
68 | class CONTAINS(NexposeCriteriaOperator):
69 | pass
70 |
71 |
72 | class NOT_CONTAINS(NexposeCriteriaOperator):
73 | pass
74 |
75 |
76 | class INCLUDE(NexposeCriteriaOperator):
77 | pass
78 |
79 |
80 | class DO_NOT_INCLUDE(NexposeCriteriaOperator):
81 | pass
82 |
83 |
84 | class IN(NexposeCriteriaOperator):
85 | pass
86 |
87 |
88 | class NOT_IN(NexposeCriteriaOperator):
89 | pass
90 |
91 |
92 | class IN_RANGE(NexposeCriteriaOperator):
93 | pass
94 |
95 |
96 | class LESS_THAN(NexposeCriteriaOperator):
97 | pass
98 |
99 |
100 | class GREATER_THAN(NexposeCriteriaOperator):
101 | pass
102 |
103 |
104 | class ON_OR_BEFORE(NexposeCriteriaOperator):
105 | pass
106 |
107 |
108 | class ON_OR_AFTER(NexposeCriteriaOperator):
109 | pass
110 |
111 |
112 | class BETWEEN(NexposeCriteriaOperator):
113 | pass
114 |
115 |
116 | class EARLIER_THAN(NexposeCriteriaOperator):
117 | pass
118 |
119 |
120 | class WITHIN_THE_LAST(NexposeCriteriaOperator):
121 | pass
122 |
--------------------------------------------------------------------------------
/nexpose/nexpose_assetfilter.py:
--------------------------------------------------------------------------------
1 | # Future Imports for py2/3 backwards compat.
2 | from __future__ import (absolute_import, division, print_function,
3 | unicode_literals)
4 | from .json_utils import JSON
5 | from .nexpose_criteria import Criteria, Criterion
6 | from .nexpose_asset import AssetBase
7 | import json
8 | from future import standard_library
9 | standard_library.install_aliases()
10 |
11 |
12 | class FilteredAsset(AssetBase, JSON):
13 | @staticmethod
14 | def CreateFromJSON(json_dict):
15 | asset = FilteredAsset()
16 | asset.id = json_dict['assetID']
17 | asset.risk_score = json_dict['riskScore']
18 |
19 | asset.assessed = json_dict['assessed']
20 | asset.malware_count = json_dict['malwareCount']
21 | asset.vulnerability_count = json_dict['vulnCount']
22 | asset.exploit_count = json_dict['exploitCount']
23 | asset.asset_name = json_dict['assetName'] # TODO: could be 'host' from AssetSummary ?
24 | asset.os_id = json_dict['assetOSID']
25 |
26 | # see also AssetSummary
27 | asset.site_id = json_dict['sitePermissions'][0]['siteID']
28 |
29 | # see also AssetDetails
30 | asset.os_name = json_dict['assetOSName']
31 | asset.ip_address = json_dict['assetIP']
32 | asset.lastScanDate = json_dict['lastScanDate'] # TODO: convert to Date object ?
33 |
34 | # Replace JSON-nulls by empty strings
35 | if asset.os_name is None:
36 | asset.os_name = ''
37 | if asset.asset_name is None:
38 | asset.asset_name = ''
39 |
40 | return asset
41 |
42 | def __init__(self):
43 | AssetBase.__init__(self)
44 |
45 | self.assessed = False
46 | self.malware_count = 0
47 | self.vulnerability_count = 0
48 | self.exploit_count = 0
49 | self.asset_name = '' # TODO: could be 'host' from AssetSummary ?
50 | self.os_id = 0
51 |
52 | # see also AssetSummary
53 | self.site_id = 0
54 |
55 | # see also AssetDetails
56 | self.os_name = None
57 | self.ip_address = ''
58 | self.last_scan_date = ''
59 |
60 |
61 | class AssetFilter(JSON):
62 | def __init__(self, criteria_or_criterion):
63 | if isinstance(criteria_or_criterion, Criterion):
64 | criteria_or_criterion = Criteria.Create(criteria_or_criterion)
65 | assert isinstance(criteria_or_criterion, Criteria)
66 | self.criteria = criteria_or_criterion
67 |
68 | def as_json(self):
69 | js = dict()
70 | js['dir'] = 'ASC'
71 | js['sort'] = 'assetIP' # why can't we sort on ID?
72 | js['table-id'] = 'assetfilter'
73 | js['searchCriteria'] = json.dumps(self.criteria.as_json(), separators=(',', ':'))
74 | return js
75 |
--------------------------------------------------------------------------------
/nexpose/nexpose_enginepool.py:
--------------------------------------------------------------------------------
1 | # Future Imports for py2/3 backwards compat.
2 | from __future__ import (absolute_import, division, print_function,
3 | unicode_literals)
4 | from builtins import object
5 | from .xml_utils import get_attribute, create_element
6 | from future import standard_library
7 | standard_library.install_aliases()
8 |
9 |
10 | class EnginePoolBase(object):
11 | def InitalizeFromXML(self, xml_data):
12 | self.id = int(get_attribute(xml_data, 'id', self.id))
13 | self.name = get_attribute(xml_data, 'name', self.name)
14 | self.host = get_attribute(xml_data, 'address', self.host)
15 | self.port = int(get_attribute(xml_data, 'port', self.port))
16 | self.scope = get_attribute(xml_data, 'scope', self.scope)
17 |
18 | def __init__(self):
19 | self.id = 0
20 | self.name = ''
21 | self.host = ''
22 | self.port = 40814
23 | self.scope = 'silo'
24 |
25 |
26 | class EnginePoolSummary(EnginePoolBase):
27 | @staticmethod
28 | def CreateFromXML(xml_data):
29 | summary = EnginePoolSummary()
30 | summary.InitalizeFromXML(xml_data)
31 | summary.status = get_attribute(xml_data, 'status', summary.status)
32 | return summary
33 |
34 | def __init__(self):
35 | EnginePoolBase.__init__(self)
36 | self.status = EnginePoolStatus.Unknown # noqa F821
37 |
38 |
39 | class EnginePoolConfiguration(EnginePoolBase):
40 | @staticmethod
41 | def CreateFromXML(xml_data):
42 | config = EnginePoolConfiguration()
43 | config.InitalizeFromXML(xml_data)
44 | config.priority = get_attribute(xml_data, 'priority', config.priority)
45 | config.assigned_sites = [(int(get_attribute(xml_site, 'id', 0)), get_attribute(xml_data, 'name', '')) for xml_site in xml_data.getchildren() if xml_site.tag == 'Site']
46 | return config
47 |
48 | @staticmethod
49 | def Create():
50 | config = EnginePoolConfiguration()
51 | config.id = -1
52 | return config
53 |
54 | @staticmethod
55 | def CreateNamed(name):
56 | config = EnginePoolConfiguration.Create()
57 | config.name = name
58 | return config
59 |
60 | def __init__(self):
61 | EnginePoolBase.__init__(self)
62 | self.priority = EnginePoolPriority.Normal # noqa F821
63 | self.assigned_sites = []
64 |
65 | def AsXML(self, exclude_id):
66 | attributes = {}
67 | if not exclude_id:
68 | attributes['id'] = self.id
69 | attributes['name'] = self.name
70 | attributes['address'] = self.host
71 | attributes['port'] = self.port
72 | attributes['scope'] = self.scope
73 | attributes['priority'] = self.priority
74 | xml_data = create_element('EnginePoolConfig', attributes)
75 | return xml_data
76 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # DEPRECATED
2 | The [RESTful API for the Nexpose/InsightVM Security Console](https://help.rapid7.com/insightvm/en-us/api/index.html) has rendered this library obsolete. If you require a Python library for that API you can use a [generated client](https://github.com/rapid7/vm-console-client-python). Clients for other languages can be generated from the Swagger specification. Note that generated clients are not officially supported or maintained by Rapid7.
3 |
4 | This project will not receive new changes from Rapid7, though pull requests may still be accepted and new releases published on request.
5 |
6 | # nexpose-client-python
7 | [](http://unmaintained.tech/) [](https://travis-ci.org/rapid7/nexpose-client-python) [](https://pypi.python.org/pypi/nexpose)  [](https://raw.githubusercontent.com/rapid7/nexpose-client-python/master/LICENSE) 
8 |
9 | This is the official Python package for the Python Nexpose API client library.
10 |
11 | For assistance with using the library or to discuss different approaches, please open an issue. To share or discuss scripts which use the library head over to the [Nexpose Resources](https://github.com/rapid7/nexpose-resources) project.
12 |
13 | Check out the [wiki](https://github.com/rapid7/nexpose-client-python/wiki) for walk-throughs and other documentation. Submit bugs and feature requests on the [issues](https://github.com/rapid7/nexpose-client-python/issues) page.
14 |
15 | This library provides calls to the Nexpose XML APIs version 1.1 and 1.2.
16 |
17 | nexpose-client-python uses [Semantic Versioning](http://semver.org/). This allows for confident use of [version pinning](https://www.python.org/dev/peps/pep-0440/#version-specifiers) in your requirements file.
18 |
19 | Install the library using pip: `pip install nexpose`
20 |
21 | ## Release Notes
22 |
23 | Release notes are available on the [Releases](https://github.com/rapid7/nexpose-client-python/releases) page.
24 |
25 | ## Contributions
26 |
27 | We welcome contributions to this package. Please see [CONTRIBUTING](.github/CONTRIBUTING.md) for details.
28 |
29 | Full usage examples or task-oriented scripts should be submitted to the [Nexpose Resources](https://github.com/rapid7/nexpose-resources) project. Smaller examples can be added to the [wiki](https://github.com/rapid7/nexpose-client-python/wiki).
30 |
31 | ## License
32 |
33 | The nexpose-client-python library is provided under the 3-Clause BSD License. See [LICENSE](./LICENSE) for details.
34 |
35 | ## Credits
36 | Davinsi Labs
37 | Rapid7, Inc.
38 |
39 | See [contributors](./contributors.md) for more info.
40 |
--------------------------------------------------------------------------------
/test_fixtures/default_tags.json:
--------------------------------------------------------------------------------
1 | {
2 | "page":1,
3 | "sort":null,
4 | "resources":[
5 | {
6 | "attributes":[
7 | {
8 | "tag_attribute_name":"SOURCE",
9 | "tag_attribute_value":"Built-in",
10 | "tag_attribute_id":2
11 | },
12 | {
13 | "tag_attribute_name":"RISK_MODIFIER",
14 | "tag_attribute_value":"2.0",
15 | "tag_attribute_id":1
16 | }
17 | ],
18 | "url":"https://localhost/api/2.0/tags/1",
19 | "asset_ids":[
20 |
21 | ],
22 | "tag_config":null,
23 | "tag_id":1,
24 | "tag_type":"CRITICALITY",
25 | "tag_name":"Very High"
26 | },
27 | {
28 | "attributes":[
29 | {
30 | "tag_attribute_name":"RISK_MODIFIER",
31 | "tag_attribute_value":"1.5",
32 | "tag_attribute_id":5
33 | },
34 | {
35 | "tag_attribute_name":"SOURCE",
36 | "tag_attribute_value":"Built-in",
37 | "tag_attribute_id":6
38 | }
39 | ],
40 | "url":"https://localhost/api/2.0/tags/2",
41 | "asset_ids":[
42 |
43 | ],
44 | "tag_config":null,
45 | "tag_id":2,
46 | "tag_type":"CRITICALITY",
47 | "tag_name":"High"
48 | },
49 | {
50 | "attributes":[
51 | {
52 | "tag_attribute_name":"RISK_MODIFIER",
53 | "tag_attribute_value":"1.0",
54 | "tag_attribute_id":9
55 | },
56 | {
57 | "tag_attribute_name":"SOURCE",
58 | "tag_attribute_value":"Built-in",
59 | "tag_attribute_id":10
60 | }
61 | ],
62 | "url":"https://localhost/api/2.0/tags/3",
63 | "asset_ids":[
64 |
65 | ],
66 | "tag_config":null,
67 | "tag_id":3,
68 | "tag_type":"CRITICALITY",
69 | "tag_name":"Medium"
70 | },
71 | {
72 | "attributes":[
73 | {
74 | "tag_attribute_name":"SOURCE",
75 | "tag_attribute_value":"Built-in",
76 | "tag_attribute_id":14
77 | },
78 | {
79 | "tag_attribute_name":"RISK_MODIFIER",
80 | "tag_attribute_value":"0.75",
81 | "tag_attribute_id":13
82 | }
83 | ],
84 | "url":"https://localhost/api/2.0/tags/4",
85 | "asset_ids":[
86 |
87 | ],
88 | "tag_config":null,
89 | "tag_id":4,
90 | "tag_type":"CRITICALITY",
91 | "tag_name":"Low"
92 | },
93 | {
94 | "attributes":[
95 | {
96 | "tag_attribute_name":"RISK_MODIFIER",
97 | "tag_attribute_value":"0.5",
98 | "tag_attribute_id":17
99 | },
100 | {
101 | "tag_attribute_name":"SOURCE",
102 | "tag_attribute_value":"Built-in",
103 | "tag_attribute_id":18
104 | }
105 | ],
106 | "url":"https://localhost/api/2.0/tags/5",
107 | "asset_ids":[
108 |
109 | ],
110 | "tag_config":null,
111 | "tag_id":5,
112 | "tag_type":"CRITICALITY",
113 | "tag_name":"Very Low"
114 | }
115 | ],
116 | "url":"https://localhost/api/2.0/tags?per_page=2147483647",
117 | "per_page":2147483647,
118 | "total_available":5,
119 | "first_url":"https://localhost/api/2.0/tags?per_page=2147483647\u0026page=1",
120 | "last_url":"https://localhost/api/2.0/tags?per_page=2147483647\u0026page=1",
121 | "next_url":null,
122 | "previous_url":null
123 | }
--------------------------------------------------------------------------------
/nexpose/nexpose_engine.py:
--------------------------------------------------------------------------------
1 | # Future Imports for py2/3 backwards compat.
2 | from __future__ import (absolute_import, division, print_function,
3 | unicode_literals)
4 | from builtins import object
5 | from .xml_utils import get_attribute, create_element
6 | from future import standard_library
7 | standard_library.install_aliases()
8 |
9 |
10 | class EnginePriority(object):
11 | VeryLow = 'very-low'
12 | Low = 'low'
13 | Normal = 'normal'
14 | High = 'high'
15 | VeryHigh = 'very high'
16 |
17 |
18 | class EngineStatus(object):
19 | Active = 'active'
20 | PendingAuthorization = 'pending-authorization'
21 | Incompatible = 'incompatible'
22 | NotResponding = 'not-responding'
23 | Unknown = 'unknown'
24 |
25 |
26 | class EngineBase(object):
27 | def InitalizeFromXML(self, xml_data):
28 | self.id = int(get_attribute(xml_data, 'id', self.id))
29 | self.name = get_attribute(xml_data, 'name', self.name)
30 | self.host = get_attribute(xml_data, 'address', self.host)
31 | self.port = int(get_attribute(xml_data, 'port', self.port))
32 | self.scope = get_attribute(xml_data, 'scope', self.scope)
33 |
34 | def __init__(self):
35 | self.id = 0
36 | self.name = ''
37 | self.host = ''
38 | self.port = 40814
39 | self.scope = 'silo'
40 |
41 |
42 | class EngineSummary(EngineBase):
43 | @staticmethod
44 | def CreateFromXML(xml_data):
45 | summary = EngineSummary()
46 | summary.InitalizeFromXML(xml_data)
47 | summary.status = get_attribute(xml_data, 'status', summary.status)
48 | return summary
49 |
50 | def __init__(self):
51 | EngineBase.__init__(self)
52 | self.status = EngineStatus.Unknown
53 |
54 |
55 | class EngineConfiguration(EngineBase):
56 | @staticmethod
57 | def CreateFromXML(xml_data):
58 | config = EngineConfiguration()
59 | config.InitalizeFromXML(xml_data)
60 | config.priority = get_attribute(xml_data, 'priority', config.priority)
61 | config.assigned_sites = [(int(get_attribute(xml_site, 'id', 0)), get_attribute(xml_site, 'name', '')) for xml_site in xml_data.getchildren() if xml_site.tag == 'Site']
62 | return config
63 |
64 | @staticmethod
65 | def Create():
66 | config = EngineConfiguration()
67 | config.id = -1
68 | return config
69 |
70 | @staticmethod
71 | def CreateNamed(name):
72 | config = EngineConfiguration.Create()
73 | config.name = name
74 | return config
75 |
76 | def __init__(self):
77 | EngineBase.__init__(self)
78 | self.priority = EnginePriority.Normal
79 | self.assigned_sites = []
80 |
81 | def AsXML(self, exclude_id):
82 | attributes = {}
83 | if not exclude_id:
84 | attributes['id'] = self.id
85 | attributes['name'] = self.name
86 | attributes['address'] = self.host
87 | attributes['port'] = self.port
88 | attributes['scope'] = self.scope
89 | attributes['priority'] = self.priority
90 | xml_data = create_element('EngineConfig', attributes)
91 | return xml_data
92 |
--------------------------------------------------------------------------------
/nexpose/nexpose_vulnerability.py:
--------------------------------------------------------------------------------
1 | # Future Imports for py2/3 backwards compat.
2 | from __future__ import (absolute_import, division, print_function,
3 | unicode_literals)
4 | from builtins import object
5 | from .xml_utils import get_attribute, get_element, as_string
6 | from future import standard_library
7 | standard_library.install_aliases()
8 |
9 |
10 | class VulnerabilityReference(object):
11 | @staticmethod
12 | def CreateFromXML(xml_data):
13 | reference = VulnerabilityReference()
14 | reference.source = get_attribute(xml_data, 'source', reference.source)
15 | return reference
16 |
17 | def __init__(self):
18 | self.source = ''
19 |
20 |
21 | class VulnerabilityBase(object):
22 | def InitalizeFromXML(self, xml_data):
23 | self.id = get_attribute(xml_data, 'id', self.id)
24 | self.title = get_attribute(xml_data, 'title', self.title)
25 | self.severity = int(get_attribute(xml_data, 'severity', self.severity))
26 | self.pci_severity = int(get_attribute(xml_data, 'pciSeverity', self.pci_severity))
27 | self.cvss_score = float(get_attribute(xml_data, 'cvssScore', self.cvss_score))
28 | self.cvss_vector = get_attribute(xml_data, 'cvssVector', self.cvss_vector)
29 | self.requires_credentials = get_attribute(xml_data, 'requiresCredentials', self.requires_credentials) in ['1', 'true']
30 | self.is_safe = get_attribute(xml_data, 'safe', self.is_safe) in ['1', 'true']
31 | self.published = get_attribute(xml_data, 'published', self.published)
32 | self.added = get_attribute(xml_data, 'added', self.added)
33 | self.modified = get_attribute(xml_data, 'modified', self.modified)
34 |
35 | def __init__(self):
36 | self.id = ''
37 | self.title = ''
38 | self.severity = 0
39 | self.pci_severity = 0
40 | self.cvss_score = 0
41 | self.cvss_vector = ''
42 | self.requires_credentials = False
43 | self.is_safe = False
44 | self.published = ''
45 | self.added = ''
46 | self.modified = ''
47 |
48 |
49 | class VulnerabilitySummary(VulnerabilityBase):
50 | @staticmethod
51 | def CreateFromXML(xml_data):
52 | summary = VulnerabilitySummary()
53 | summary.InitalizeFromXML(xml_data)
54 | return summary
55 |
56 | def __init__(self):
57 | VulnerabilityBase.__init__(self)
58 |
59 |
60 | class VulnerabilityDetail(VulnerabilityBase):
61 | @staticmethod
62 | def CreateFromXML(xml_data):
63 | xml_description = get_element(xml_data, 'description')
64 | xml_solution = get_element(xml_data, 'solution')
65 |
66 | reference_generator = map(lambda xml_reference: VulnerabilityReference.CreateFromXML(xml_reference), xml_data.iterfind('references/reference'))
67 | config = VulnerabilityDetail()
68 | config.InitalizeFromXML(xml_data)
69 | config.description = as_string(xml_description) if xml_description is not None else config.description
70 | config.references = list(reference_generator)
71 | config.solution = as_string(xml_solution) if xml_solution is not None else config.solution
72 | return config
73 |
74 | def __init__(self):
75 | VulnerabilityBase.__init__(self)
76 | self.description = ''
77 | self.references = []
78 | self.solution = ''
79 |
--------------------------------------------------------------------------------
/nexpose/nexpose_report.py:
--------------------------------------------------------------------------------
1 | # Future Imports for py2/3 backwards compat.
2 | from __future__ import (absolute_import, division, print_function,
3 | unicode_literals)
4 | from builtins import object
5 | from .xml_utils import get_attribute
6 | from future import standard_library
7 | standard_library.install_aliases()
8 |
9 |
10 | class ReportStatus(object):
11 | STARTED = 'Started'
12 | GENERATED = 'Generated'
13 | FAILED = 'Failed'
14 | ABORTED = 'Aborted'
15 | UNKNOWN = 'Unknown'
16 |
17 |
18 | class ReportTemplate(object):
19 | pass
20 |
21 |
22 | # TODO: test the difference between global and silo scoped reports
23 | # and refactor accordingly
24 | class _ReportBase(object):
25 | def _InitalizeFromXML(self, xml_data, name_of_id_field):
26 | self.id = int(get_attribute(xml_data, name_of_id_field, self.id))
27 | self.status = get_attribute(xml_data, 'status', self.status)
28 | self.generated_on = get_attribute(xml_data, 'generated-on', self.generated_on) # TODO: parse this as a date
29 | self.URI = get_attribute(xml_data, 'report-URI', self.URI)
30 | self.scope = get_attribute(xml_data, 'scope', self.scope)
31 |
32 | def __init__(self):
33 | self.id = 0
34 | self.status = ReportStatus.UNKNOWN
35 | self.generated_on = '' # TODO: default date?
36 | self.URI = ''
37 | self.scope = 'silo'
38 |
39 |
40 | # TODO: test the difference between global and silo scoped reports
41 | # and refactor accordingly
42 | class _ReportConfigurationBase(object):
43 | def _InitalizeFromXML(self, xml_data):
44 | self.template_id = get_attribute(xml_data, 'template-id', self.template_id)
45 | self.name = get_attribute(xml_data, 'name', self.name)
46 |
47 | def __init__(self):
48 | self.template_id = ''
49 | self.name = ''
50 |
51 |
52 | class ReportSummary(_ReportBase):
53 | @staticmethod
54 | def CreateFromXML(xml_data):
55 | summary = ReportSummary()
56 | _ReportBase._InitalizeFromXML(summary, xml_data, 'id')
57 | summary.configuration_id = int(get_attribute(xml_data, 'cfg-id', summary.configuration_id))
58 | return summary
59 |
60 | def __init__(self):
61 | _ReportBase.__init__(self)
62 | self.configuration_id = 0
63 |
64 |
65 | class ReportConfigurationSummary(_ReportBase, _ReportConfigurationBase):
66 | @staticmethod
67 | def CreateFromXML(xml_data):
68 | config = ReportConfigurationSummary()
69 | _ReportBase._InitalizeFromXML(config, xml_data, 'cfg-id')
70 | _ReportConfigurationBase._InitalizeFromXML(config, xml_data)
71 | return config
72 |
73 | def __init__(self):
74 | _ReportBase.__init__(self)
75 | _ReportConfigurationBase.__init__(self)
76 |
77 |
78 | class ReportConfiguration(_ReportConfigurationBase):
79 | @staticmethod
80 | def CreateFromXML(xml_data):
81 | config = ReportConfiguration()
82 | _ReportConfigurationBase._InitalizeFromXML(config, xml_data)
83 | return config
84 |
85 | def __init__(self):
86 | _ReportConfigurationBase.__init__(self)
87 | self.format = ''
88 | self.owner = ''
89 | self.timezone = ''
90 | self.description = ''
91 | self.filters = []
92 | self.baseline = '' # TODO: default date?
93 | self.users = []
94 | self.generate = None
95 | self.delivery = ''
96 | self.dbexport = ''
97 | self.credentials = ''
98 | self.parameter_name = '' # TODO: ??
99 |
--------------------------------------------------------------------------------
/README.rst:
--------------------------------------------------------------------------------
1 | DEPRECATED
2 | ==========
3 |
4 | The `RESTful API for the Nexpose/InsightVM Security
5 | Console `__ has
6 | rendered this library obsolete. If you require a Python library for that
7 | API you can use a `generated
8 | client `__. Clients
9 | for other languages can be generated from the Swagger specification.
10 | Note that generated clients are not officially supported or maintained
11 | by Rapid7.
12 |
13 | This project will not receive new changes from Rapid7, though pull
14 | requests may still be accepted and new releases published on request.
15 |
16 | nexpose-client-python
17 | =====================
18 |
19 | |No Maintenance Intended| |Travis| |PyPI Version| |PyPI Status| |GitHub
20 | license| |PyPI Pythons|
21 |
22 | This is the official Python package for the Python Nexpose API client
23 | library.
24 |
25 | For assistance with using the library or to discuss different
26 | approaches, please open an issue. To share or discuss scripts which use
27 | the library head over to the `Nexpose
28 | Resources `__ project.
29 |
30 | Check out the
31 | `wiki `__ for
32 | walk-throughs and other documentation. Submit bugs and feature requests
33 | on the
34 | `issues `__
35 | page.
36 |
37 | This library provides calls to the Nexpose XML APIs version 1.1 and 1.2.
38 |
39 | nexpose-client-python uses `Semantic Versioning `__.
40 | This allows for confident use of `version
41 | pinning `__
42 | in your requirements file.
43 |
44 | Install the library using pip: ``pip install nexpose``
45 |
46 | Release Notes
47 | -------------
48 |
49 | Release notes are available on the
50 | `Releases `__
51 | page.
52 |
53 | Contributions
54 | -------------
55 |
56 | We welcome contributions to this package. Please see
57 | `CONTRIBUTING `__ for details.
58 |
59 | Full usage examples or task-oriented scripts should be submitted to the
60 | `Nexpose Resources `__
61 | project. Smaller examples can be added to the
62 | `wiki `__.
63 |
64 | License
65 | -------
66 |
67 | The nexpose-client-python library is provided under the 3-Clause BSD
68 | License. See `LICENSE `__ for details.
69 |
70 | Credits
71 | -------
72 |
73 | | Davinsi Labs
74 | | Rapid7, Inc.
75 |
76 | See `contributors `__ for more info.
77 |
78 | .. |No Maintenance Intended| image:: http://unmaintained.tech/badge.svg
79 | :target: http://unmaintained.tech/
80 | .. |Travis| image:: https://img.shields.io/travis/rapid7/nexpose-client-python.svg
81 | :target: https://travis-ci.org/rapid7/nexpose-client-python
82 | .. |PyPI Version| image:: https://img.shields.io/pypi/v/nexpose.svg
83 | :target: https://pypi.python.org/pypi/nexpose
84 | .. |PyPI Status| image:: https://img.shields.io/pypi/status/nexpose.svg
85 | .. |GitHub license| image:: https://img.shields.io/badge/license-BSD-blue.svg
86 | :target: https://raw.githubusercontent.com/rapid7/nexpose-client-python/master/LICENSE
87 | .. |PyPI Pythons| image:: https://img.shields.io/pypi/pyversions/nexpose.svg
88 |
--------------------------------------------------------------------------------
/nexpose/nexpose_discoveryconnection.py:
--------------------------------------------------------------------------------
1 | # Future Imports for py2/3 backwards compat.
2 | from __future__ import (absolute_import, division, print_function, unicode_literals)
3 | from builtins import object
4 | from future import standard_library
5 | standard_library.install_aliases()
6 | from .xml_utils import get_attribute, create_element
7 | from urllib.parse import urlparse
8 |
9 |
10 | class DiscoveryConnectionProtocol(object):
11 | HTTP = 'http'
12 | HTTPS = 'https'
13 |
14 |
15 | class _DiscoveryConnectionBase(object):
16 | def InitalizeFromXML(self, xml_data):
17 | self.id = int(get_attribute(xml_data, 'id', self.id))
18 | self.name = get_attribute(xml_data, 'name', self.name)
19 | self.host = get_attribute(xml_data, 'address', self.host)
20 | self.port = int(get_attribute(xml_data, 'port', self.port))
21 | self.protocol = get_attribute(xml_data, 'protocol', self.protocol).lower()
22 | self.username = get_attribute(xml_data, 'user-name', self.username)
23 | self.password = get_attribute(xml_data, 'password', self.password)
24 | # TODO: according to the manual a : is added, I doubt that, untested yet
25 | if self.protocol.endswith(':'):
26 | self.protocol = self.protocol[:-1]
27 |
28 | def __init__(self):
29 | self.id = 0
30 | self.name = ''
31 | self.host = ''
32 | self.port = 0
33 | self.protocol = ''
34 | self.username = ''
35 | self.password = ''
36 |
37 |
38 | class DiscoveryConnectionSummary(_DiscoveryConnectionBase):
39 | @staticmethod
40 | def CreateFromXML(xml_data):
41 | summary = DiscoveryConnectionSummary()
42 | summary.InitalizeFromXML(xml_data)
43 | return summary
44 |
45 | def __init__(self):
46 | _DiscoveryConnectionBase.__init__(self)
47 |
48 |
49 | class DiscoveryConnectionConfiguration(DiscoveryConnectionSummary):
50 | @staticmethod
51 | def CreateFromXML(xml_data):
52 | config = DiscoveryConnectionConfiguration()
53 | config.InitalizeFromXML(xml_data)
54 | return config
55 |
56 | @staticmethod
57 | def Create():
58 | config = DiscoveryConnectionConfiguration()
59 | config.id = -1
60 | return config
61 |
62 | @staticmethod
63 | def CreateNamed(name):
64 | config = DiscoveryConnectionConfiguration.Create()
65 | config.name = name
66 | return config
67 |
68 | @staticmethod
69 | def CreateNamedFromURL(name, url, username=None, password=None):
70 | parsed_url = urlparse(url)
71 | host, _, port = parsed_url.netloc.rpartition(':')
72 | if host == '':
73 | host = port
74 | port = '80' if parsed_url.scheme == 'http' else '443'
75 | config = DiscoveryConnectionConfiguration.CreateNamed(name)
76 | config.protocol = parsed_url.scheme.upper()
77 | config.host = host
78 | config.port = port
79 | config.username = '' if username is None else username
80 | config.password = '' if password is None else password
81 | return config
82 |
83 | def __init__(self):
84 | _DiscoveryConnectionBase.__init__(self)
85 |
86 | def AsXML(self, exclude_id):
87 | attributes = {}
88 | if not exclude_id:
89 | attributes['id'] = self.id
90 | attributes['name'] = self.name
91 | attributes['address'] = self.host
92 | attributes['port'] = self.port
93 | attributes['protocol'] = self.protocol
94 | attributes['user-name'] = self.username
95 | attributes['password'] = self.password
96 | xml_data = create_element('DiscoveryConnection', attributes)
97 | return xml_data
98 |
--------------------------------------------------------------------------------
/nexpose/nexpose_vulnerabilityexception.py:
--------------------------------------------------------------------------------
1 | # Future Imports for py2/3 backwards compat.
2 | from __future__ import (absolute_import, division, print_function,
3 | unicode_literals)
4 | from builtins import object
5 | from .xml_utils import get_attribute, get_content_of
6 | from future import standard_library
7 | standard_library.install_aliases()
8 |
9 |
10 | def fix_null(data):
11 | if data == 'null':
12 | return 0
13 | return data
14 |
15 |
16 | class VulnerabilityExceptionStatus(object):
17 | UNDER_REVIEW = "Under Review"
18 | APPROVED = "Approved"
19 | REJECTED = "Rejected"
20 | DELETED = "Deleted" # This state is also used for recalled exceptions!
21 |
22 |
23 | class VulnerabilityExceptionReason(object):
24 | FALSE_POSITIVE = "False Positive"
25 | COMPENSATING_CONTROL = "Compensating Control"
26 | ACCEPTABLE_USE = "Acceptable Use"
27 | ACCEPTABLE_RISK = "Acceptable Risk"
28 | OTHER = "Other"
29 |
30 |
31 | class VulnerabilityExceptionScope(object):
32 | ALL_INSTANCES = "All Instances"
33 | ALL_INSTANCES_SPECIFIC_ASSET = "All Instances on a Specific Asset"
34 | ALL_INSTANCES_SPECIFIC_SITE = "All Instances on a Specific Site"
35 | SPECIFIC_INSTANCE_SPECIFIC_ASSET = "Specific Instance of Specific Asset"
36 |
37 |
38 | class SiloVulnerabilityExceptionDetails(object):
39 | @staticmethod
40 | def CreateFromXML(xml_data):
41 | details = SiloVulnerabilityExceptionDetails()
42 | details.silo_id = get_attribute(xml_data, 'siloId', details.silo_id)
43 | details.oldest_exception_creation_date = get_attribute(xml_data, 'oldestExceptionCreationDate', details.oldest_exception_creation_date) # TODO: date object
44 | details.pending_exception_count = get_attribute(xml_data, 'pendingVulnExceptionsCount', details.pending_exception_count)
45 | return details
46 |
47 | def __init__(self):
48 | self.silo_id = ''
49 | self.oldest_exception_creation_date = 'N/A' # TODO: date object
50 | self.pending_exception_count = 0
51 |
52 |
53 | class VulnerabilityException(object):
54 | @staticmethod
55 | def CreateFromXML(xml_data):
56 | details = VulnerabilityException()
57 | details.id = int(get_attribute(xml_data, 'exception-id', details.id))
58 | details.vulnerability_id = get_attribute(xml_data, 'vuln-id', details.vulnerability_id)
59 | details.vulnerability_key = get_attribute(xml_data, 'vuln-key', details.vulnerability_key)
60 | details.expiration_date = get_attribute(xml_data, 'expiration-date', details.expiration_date) # TODO: date object
61 | details.submitter = get_attribute(xml_data, 'submitter', details.submitter)
62 | details.submitter_comment = get_content_of(xml_data, 'submitter-comment', details.submitter_comment)
63 | details.reviewer = get_attribute(xml_data, 'reviewer', details.reviewer)
64 | details.reviewer_comment = get_content_of(xml_data, 'reviewer-comment', details.reviewer_comment)
65 | details.status = get_attribute(xml_data, 'status', details.status)
66 | details.reason = get_attribute(xml_data, 'reason', details.reason)
67 | details.scope = get_attribute(xml_data, 'scope', details.scope)
68 | details.asset_id = int(fix_null(get_attribute(xml_data, 'device-id', details.asset_id)))
69 | details.asset_port = int(fix_null(get_attribute(xml_data, 'port-no', details.asset_port)))
70 | return details
71 |
72 | def __init__(self):
73 | self.id = 0
74 | self.vulnerability_id = ''
75 | self.vulnerability_key = ''
76 | self.expiration_date = '' # TODO: date object
77 | self.submitter = ''
78 | self.submitter_comment = ''
79 | self.reviewer = ''
80 | self.reviewer_comment = ''
81 | self.status = ''
82 | self.reason = ''
83 | self.scope = ''
84 | self.asset_id = 0
85 | self.asset_port = 0
86 |
--------------------------------------------------------------------------------
/.github/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing to nexpose-client-python
2 |
3 | The users and maintainers of nexpose-client-python would greatly appreciate any contributions
4 | you can make to the project. These contributions typically come in the form of
5 | filed bugs/issues or pull requests (PRs). These contributions routinely result
6 | in new versions of the [nexpose-client-python library](https://pypi.python.org/pypi/nexpose) and the
7 | [nexpose-client-python release](https://github.com/rapid7/nexpose-client-python/releases) to be released. The
8 | process for each is outlined below.
9 |
10 | ## Contributing Issues / Bug Reports
11 |
12 | If you encounter any bugs or problems with nexpose-client-python, please file them
13 | [here](https://github.com/rapid7/nexpose-client-python/issues/new), providing as much detail as
14 | possible. If the bug is straight-forward enough and you understand the fix for
15 | the bug well enough, you may take the simpler, less-paperwork route and simply
16 | file a PR with the fix and the necessary details.
17 |
18 | ## Contributing Code
19 |
20 | nexpose-client-python uses a model nearly identical to that of
21 | [Metasploit](https://github.com/rapid7/metasploit-framework) as outlined
22 | [here](https://github.com/rapid7/metasploit-framework/wiki/Setting-Up-a-Metasploit-Development-Environment),
23 | at least from a ```git``` perspective. If you've been through that process
24 | (or, even better, you've been through it many times with many people), you can
25 | do exactly what you did for Metasploit but with nexpose-client-python and ignore the rest of
26 | this document.
27 |
28 | On the other hand, if you haven't, read on!
29 |
30 | ### Fork and Clone
31 |
32 | Generally, this should only need to be done once, or if you need to start over.
33 |
34 | 1. Fork nexpose-client: Visit https://github.com/rapid7/nexpose-client-python and click Fork,
35 | selecting your github account if prompted
36 | 2. Clone ```git@github.com:/nexpose-client-python.git```, replacing
37 | `````` with, you guessed it, your Github username.
38 | 3. Add the master nexpose-client-python repository as your upstream:
39 | ```
40 | git remote add upstream git://github.com/rapid7/nexpose-client-python.git
41 | git fetch --all
42 | ```
43 |
44 | ### Branch and Improve
45 |
46 | If you have a contribution to make, first create a branch to contain your
47 | work. The name is yours to choose, however generally it should roughly
48 | describe what you are doing. In this example, and from here on out, the
49 | branch will be wow, but you should change this.
50 |
51 | ```
52 | git fetch --all
53 | git checkout master
54 | git rebase upstream/master
55 | git checkout -b wow
56 | ```
57 |
58 | Now, make your changes, committing as necessary, using useful commit messages:
59 |
60 | ```
61 | vim CONTRIBUTING.md
62 | git add CONTRIBUTING.md
63 | git commit -m "Adds a document on how to contribute to nexpose-client-python." -a
64 | ```
65 |
66 | Please note that changes to [version.py](version.py) in PRs are almost never necessary.
67 |
68 | Now push your changes to your fork:
69 |
70 | ```
71 | git push origin wow
72 | ```
73 |
74 | Finally, submit the PR. Navigate to ```https://github.com//nexpose-client-python/compare/wow```, fill in the details, and submit.
75 |
76 | ## Releasing New Versions
77 |
78 | Typically this process is reserved for contributors with push permissions to
79 | nexpose-client-python:
80 |
81 | Be sure to regenerate the README.rst file if the README.md has changed. Use `pandoc -s -r markdown -w rst README.md -o README.rst` and validate the link URLs.
82 |
83 | ### Pypi Release
84 |
85 | Pypi releases, for use with the `pip` command, are performed by a Jenkins job. Currently Jenkins access is restricted to Rapid7 employees. The package will be published at [https://pypi.python.org/pypi/nexpose](https://pypi.python.org/pypi/nexpose).
86 |
87 | ### Github Release
88 |
89 | Some users may prefer to consume nexpose-client-python in a manner other than using git itself. For that reason, Github offers [Releases](https://github.com/blog/1547-release-your-software). Whenever a new version of the software is to be released, be kind and also create a new [Release](https://github.com/rapid7/nexpose-client-python/releases), using a versioning scheme identical to that used for the library.
90 |
--------------------------------------------------------------------------------
/tests/test_NexposeReportConfigurationSummary.py:
--------------------------------------------------------------------------------
1 | # Future Imports for py2/3 backwards compat.
2 | from __future__ import (absolute_import, division, print_function,
3 | unicode_literals)
4 | from .load_unittest import unittest
5 | from .LoadFixture import CreateEmptyFixture, LoadFixture, XML, JSON
6 | from nexpose.nexpose_report import ReportStatus, ReportConfigurationSummary, ReportSummary
7 | from future import standard_library
8 | standard_library.install_aliases()
9 |
10 |
11 | class NexposeReportConfigurationSummaryTestCase(unittest.TestCase):
12 | def testCreateFromXML(self):
13 | fixture = CreateEmptyFixture(XML)
14 | report_cfg = ReportConfigurationSummary.CreateFromXML(fixture)
15 | self.assertEquals(0, report_cfg.id)
16 | self.assertEquals('', report_cfg.template_id)
17 | self.assertEquals('', report_cfg.name)
18 | self.assertEquals(ReportStatus.UNKNOWN, report_cfg.status)
19 | self.assertEquals('', report_cfg.generated_on)
20 | self.assertEquals('', report_cfg.URI)
21 | self.assertEquals('silo', report_cfg.scope)
22 |
23 | fixture = LoadFixture('ReportListingResponse.xml')
24 |
25 | report_cfg = ReportConfigurationSummary.CreateFromXML(fixture[0])
26 | self.assertEquals(2, report_cfg.id)
27 | self.assertEquals('audit-report', report_cfg.template_id)
28 | self.assertEquals('Report 2.0 - Complete Site', report_cfg.name)
29 | self.assertEquals(ReportStatus.GENERATED, report_cfg.status)
30 | self.assertEquals('20160303T122452808', report_cfg.generated_on)
31 | self.assertEquals('/reports/00000002/00000002/report.xml', report_cfg.URI)
32 | self.assertEquals('silo', report_cfg.scope)
33 |
34 | report_cfg = ReportConfigurationSummary.CreateFromXML(fixture[1])
35 | self.assertEquals(3, report_cfg.id)
36 | self.assertEquals('audit-report', report_cfg.template_id)
37 | self.assertEquals('Report 2.0 - Including non-vuln', report_cfg.name)
38 | self.assertEquals(ReportStatus.GENERATED, report_cfg.status)
39 | self.assertEquals('20160303T122922250', report_cfg.generated_on)
40 | self.assertEquals('/reports/00000003/00000003/report.xml', report_cfg.URI)
41 | self.assertEquals('silo', report_cfg.scope)
42 |
43 | report_cfg = ReportConfigurationSummary.CreateFromXML(fixture[2])
44 | self.assertEquals(1, report_cfg.id)
45 | self.assertEquals('audit-report', report_cfg.template_id)
46 | self.assertEquals('XML 2.0 export', report_cfg.name)
47 | self.assertEquals(ReportStatus.GENERATED, report_cfg.status)
48 | self.assertEquals('20160219T062407874', report_cfg.generated_on)
49 | self.assertEquals('/reports/00000001/00000001/report.xml', report_cfg.URI)
50 | self.assertEquals('global', report_cfg.scope)
51 |
52 |
53 | class NexposeReportSummaryTestCase(unittest.TestCase):
54 | def testCreateFromXML(self):
55 | fixture = CreateEmptyFixture(XML)
56 | report_summary = ReportSummary.CreateFromXML(fixture)
57 | self.assertEquals(0, report_summary.id)
58 | self.assertEquals(ReportStatus.UNKNOWN, report_summary.status)
59 | self.assertEquals('', report_summary.generated_on)
60 | self.assertEquals('', report_summary.URI)
61 | self.assertEquals('silo', report_summary.scope)
62 |
63 | fixture = LoadFixture('ReportHistoryResponse.xml')
64 |
65 | report_summary = ReportSummary.CreateFromXML(fixture[0])
66 | self.assertEquals(6, report_summary.id)
67 | self.assertEquals(2, report_summary.configuration_id)
68 | self.assertEquals(ReportStatus.GENERATED, report_summary.status)
69 | self.assertEquals('20160303T161938459', report_summary.generated_on)
70 | self.assertEquals('/reports/00000002/00000006/report.xml', report_summary.URI)
71 | self.assertEquals('silo', report_summary.scope)
72 |
73 | report_summary = ReportSummary.CreateFromXML(fixture[1])
74 | self.assertEquals(2, report_summary.id)
75 | self.assertEquals(2, report_summary.configuration_id)
76 | self.assertEquals(ReportStatus.GENERATED, report_summary.status)
77 | self.assertEquals('20160303T122452808', report_summary.generated_on)
78 | self.assertEquals('/reports/00000002/00000002/report.xml', report_summary.URI)
79 | self.assertEquals('silo', report_summary.scope)
80 |
--------------------------------------------------------------------------------
/tests/test_NexposeSession.py:
--------------------------------------------------------------------------------
1 | # Future Imports for py2/3 backwards compat.
2 | from __future__ import (absolute_import, division, print_function,
3 | unicode_literals)
4 | from .load_unittest import unittest
5 | from nexpose.nexpose import NexposeFailureException, NexposeException, SessionIsNotClosedException
6 | from nexpose.xml_utils import as_string, as_xml
7 | from .NexposeSessionSpy import NexposeSessionSpy, SpyFactory
8 | from future import standard_library
9 | standard_library.install_aliases()
10 |
11 | FAKE_SESSIONID = "B33R"
12 | FAKE_SITEID = 201
13 |
14 | # hackish
15 | SpyFactory_CreateOpenSession = SpyFactory.CreateOpenSession
16 | SpyFactory.CreateOpenSession = staticmethod(lambda: SpyFactory_CreateOpenSession(FAKE_SESSIONID))
17 |
18 |
19 | class NexposeSessionTestCase(unittest.TestCase):
20 | def assertEqualXml(self, xml_object, xml_string):
21 | self.assertEqual(as_string(xml_object), as_string(as_xml(xml_string)))
22 |
23 | def testDefaultConstructionOfURI_APIv1d1(self):
24 | expected_uri = "https://localhost:3780/api/1.1/xml"
25 | session = SpyFactory.CreateEmpty()
26 | self.assertEqual(session.GetURI_APIv1d1(), expected_uri)
27 |
28 | def testConstructionOfURI_APIv1d1(self):
29 | expected_uri = "https://nexpose.davansilabs.local:666/api/1.1/xml"
30 | session = SpyFactory.CreateWithDefaultLogin("nexpose.davansilabs.local", 666)
31 | self.assertEqual(session.GetURI_APIv1d1(), expected_uri)
32 |
33 | def testConstructionOfLoginRequest(self):
34 | expected_request = [b'', b'']
35 | session = SpyFactory.CreateWithDefaultLogin("server")
36 | self.assertIn(as_string(session.GetLoginRequest()), expected_request)
37 |
38 | def testCorrectLogin(self):
39 | session = SpyFactory.CreateWithDefaultLogin('*')
40 | session.XmlStringToReturnOnExecute = ''.format(FAKE_SESSIONID)
41 | session.Open()
42 | self.assertEqual(session.GetSessionID(), FAKE_SESSIONID)
43 |
44 | def testIncorrectLogin(self):
45 | session = SpyFactory.CreateWithDefaultLogin('*')
46 | session.XmlStringToReturnOnExecute = ''.format(FAKE_SESSIONID)
47 | self.assertEqual(session.GetSessionID(), None)
48 | self.assertRaises(NexposeFailureException, session.Open)
49 | self.assertNotEqual(session.GetSessionID(), FAKE_SESSIONID)
50 |
51 | def testLoginWithInvalidHtmlReply(self):
52 | session = SpyFactory.CreateWithDefaultLogin('*')
53 | session.XmlStringToReturnOnExecute = '