├── .flake8 ├── .github ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE.md └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── MANIFEST.in ├── README.md ├── README.rst ├── contributors.md ├── demo ├── demo.cfg.default ├── run_demo.py └── sslfix.py ├── docs ├── Makefile ├── conf.py ├── index.rst ├── make.bat ├── modules.rst └── nexpose.rst ├── nexpose ├── __init__.py ├── json_utils.py ├── nexpose.py ├── nexpose_asset.py ├── nexpose_assetfilter.py ├── nexpose_assetgroup.py ├── nexpose_backup.py ├── nexpose_credential.py ├── nexpose_criteria.py ├── nexpose_criteria_constants.py ├── nexpose_criteria_fields.py ├── nexpose_criteria_operators.py ├── nexpose_discoveryconnection.py ├── nexpose_engine.py ├── nexpose_enginepool.py ├── nexpose_node.py ├── nexpose_privileges.py ├── nexpose_report.py ├── nexpose_role.py ├── nexpose_scansummary.py ├── nexpose_sharedcredential.py ├── nexpose_site.py ├── nexpose_status.py ├── nexpose_tag.py ├── nexpose_ticket.py ├── nexpose_user.py ├── nexpose_userauthenticator.py ├── nexpose_vulnerability.py ├── nexpose_vulnerabilityexception.py ├── python_utils.py └── xml_utils.py ├── requirements.txt ├── requirements_dev.txt ├── requirements_test.txt ├── setup.cfg ├── setup.py ├── test_fixtures ├── ReportConfigResponse_Samples.xml ├── ReportHistoryResponse.xml ├── ReportListingResponse.xml ├── UserAuthenticatorListingResponse.xml ├── custom_tag_example.json ├── data-scan_complete-assets_Nexpose6.json ├── default_tags.json ├── report (2.0).xml └── xml_fixtures.py ├── tests ├── LoadFixture.py ├── NexposeSessionSpy.py ├── __init__.py ├── load_unittest.py ├── test_LoadFixture.py ├── test_NexposeNode.py ├── test_NexposeReportConfigurationSummary.py ├── test_NexposeSession.py ├── test_NexposeTag.py ├── test_NexposeUserAuthenticatorSummary.py └── test_NexposeVulnerability.py └── utilities └── create_credential_code.py /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## [0.1.7](https://github.com/rapid7/nexpose-client-python/tree/0.1.7) (2017-11-20) 4 | [Full Changelog](https://github.com/rapid7/nexpose-client-python/compare/0.1.6...0.1.7) 5 | 6 | **Merged pull requests:** 7 | 8 | - Improvements for AssetDetails, Tags & Unique Identifiers + Adhoc Reports [\#43](https://github.com/rapid7/nexpose-client-python/pull/43) ([fruechel](https://github.com/fruechel)) 9 | - Made the demo more safe [\#42](https://github.com/rapid7/nexpose-client-python/pull/42) ([NVolcz](https://github.com/NVolcz)) 10 | 11 | ## [0.1.6](https://github.com/rapid7/nexpose-client-python/tree/0.1.6) (2017-10-17) 12 | [Full Changelog](https://github.com/rapid7/nexpose-client-python/compare/0.1.5...0.1.6) 13 | 14 | **Fixed bugs:** 15 | 16 | - ImportError: No module named request [\#40](https://github.com/rapid7/nexpose-client-python/issues/40) 17 | - Fix things that stopped working after py3 updates [\#32](https://github.com/rapid7/nexpose-client-python/issues/32) 18 | - Bug: DemonstrateCriteriaAPI\(\) + DemonstrateAssetFilterAPI\(\) [\#36](https://github.com/rapid7/nexpose-client-python/pull/36) ([grobinson-r7](https://github.com/grobinson-r7)) 19 | - Bug: DemonstrateBackupAPI\(\) + DemonstrateSharedCredentialsAPI\(\) [\#35](https://github.com/rapid7/nexpose-client-python/pull/35) ([grobinson-r7](https://github.com/grobinson-r7)) 20 | - Bug: DemonstrateUserAPI\(\) [\#34](https://github.com/rapid7/nexpose-client-python/pull/34) ([grobinson-r7](https://github.com/grobinson-r7)) 21 | - Bug: DemonstrateVulnerabilityAPI\(\) [\#31](https://github.com/rapid7/nexpose-client-python/pull/31) ([grobinson-r7](https://github.com/grobinson-r7)) 22 | 23 | **Merged pull requests:** 24 | 25 | - Fix urllib imports by installing futures aliases earlier [\#41](https://github.com/rapid7/nexpose-client-python/pull/41) ([gschneider-r7](https://github.com/gschneider-r7)) 26 | 27 | ## [0.1.5](https://github.com/rapid7/nexpose-client-python/tree/0.1.5) (2017-10-02) 28 | [Full Changelog](https://github.com/rapid7/nexpose-client-python/compare/0.1.4...0.1.5) 29 | 30 | **Fixed bugs:** 31 | 32 | - module 'nexpose' has no attribute 'NexposeSession' in version 0.1.3 [\#23](https://github.com/rapid7/nexpose-client-python/issues/23) 33 | - Fix py2/py3 compatibility due to relative import issues [\#24](https://github.com/rapid7/nexpose-client-python/pull/24) ([gschneider-r7](https://github.com/gschneider-r7)) 34 | 35 | ## [0.1.4](https://github.com/rapid7/nexpose-client-python/tree/0.1.4) (2017-08-25) 36 | [Full Changelog](https://github.com/rapid7/nexpose-client-python/compare/0.1.3...0.1.4) 37 | 38 | ## [0.1.3](https://github.com/rapid7/nexpose-client-python/tree/0.1.3) (2017-08-21) 39 | [Full Changelog](https://github.com/rapid7/nexpose-client-python/compare/0.1.2...0.1.3) 40 | 41 | **Implemented enhancements:** 42 | 43 | - Add Python 3 compatibility [\#4](https://github.com/rapid7/nexpose-client-python/issues/4) 44 | 45 | **Merged pull requests:** 46 | 47 | - Python 3/2 backwards compatibility [\#18](https://github.com/rapid7/nexpose-client-python/pull/18) ([dhaynespls](https://github.com/dhaynespls)) 48 | 49 | ## [0.1.2](https://github.com/rapid7/nexpose-client-python/tree/0.1.2) (2017-08-10) 50 | [Full Changelog](https://github.com/rapid7/nexpose-client-python/compare/0.1.1...0.1.2) 51 | 52 | **Merged pull requests:** 53 | 54 | - Maintain site info when saving [\#20](https://github.com/rapid7/nexpose-client-python/pull/20) ([derpadoo](https://github.com/derpadoo)) 55 | - Making python files more PEP8 compliant [\#16](https://github.com/rapid7/nexpose-client-python/pull/16) ([derpadoo](https://github.com/derpadoo)) 56 | 57 | ## [0.1.1](https://github.com/rapid7/nexpose-client-python/tree/0.1.1) (2017-07-20) 58 | [Full Changelog](https://github.com/rapid7/nexpose-client-python/compare/0.1.0...0.1.1) 59 | 60 | **Fixed bugs:** 61 | 62 | - Creating a new site fails with "NexposeFailureException: templateID must be specified." [\#6](https://github.com/rapid7/nexpose-client-python/issues/6) 63 | - GetFilteredAssets fails with "HTTP Error 500: Internal Server Error" [\#5](https://github.com/rapid7/nexpose-client-python/issues/5) 64 | 65 | **Closed issues:** 66 | 67 | - Typo in RequestSiteConfig Docstring [\#7](https://github.com/rapid7/nexpose-client-python/issues/7) 68 | 69 | **Merged pull requests:** 70 | 71 | - Fix for GetFilteredAssets internal server error [\#10](https://github.com/rapid7/nexpose-client-python/pull/10) ([gschneider-r7](https://github.com/gschneider-r7)) 72 | - Include ScanConfig attributes with defaults [\#9](https://github.com/rapid7/nexpose-client-python/pull/9) ([scottjpack](https://github.com/scottjpack)) 73 | 74 | ## [0.1.0](https://github.com/rapid7/nexpose-client-python/tree/0.1.0) (2017-06-19) 75 | 76 | 77 | \* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)* -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.rst LICENSE 2 | -------------------------------------------------------------------------------- /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 | [![No Maintenance Intended](http://unmaintained.tech/badge.svg)](http://unmaintained.tech/) [![Travis](https://img.shields.io/travis/rapid7/nexpose-client-python.svg)](https://travis-ci.org/rapid7/nexpose-client-python) [![PyPI Version](https://img.shields.io/pypi/v/nexpose.svg)](https://pypi.python.org/pypi/nexpose) ![PyPI Status](https://img.shields.io/pypi/status/nexpose.svg) [![GitHub license](https://img.shields.io/badge/license-BSD-blue.svg)](https://raw.githubusercontent.com/rapid7/nexpose-client-python/master/LICENSE) ![PyPI Pythons](https://img.shields.io/pypi/pyversions/nexpose.svg) 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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # Internal variables. 11 | PAPEROPT_a4 = -D latex_paper_size=a4 12 | PAPEROPT_letter = -D latex_paper_size=letter 13 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 14 | # the i18n builder cannot share the environment and doctrees with the others 15 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 16 | 17 | .PHONY: help 18 | help: 19 | @echo "Please use \`make ' where is one of" 20 | @echo " html to make standalone HTML files" 21 | @echo " dirhtml to make HTML files named index.html in directories" 22 | @echo " singlehtml to make a single large HTML file" 23 | @echo " pickle to make pickle files" 24 | @echo " json to make JSON files" 25 | @echo " htmlhelp to make HTML files and a HTML help project" 26 | @echo " qthelp to make HTML files and a qthelp project" 27 | @echo " applehelp to make an Apple Help Book" 28 | @echo " devhelp to make HTML files and a Devhelp project" 29 | @echo " epub to make an epub" 30 | @echo " epub3 to make an epub3" 31 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 32 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 33 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 34 | @echo " text to make text files" 35 | @echo " man to make manual pages" 36 | @echo " texinfo to make Texinfo files" 37 | @echo " info to make Texinfo files and run them through makeinfo" 38 | @echo " gettext to make PO message catalogs" 39 | @echo " changes to make an overview of all changed/added/deprecated items" 40 | @echo " xml to make Docutils-native XML files" 41 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 42 | @echo " linkcheck to check all external links for integrity" 43 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 44 | @echo " coverage to run coverage check of the documentation (if enabled)" 45 | @echo " dummy to check syntax errors of document sources" 46 | 47 | .PHONY: clean 48 | clean: 49 | rm -rf $(BUILDDIR)/* 50 | 51 | .PHONY: html 52 | html: 53 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 54 | @echo 55 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 56 | 57 | .PHONY: dirhtml 58 | dirhtml: 59 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 60 | @echo 61 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 62 | 63 | .PHONY: singlehtml 64 | singlehtml: 65 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 66 | @echo 67 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 68 | 69 | .PHONY: pickle 70 | pickle: 71 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 72 | @echo 73 | @echo "Build finished; now you can process the pickle files." 74 | 75 | .PHONY: json 76 | json: 77 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 78 | @echo 79 | @echo "Build finished; now you can process the JSON files." 80 | 81 | .PHONY: htmlhelp 82 | htmlhelp: 83 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 84 | @echo 85 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 86 | ".hhp project file in $(BUILDDIR)/htmlhelp." 87 | 88 | .PHONY: qthelp 89 | qthelp: 90 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 91 | @echo 92 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 93 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 94 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/nexpose-client-python.qhcp" 95 | @echo "To view the help file:" 96 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/nexpose-client-python.qhc" 97 | 98 | .PHONY: applehelp 99 | applehelp: 100 | $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp 101 | @echo 102 | @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." 103 | @echo "N.B. You won't be able to view it unless you put it in" \ 104 | "~/Library/Documentation/Help or install it in your application" \ 105 | "bundle." 106 | 107 | .PHONY: devhelp 108 | devhelp: 109 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 110 | @echo 111 | @echo "Build finished." 112 | @echo "To view the help file:" 113 | @echo "# mkdir -p $$HOME/.local/share/devhelp/nexpose-client-python" 114 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/nexpose-client-python" 115 | @echo "# devhelp" 116 | 117 | .PHONY: epub 118 | epub: 119 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 120 | @echo 121 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 122 | 123 | .PHONY: epub3 124 | epub3: 125 | $(SPHINXBUILD) -b epub3 $(ALLSPHINXOPTS) $(BUILDDIR)/epub3 126 | @echo 127 | @echo "Build finished. The epub3 file is in $(BUILDDIR)/epub3." 128 | 129 | .PHONY: latex 130 | latex: 131 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 132 | @echo 133 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 134 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 135 | "(use \`make latexpdf' here to do that automatically)." 136 | 137 | .PHONY: latexpdf 138 | latexpdf: 139 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 140 | @echo "Running LaTeX files through pdflatex..." 141 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 142 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 143 | 144 | .PHONY: latexpdfja 145 | latexpdfja: 146 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 147 | @echo "Running LaTeX files through platex and dvipdfmx..." 148 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 149 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 150 | 151 | .PHONY: text 152 | text: 153 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 154 | @echo 155 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 156 | 157 | .PHONY: man 158 | man: 159 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 160 | @echo 161 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 162 | 163 | .PHONY: texinfo 164 | texinfo: 165 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 166 | @echo 167 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 168 | @echo "Run \`make' in that directory to run these through makeinfo" \ 169 | "(use \`make info' here to do that automatically)." 170 | 171 | .PHONY: info 172 | info: 173 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 174 | @echo "Running Texinfo files through makeinfo..." 175 | make -C $(BUILDDIR)/texinfo info 176 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 177 | 178 | .PHONY: gettext 179 | gettext: 180 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 181 | @echo 182 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 183 | 184 | .PHONY: changes 185 | changes: 186 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 187 | @echo 188 | @echo "The overview file is in $(BUILDDIR)/changes." 189 | 190 | .PHONY: linkcheck 191 | linkcheck: 192 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 193 | @echo 194 | @echo "Link check complete; look for any errors in the above output " \ 195 | "or in $(BUILDDIR)/linkcheck/output.txt." 196 | 197 | .PHONY: doctest 198 | doctest: 199 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 200 | @echo "Testing of doctests in the sources finished, look at the " \ 201 | "results in $(BUILDDIR)/doctest/output.txt." 202 | 203 | .PHONY: coverage 204 | coverage: 205 | $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage 206 | @echo "Testing of coverage in the sources finished, look at the " \ 207 | "results in $(BUILDDIR)/coverage/python.txt." 208 | 209 | .PHONY: xml 210 | xml: 211 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 212 | @echo 213 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 214 | 215 | .PHONY: pseudoxml 216 | pseudoxml: 217 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 218 | @echo 219 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 220 | 221 | .PHONY: dummy 222 | dummy: 223 | $(SPHINXBUILD) -b dummy $(ALLSPHINXOPTS) $(BUILDDIR)/dummy 224 | @echo 225 | @echo "Build finished. Dummy builder generates no files." 226 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # nexpose-client-python documentation build configuration file, created by 4 | # sphinx-quickstart on Fri Jul 22 22:46:51 2016. 5 | # 6 | # This file is execfile()d with the current directory set to its 7 | # containing dir. 8 | # 9 | # Note that not all possible configuration values are present in this 10 | # autogenerated file. 11 | # 12 | # All configuration values have a default; values that are commented out 13 | # serve to show the default. 14 | 15 | # If extensions (or modules to document with autodoc) are in another directory, 16 | # add these directories to sys.path here. If the directory is relative to the 17 | # documentation root, use os.path.abspath to make it absolute, like shown here. 18 | # 19 | # Future Imports for py2/3 backwards compat. 20 | from __future__ import (absolute_import, division, print_function, 21 | unicode_literals) 22 | 23 | import os 24 | import sys 25 | from future import standard_library 26 | standard_library.install_aliases() 27 | 28 | sys.path.insert(0, os.path.abspath('../nexpose')) 29 | 30 | # -- General configuration ------------------------------------------------ 31 | 32 | # If your documentation needs a minimal Sphinx version, state it here. 33 | # 34 | # needs_sphinx = '1.0' 35 | 36 | # Add any Sphinx extension module names here, as strings. They can be 37 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 38 | # ones. 39 | extensions = [ 40 | 'sphinx.ext.autodoc', 41 | 'sphinx.ext.todo', 42 | 'sphinx.ext.coverage', 43 | 'sphinx.ext.viewcode', 44 | ] 45 | 46 | # Add any paths that contain templates here, relative to this directory. 47 | templates_path = ['_templates'] 48 | 49 | # The suffix(es) of source filenames. 50 | # You can specify multiple suffix as a list of string: 51 | # 52 | # source_suffix = ['.rst', '.md'] 53 | source_suffix = '.rst' 54 | 55 | # The encoding of source files. 56 | # 57 | # source_encoding = 'utf-8-sig' 58 | 59 | # The master toctree document. 60 | master_doc = 'index' 61 | 62 | # General information about the project. 63 | project = u'nexpose-client-python' 64 | copyright = u'2016, Rapid7' 65 | author = u'Rapid7' 66 | 67 | # The version info for the project you're documenting, acts as replacement for 68 | # |version| and |release|, also used in various other places throughout the 69 | # built documents. 70 | # 71 | # The short X.Y version. 72 | version = u'0.0.1' 73 | # The full version, including alpha/beta/rc tags. 74 | release = u'0.0.1' 75 | 76 | # The language for content autogenerated by Sphinx. Refer to documentation 77 | # for a list of supported languages. 78 | # 79 | # This is also used if you do content translation via gettext catalogs. 80 | # Usually you set "language" from the command line for these cases. 81 | language = None 82 | 83 | # There are two options for replacing |today|: either, you set today to some 84 | # non-false value, then it is used: 85 | # 86 | # today = '' 87 | # 88 | # Else, today_fmt is used as the format for a strftime call. 89 | # 90 | # today_fmt = '%B %d, %Y' 91 | 92 | # List of patterns, relative to source directory, that match files and 93 | # directories to ignore when looking for source files. 94 | # This patterns also effect to html_static_path and html_extra_path 95 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] 96 | 97 | # The reST default role (used for this markup: `text`) to use for all 98 | # documents. 99 | # 100 | # default_role = None 101 | 102 | # If true, '()' will be appended to :func: etc. cross-reference text. 103 | # 104 | # add_function_parentheses = True 105 | 106 | # If true, the current module name will be prepended to all description 107 | # unit titles (such as .. function::). 108 | # 109 | # add_module_names = True 110 | 111 | # If true, sectionauthor and moduleauthor directives will be shown in the 112 | # output. They are ignored by default. 113 | # 114 | # show_authors = False 115 | 116 | # The name of the Pygments (syntax highlighting) style to use. 117 | pygments_style = 'sphinx' 118 | 119 | # A list of ignored prefixes for module index sorting. 120 | # modindex_common_prefix = [] 121 | 122 | # If true, keep warnings as "system message" paragraphs in the built documents. 123 | # keep_warnings = False 124 | 125 | # If true, `todo` and `todoList` produce output, else they produce nothing. 126 | todo_include_todos = True 127 | 128 | 129 | # -- Options for HTML output ---------------------------------------------- 130 | 131 | # The theme to use for HTML and HTML Help pages. See the documentation for 132 | # a list of builtin themes. 133 | # 134 | html_theme = 'alabaster' 135 | 136 | # Theme options are theme-specific and customize the look and feel of a theme 137 | # further. For a list of options available for each theme, see the 138 | # documentation. 139 | # 140 | # html_theme_options = {} 141 | 142 | # Add any paths that contain custom themes here, relative to this directory. 143 | # html_theme_path = [] 144 | 145 | # The name for this set of Sphinx documents. 146 | # " v documentation" by default. 147 | # 148 | # html_title = u'nexpose-client-python v0.0.1' 149 | 150 | # A shorter title for the navigation bar. Default is the same as html_title. 151 | # 152 | # html_short_title = None 153 | 154 | # The name of an image file (relative to this directory) to place at the top 155 | # of the sidebar. 156 | # 157 | # html_logo = None 158 | 159 | # The name of an image file (relative to this directory) to use as a favicon of 160 | # the docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 161 | # pixels large. 162 | # 163 | # html_favicon = None 164 | 165 | # Add any paths that contain custom static files (such as style sheets) here, 166 | # relative to this directory. They are copied after the builtin static files, 167 | # so a file named "default.css" will overwrite the builtin "default.css". 168 | html_static_path = ['_static'] 169 | 170 | # Add any extra paths that contain custom files (such as robots.txt or 171 | # .htaccess) here, relative to this directory. These files are copied 172 | # directly to the root of the documentation. 173 | # 174 | # html_extra_path = [] 175 | 176 | # If not None, a 'Last updated on:' timestamp is inserted at every page 177 | # bottom, using the given strftime format. 178 | # The empty string is equivalent to '%b %d, %Y'. 179 | # 180 | # html_last_updated_fmt = None 181 | 182 | # If true, SmartyPants will be used to convert quotes and dashes to 183 | # typographically correct entities. 184 | # 185 | # html_use_smartypants = True 186 | 187 | # Custom sidebar templates, maps document names to template names. 188 | # 189 | # html_sidebars = {} 190 | 191 | # Additional templates that should be rendered to pages, maps page names to 192 | # template names. 193 | # 194 | # html_additional_pages = {} 195 | 196 | # If false, no module index is generated. 197 | # 198 | # html_domain_indices = True 199 | 200 | # If false, no index is generated. 201 | # 202 | # html_use_index = True 203 | 204 | # If true, the index is split into individual pages for each letter. 205 | # 206 | # html_split_index = False 207 | 208 | # If true, links to the reST sources are added to the pages. 209 | # 210 | # html_show_sourcelink = True 211 | 212 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 213 | # 214 | # html_show_sphinx = True 215 | 216 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 217 | # 218 | # html_show_copyright = True 219 | 220 | # If true, an OpenSearch description file will be output, and all pages will 221 | # contain a tag referring to it. The value of this option must be the 222 | # base URL from which the finished HTML is served. 223 | # 224 | # html_use_opensearch = '' 225 | 226 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 227 | # html_file_suffix = None 228 | 229 | # Language to be used for generating the HTML full-text search index. 230 | # Sphinx supports the following languages: 231 | # 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' 232 | # 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr', 'zh' 233 | # 234 | # html_search_language = 'en' 235 | 236 | # A dictionary with options for the search language support, empty by default. 237 | # 'ja' uses this config value. 238 | # 'zh' user can custom change `jieba` dictionary path. 239 | # 240 | # html_search_options = {'type': 'default'} 241 | 242 | # The name of a javascript file (relative to the configuration directory) that 243 | # implements a search results scorer. If empty, the default will be used. 244 | # 245 | # html_search_scorer = 'scorer.js' 246 | 247 | # Output file base name for HTML help builder. 248 | htmlhelp_basename = 'nexpose-client-pythondoc' 249 | 250 | # -- Options for LaTeX output --------------------------------------------- 251 | 252 | latex_elements = { 253 | # The paper size ('letterpaper' or 'a4paper'). 254 | # 255 | # 'papersize': 'letterpaper', 256 | 257 | # The font size ('10pt', '11pt' or '12pt'). 258 | # 259 | # 'pointsize': '10pt', 260 | 261 | # Additional stuff for the LaTeX preamble. 262 | # 263 | # 'preamble': '', 264 | 265 | # Latex figure (float) alignment 266 | # 267 | # 'figure_align': 'htbp', 268 | } 269 | 270 | # Grouping the document tree into LaTeX files. List of tuples 271 | # (source start file, target name, title, 272 | # author, documentclass [howto, manual, or own class]). 273 | latex_documents = [ 274 | (master_doc, 'nexpose-client-python.tex', u'nexpose-client-python Documentation', 275 | u'Rapid7', 'manual'), 276 | ] 277 | 278 | # The name of an image file (relative to this directory) to place at the top of 279 | # the title page. 280 | # 281 | # latex_logo = None 282 | 283 | # For "manual" documents, if this is true, then toplevel headings are parts, 284 | # not chapters. 285 | # 286 | # latex_use_parts = False 287 | 288 | # If true, show page references after internal links. 289 | # 290 | # latex_show_pagerefs = False 291 | 292 | # If true, show URL addresses after external links. 293 | # 294 | # latex_show_urls = False 295 | 296 | # Documents to append as an appendix to all manuals. 297 | # 298 | # latex_appendices = [] 299 | 300 | # It false, will not define \strong, \code, itleref, \crossref ... but only 301 | # \sphinxstrong, ..., \sphinxtitleref, ... To help avoid clash with user added 302 | # packages. 303 | # 304 | # latex_keep_old_macro_names = True 305 | 306 | # If false, no module index is generated. 307 | # 308 | # latex_domain_indices = True 309 | 310 | 311 | # -- Options for manual page output --------------------------------------- 312 | 313 | # One entry per manual page. List of tuples 314 | # (source start file, name, description, authors, manual section). 315 | man_pages = [ 316 | (master_doc, 'nexpose-client-python', u'nexpose-client-python Documentation', 317 | [author], 1) 318 | ] 319 | 320 | # If true, show URL addresses after external links. 321 | # 322 | # man_show_urls = False 323 | 324 | 325 | # -- Options for Texinfo output ------------------------------------------- 326 | 327 | # Grouping the document tree into Texinfo files. List of tuples 328 | # (source start file, target name, title, author, 329 | # dir menu entry, description, category) 330 | texinfo_documents = [ 331 | (master_doc, 'nexpose-client-python', u'nexpose-client-python Documentation', 332 | author, 'nexpose-client-python', 'One line description of project.', 333 | 'Miscellaneous'), 334 | ] 335 | 336 | # Documents to append as an appendix to all manuals. 337 | # 338 | # texinfo_appendices = [] 339 | 340 | # If false, no module index is generated. 341 | # 342 | # texinfo_domain_indices = True 343 | 344 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 345 | # 346 | # texinfo_show_urls = 'footnote' 347 | 348 | # If true, do not generate a @detailmenu in the "Top" node's menu. 349 | # 350 | # texinfo_no_detailmenu = False 351 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | if "%SPHINXBUILD%" == "" ( 6 | set SPHINXBUILD=sphinx-build 7 | ) 8 | set BUILDDIR=_build 9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . 10 | set I18NSPHINXOPTS=%SPHINXOPTS% . 11 | if NOT "%PAPER%" == "" ( 12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% 14 | ) 15 | 16 | if "%1" == "" goto help 17 | 18 | if "%1" == "help" ( 19 | :help 20 | echo.Please use `make ^` where ^ is one of 21 | echo. html to make standalone HTML files 22 | echo. dirhtml to make HTML files named index.html in directories 23 | echo. singlehtml to make a single large HTML file 24 | echo. pickle to make pickle files 25 | echo. json to make JSON files 26 | echo. htmlhelp to make HTML files and a HTML help project 27 | echo. qthelp to make HTML files and a qthelp project 28 | echo. devhelp to make HTML files and a Devhelp project 29 | echo. epub to make an epub 30 | echo. epub3 to make an epub3 31 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 32 | echo. text to make text files 33 | echo. man to make manual pages 34 | echo. texinfo to make Texinfo files 35 | echo. gettext to make PO message catalogs 36 | echo. changes to make an overview over all changed/added/deprecated items 37 | echo. xml to make Docutils-native XML files 38 | echo. pseudoxml to make pseudoxml-XML files for display purposes 39 | echo. linkcheck to check all external links for integrity 40 | echo. doctest to run all doctests embedded in the documentation if enabled 41 | echo. coverage to run coverage check of the documentation if enabled 42 | echo. dummy to check syntax errors of document sources 43 | goto end 44 | ) 45 | 46 | if "%1" == "clean" ( 47 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 48 | del /q /s %BUILDDIR%\* 49 | goto end 50 | ) 51 | 52 | 53 | REM Check if sphinx-build is available and fallback to Python version if any 54 | %SPHINXBUILD% 1>NUL 2>NUL 55 | if errorlevel 9009 goto sphinx_python 56 | goto sphinx_ok 57 | 58 | :sphinx_python 59 | 60 | set SPHINXBUILD=python -m sphinx.__init__ 61 | %SPHINXBUILD% 2> nul 62 | if errorlevel 9009 ( 63 | echo. 64 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 65 | echo.installed, then set the SPHINXBUILD environment variable to point 66 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 67 | echo.may add the Sphinx directory to PATH. 68 | echo. 69 | echo.If you don't have Sphinx installed, grab it from 70 | echo.http://sphinx-doc.org/ 71 | exit /b 1 72 | ) 73 | 74 | :sphinx_ok 75 | 76 | 77 | if "%1" == "html" ( 78 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 79 | if errorlevel 1 exit /b 1 80 | echo. 81 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 82 | goto end 83 | ) 84 | 85 | if "%1" == "dirhtml" ( 86 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 87 | if errorlevel 1 exit /b 1 88 | echo. 89 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 90 | goto end 91 | ) 92 | 93 | if "%1" == "singlehtml" ( 94 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml 95 | if errorlevel 1 exit /b 1 96 | echo. 97 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. 98 | goto end 99 | ) 100 | 101 | if "%1" == "pickle" ( 102 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 103 | if errorlevel 1 exit /b 1 104 | echo. 105 | echo.Build finished; now you can process the pickle files. 106 | goto end 107 | ) 108 | 109 | if "%1" == "json" ( 110 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 111 | if errorlevel 1 exit /b 1 112 | echo. 113 | echo.Build finished; now you can process the JSON files. 114 | goto end 115 | ) 116 | 117 | if "%1" == "htmlhelp" ( 118 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 119 | if errorlevel 1 exit /b 1 120 | echo. 121 | echo.Build finished; now you can run HTML Help Workshop with the ^ 122 | .hhp project file in %BUILDDIR%/htmlhelp. 123 | goto end 124 | ) 125 | 126 | if "%1" == "qthelp" ( 127 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 128 | if errorlevel 1 exit /b 1 129 | echo. 130 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 131 | .qhcp project file in %BUILDDIR%/qthelp, like this: 132 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\nexpose-client-python.qhcp 133 | echo.To view the help file: 134 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\nexpose-client-python.ghc 135 | goto end 136 | ) 137 | 138 | if "%1" == "devhelp" ( 139 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp 140 | if errorlevel 1 exit /b 1 141 | echo. 142 | echo.Build finished. 143 | goto end 144 | ) 145 | 146 | if "%1" == "epub" ( 147 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub 148 | if errorlevel 1 exit /b 1 149 | echo. 150 | echo.Build finished. The epub file is in %BUILDDIR%/epub. 151 | goto end 152 | ) 153 | 154 | if "%1" == "epub3" ( 155 | %SPHINXBUILD% -b epub3 %ALLSPHINXOPTS% %BUILDDIR%/epub3 156 | if errorlevel 1 exit /b 1 157 | echo. 158 | echo.Build finished. The epub3 file is in %BUILDDIR%/epub3. 159 | goto end 160 | ) 161 | 162 | if "%1" == "latex" ( 163 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 164 | if errorlevel 1 exit /b 1 165 | echo. 166 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 167 | goto end 168 | ) 169 | 170 | if "%1" == "latexpdf" ( 171 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 172 | cd %BUILDDIR%/latex 173 | make all-pdf 174 | cd %~dp0 175 | echo. 176 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 177 | goto end 178 | ) 179 | 180 | if "%1" == "latexpdfja" ( 181 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 182 | cd %BUILDDIR%/latex 183 | make all-pdf-ja 184 | cd %~dp0 185 | echo. 186 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 187 | goto end 188 | ) 189 | 190 | if "%1" == "text" ( 191 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text 192 | if errorlevel 1 exit /b 1 193 | echo. 194 | echo.Build finished. The text files are in %BUILDDIR%/text. 195 | goto end 196 | ) 197 | 198 | if "%1" == "man" ( 199 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man 200 | if errorlevel 1 exit /b 1 201 | echo. 202 | echo.Build finished. The manual pages are in %BUILDDIR%/man. 203 | goto end 204 | ) 205 | 206 | if "%1" == "texinfo" ( 207 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo 208 | if errorlevel 1 exit /b 1 209 | echo. 210 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. 211 | goto end 212 | ) 213 | 214 | if "%1" == "gettext" ( 215 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale 216 | if errorlevel 1 exit /b 1 217 | echo. 218 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale. 219 | goto end 220 | ) 221 | 222 | if "%1" == "changes" ( 223 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 224 | if errorlevel 1 exit /b 1 225 | echo. 226 | echo.The overview file is in %BUILDDIR%/changes. 227 | goto end 228 | ) 229 | 230 | if "%1" == "linkcheck" ( 231 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 232 | if errorlevel 1 exit /b 1 233 | echo. 234 | echo.Link check complete; look for any errors in the above output ^ 235 | or in %BUILDDIR%/linkcheck/output.txt. 236 | goto end 237 | ) 238 | 239 | if "%1" == "doctest" ( 240 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 241 | if errorlevel 1 exit /b 1 242 | echo. 243 | echo.Testing of doctests in the sources finished, look at the ^ 244 | results in %BUILDDIR%/doctest/output.txt. 245 | goto end 246 | ) 247 | 248 | if "%1" == "coverage" ( 249 | %SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage 250 | if errorlevel 1 exit /b 1 251 | echo. 252 | echo.Testing of coverage in the sources finished, look at the ^ 253 | results in %BUILDDIR%/coverage/python.txt. 254 | goto end 255 | ) 256 | 257 | if "%1" == "xml" ( 258 | %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml 259 | if errorlevel 1 exit /b 1 260 | echo. 261 | echo.Build finished. The XML files are in %BUILDDIR%/xml. 262 | goto end 263 | ) 264 | 265 | if "%1" == "pseudoxml" ( 266 | %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml 267 | if errorlevel 1 exit /b 1 268 | echo. 269 | echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. 270 | goto end 271 | ) 272 | 273 | if "%1" == "dummy" ( 274 | %SPHINXBUILD% -b dummy %ALLSPHINXOPTS% %BUILDDIR%/dummy 275 | if errorlevel 1 exit /b 1 276 | echo. 277 | echo.Build finished. Dummy builder generates no files. 278 | goto end 279 | ) 280 | 281 | :end 282 | -------------------------------------------------------------------------------- /docs/modules.rst: -------------------------------------------------------------------------------- 1 | nexpose 2 | ======= 3 | 4 | .. toctree:: 5 | :maxdepth: 4 6 | 7 | nexpose 8 | -------------------------------------------------------------------------------- /docs/nexpose.rst: -------------------------------------------------------------------------------- 1 | nexpose package 2 | =============== 3 | 4 | Submodules 5 | ---------- 6 | 7 | nexpose.dtd module 8 | ------------------ 9 | 10 | .. automodule:: nexpose.dtd 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | nexpose.dtd_utils module 16 | ------------------------ 17 | 18 | .. automodule:: nexpose.dtd_utils 19 | :members: 20 | :undoc-members: 21 | :show-inheritance: 22 | 23 | nexpose.json_utils module 24 | ------------------------- 25 | 26 | .. automodule:: nexpose.json_utils 27 | :members: 28 | :undoc-members: 29 | :show-inheritance: 30 | 31 | nexpose.nexpose module 32 | ---------------------- 33 | 34 | .. automodule:: nexpose.nexpose 35 | :members: 36 | :undoc-members: 37 | :show-inheritance: 38 | 39 | nexpose.nexpose_asset module 40 | ---------------------------- 41 | 42 | .. automodule:: nexpose.nexpose_asset 43 | :members: 44 | :undoc-members: 45 | :show-inheritance: 46 | 47 | nexpose.nexpose_assetfilter module 48 | ---------------------------------- 49 | 50 | .. automodule:: nexpose.nexpose_assetfilter 51 | :members: 52 | :undoc-members: 53 | :show-inheritance: 54 | 55 | nexpose.nexpose_assetgroup module 56 | --------------------------------- 57 | 58 | .. automodule:: nexpose.nexpose_assetgroup 59 | :members: 60 | :undoc-members: 61 | :show-inheritance: 62 | 63 | nexpose.nexpose_backup module 64 | ----------------------------- 65 | 66 | .. automodule:: nexpose.nexpose_backup 67 | :members: 68 | :undoc-members: 69 | :show-inheritance: 70 | 71 | nexpose.nexpose_credential module 72 | --------------------------------- 73 | 74 | .. automodule:: nexpose.nexpose_credential 75 | :members: 76 | :undoc-members: 77 | :show-inheritance: 78 | 79 | nexpose.nexpose_criteria module 80 | ------------------------------- 81 | 82 | .. automodule:: nexpose.nexpose_criteria 83 | :members: 84 | :undoc-members: 85 | :show-inheritance: 86 | 87 | nexpose.nexpose_criteria_constants module 88 | ----------------------------------------- 89 | 90 | .. automodule:: nexpose.nexpose_criteria_constants 91 | :members: 92 | :undoc-members: 93 | :show-inheritance: 94 | 95 | nexpose.nexpose_criteria_fields module 96 | -------------------------------------- 97 | 98 | .. automodule:: nexpose.nexpose_criteria_fields 99 | :members: 100 | :undoc-members: 101 | :show-inheritance: 102 | 103 | nexpose.nexpose_criteria_operators module 104 | ----------------------------------------- 105 | 106 | .. automodule:: nexpose.nexpose_criteria_operators 107 | :members: 108 | :undoc-members: 109 | :show-inheritance: 110 | 111 | nexpose.nexpose_demo module 112 | --------------------------- 113 | 114 | .. automodule:: nexpose.nexpose_demo 115 | :members: 116 | :undoc-members: 117 | :show-inheritance: 118 | 119 | nexpose.nexpose_discoveryconnection module 120 | ------------------------------------------ 121 | 122 | .. automodule:: nexpose.nexpose_discoveryconnection 123 | :members: 124 | :undoc-members: 125 | :show-inheritance: 126 | 127 | nexpose.nexpose_engine module 128 | ----------------------------- 129 | 130 | .. automodule:: nexpose.nexpose_engine 131 | :members: 132 | :undoc-members: 133 | :show-inheritance: 134 | 135 | nexpose.nexpose_enginepool module 136 | --------------------------------- 137 | 138 | .. automodule:: nexpose.nexpose_enginepool 139 | :members: 140 | :undoc-members: 141 | :show-inheritance: 142 | 143 | nexpose.nexpose_node module 144 | --------------------------- 145 | 146 | .. automodule:: nexpose.nexpose_node 147 | :members: 148 | :undoc-members: 149 | :show-inheritance: 150 | 151 | nexpose.nexpose_privileges module 152 | --------------------------------- 153 | 154 | .. automodule:: nexpose.nexpose_privileges 155 | :members: 156 | :undoc-members: 157 | :show-inheritance: 158 | 159 | nexpose.nexpose_report module 160 | ----------------------------- 161 | 162 | .. automodule:: nexpose.nexpose_report 163 | :members: 164 | :undoc-members: 165 | :show-inheritance: 166 | 167 | nexpose.nexpose_role module 168 | --------------------------- 169 | 170 | .. automodule:: nexpose.nexpose_role 171 | :members: 172 | :undoc-members: 173 | :show-inheritance: 174 | 175 | nexpose.nexpose_scansummary module 176 | ---------------------------------- 177 | 178 | .. automodule:: nexpose.nexpose_scansummary 179 | :members: 180 | :undoc-members: 181 | :show-inheritance: 182 | 183 | nexpose.nexpose_sharedcredential module 184 | --------------------------------------- 185 | 186 | .. automodule:: nexpose.nexpose_sharedcredential 187 | :members: 188 | :undoc-members: 189 | :show-inheritance: 190 | 191 | nexpose.nexpose_site module 192 | --------------------------- 193 | 194 | .. automodule:: nexpose.nexpose_site 195 | :members: 196 | :undoc-members: 197 | :show-inheritance: 198 | 199 | nexpose.nexpose_status module 200 | ----------------------------- 201 | 202 | .. automodule:: nexpose.nexpose_status 203 | :members: 204 | :undoc-members: 205 | :show-inheritance: 206 | 207 | nexpose.nexpose_tag module 208 | -------------------------- 209 | 210 | .. automodule:: nexpose.nexpose_tag 211 | :members: 212 | :undoc-members: 213 | :show-inheritance: 214 | 215 | nexpose.nexpose_ticket module 216 | ----------------------------- 217 | 218 | .. automodule:: nexpose.nexpose_ticket 219 | :members: 220 | :undoc-members: 221 | :show-inheritance: 222 | 223 | nexpose.nexpose_user module 224 | --------------------------- 225 | 226 | .. automodule:: nexpose.nexpose_user 227 | :members: 228 | :undoc-members: 229 | :show-inheritance: 230 | 231 | nexpose.nexpose_userauthenticator module 232 | ---------------------------------------- 233 | 234 | .. automodule:: nexpose.nexpose_userauthenticator 235 | :members: 236 | :undoc-members: 237 | :show-inheritance: 238 | 239 | nexpose.nexpose_vulnerability module 240 | ------------------------------------ 241 | 242 | .. automodule:: nexpose.nexpose_vulnerability 243 | :members: 244 | :undoc-members: 245 | :show-inheritance: 246 | 247 | nexpose.nexpose_vulnerabilityexception module 248 | --------------------------------------------- 249 | 250 | .. automodule:: nexpose.nexpose_vulnerabilityexception 251 | :members: 252 | :undoc-members: 253 | :show-inheritance: 254 | 255 | nexpose.python_utils module 256 | --------------------------- 257 | 258 | .. automodule:: nexpose.python_utils 259 | :members: 260 | :undoc-members: 261 | :show-inheritance: 262 | 263 | nexpose.xml_utils module 264 | ------------------------ 265 | 266 | .. automodule:: nexpose.xml_utils 267 | :members: 268 | :undoc-members: 269 | :show-inheritance: 270 | 271 | 272 | Module contents 273 | --------------- 274 | 275 | .. automodule:: nexpose 276 | :members: 277 | :undoc-members: 278 | :show-inheritance: 279 | -------------------------------------------------------------------------------- /nexpose/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rapid7/nexpose-client-python/628143a94c55246144aa98f5f1863a2c1dfbfe65/nexpose/__init__.py -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /nexpose/nexpose_asset.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 | from .nexpose_tag import Tag 10 | 11 | 12 | class AssetHostTypes(object): 13 | Empty = '' 14 | Guest = 'GUEST' 15 | Hypervisor = 'HYPERVISOR' 16 | Physical = 'PHYSICAL' 17 | Mobile = 'MOBILE' 18 | 19 | 20 | class AssetBase(object): 21 | def InitializeFromXML(self, xml_data): 22 | self.id = int(get_attribute(xml_data, 'id', self.id)) 23 | self.risk_score = float(get_attribute(xml_data, 'riskscore', self.risk_score)) 24 | 25 | def InitializeFromJSON(self, json_dict): 26 | self.id = json_dict['id'] 27 | try: 28 | self.risk_score = json_dict['assessment']['json']['risk_score'] 29 | except KeyError: 30 | pass 31 | 32 | def __init__(self): 33 | self.id = 0 34 | self.risk_score = 0.0 35 | 36 | 37 | class AssetSummary(AssetBase): 38 | @staticmethod 39 | def Create(): 40 | return AssetSummary() 41 | 42 | @staticmethod 43 | def CreateFromXML(xml_data, site_id=None): 44 | asset = AssetSummary.Create() 45 | asset.InitializeFromXML(xml_data) 46 | asset.site_id = int(site_id if site_id is not None else get_attribute(xml_data, 'site-id', asset.site_id)) 47 | asset.host = get_attribute(xml_data, 'address', asset.host) 48 | asset.risk_factor = float('0' + get_attribute(xml_data, 'riskfactor', asset.risk_factor)) # riskfactor can be an emtpy string 49 | return asset 50 | 51 | def __init__(self): 52 | AssetBase.__init__(self) 53 | self.site_id = 0 54 | self.host = '' 55 | self.risk_factor = 1.0 56 | 57 | 58 | class AssetDetails(AssetBase): 59 | @staticmethod 60 | def CreateFromJSON(json_dict): 61 | host_names = json_dict["host_names"] 62 | host_type = json_dict["host_type"] 63 | details = AssetDetails() 64 | details.InitializeFromJSON(json_dict) 65 | details.ip_address = json_dict["ip"] 66 | details.mac_address = json_dict["mac"] 67 | details.addresses = json_dict["addresses"] 68 | if host_names is not None: 69 | details.host_names = host_names 70 | if host_type is not None: 71 | details.host_type = host_type 72 | details.os_name = json_dict["os_name"] 73 | details.os_cpe = json_dict["os_cpe"] 74 | try: 75 | assessment = json_dict['assessment']['json'] 76 | except KeyError: 77 | pass 78 | else: 79 | details.last_scan_id = assessment['last_scan_id'] 80 | details.last_scan_date = assessment['last_scan_date'] 81 | 82 | try: 83 | tags = json_dict['tags']['json']['resources'] 84 | except KeyError: 85 | pass 86 | else: 87 | for tag in tags: 88 | details.tags.append(Tag.CreateFromJSON(tag)) 89 | 90 | details.unique_identifiers = [] 91 | try: 92 | unique_identifiers_data = json_dict['unique_identifiers']['json'] 93 | except KeyError: 94 | # Unique Identifiers not fetched 95 | pass 96 | else: 97 | for identifier in unique_identifiers_data: 98 | details.unique_identifiers.append( 99 | UniqueIdentifier.CreateFromJSON(identifier) 100 | ) 101 | 102 | # TODO: 103 | # ----begin 104 | details.files = [] 105 | details.vulnerability_instances = [] 106 | details.group_accounts = [] 107 | details.user_accounts = [] 108 | details.vulnerabilities = [] 109 | details.software = [] 110 | details.services = [] 111 | # TODO: 112 | # ----end 113 | return details 114 | 115 | def __init__(self): 116 | AssetBase.__init__(self) 117 | self.ip_address = '' 118 | self.mac_address = '' 119 | self.addresses = [] 120 | self.host_names = [] 121 | self.host_type = AssetHostTypes.Empty 122 | self.os_name = '' 123 | self.os_cpe = '' 124 | self.last_scan_id = 0 125 | self.last_scan_date = '' 126 | self.files = [] 127 | self.vulnerability_instances = [] 128 | self.unique_identifiers = [] 129 | self.group_accounts = [] 130 | self.user_accounts = [] 131 | self.vulnerabilities = [] 132 | self.software = [] 133 | self.services = [] 134 | self.tags = [] 135 | 136 | 137 | class UniqueIdentifier(object): 138 | 139 | def __init__(self): 140 | self.source = '' 141 | self.id = '' 142 | 143 | @staticmethod 144 | def CreateFromJSON(json_dict): 145 | unique_identifier = UniqueIdentifier() 146 | unique_identifier.source = json_dict['source'] 147 | unique_identifier.id = json_dict['id'] 148 | return unique_identifier 149 | 150 | def __repr__(self): 151 | return ''.format( 152 | type=self.source, 153 | id=self.id, 154 | ) 155 | -------------------------------------------------------------------------------- /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_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_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_credential.py: -------------------------------------------------------------------------------- 1 | # Future Imports for py2/3 backwards compat. 2 | from __future__ import (absolute_import, division, print_function, 3 | unicode_literals) 4 | # Auto-created by 'create_credential_code.py' 5 | from builtins import object 6 | from .xml_utils import create_element, get_content_of 7 | from .python_utils import is_subclass_of 8 | import sys 9 | from future import standard_library 10 | standard_library.install_aliases() 11 | 12 | 13 | def GetSupportedCredentials(): 14 | this_module = sys.modules[__name__] 15 | credentials = [this_module.__dict__[name] for name in dir(this_module) if is_subclass_of(this_module.__dict__[name], Credential)] 16 | for credential in credentials: 17 | if credential.SERVICE_TYPE: 18 | yield credential 19 | 20 | 21 | class Credential(object): 22 | SERVICE_TYPE = None 23 | DEFAULT_PORT = 0 24 | 25 | # NOTE: factory method in a base class (not so-clean) 26 | @staticmethod 27 | def CreateFromXML(xml, service_type): 28 | for credential in GetSupportedCredentials(): 29 | if service_type == credential.SERVICE_TYPE: 30 | return credential.CreateFromXML(xml) 31 | return None # TODO: raise exception 32 | 33 | @staticmethod 34 | def CreateFromType(service_type): 35 | for credential in GetSupportedCredentials(): 36 | if service_type == credential.SERVICE_TYPE: 37 | return credential.Create() 38 | return None # TODO: raise exception 39 | 40 | 41 | def _create_field(key, value): 42 | field = create_element('Field', {'name': key}) 43 | field.text = value 44 | return field 45 | 46 | 47 | def _create_field_and_append(xml, key, value): 48 | xml.append(_create_field(key, value)) 49 | 50 | 51 | class Credential_RemoteExecution(Credential): 52 | SERVICE_TYPE = 'remote execution' 53 | DEFAULT_PORT = 512 54 | 55 | @staticmethod 56 | def CreateFromXML(xml): 57 | credential = Credential_RemoteExecution() 58 | credential.username = get_content_of(xml, "Field/[@name='username']", credential.username) 59 | credential.password = get_content_of(xml, "Field/[@name='password']", credential.password) 60 | return credential 61 | 62 | @staticmethod 63 | def Create(): 64 | credential = Credential_RemoteExecution() 65 | credential.id = -1 66 | return credential 67 | 68 | def __init__(self): 69 | self.username = '' 70 | self.password = '' 71 | 72 | def AsXML(self): 73 | xml = create_element('Account', {'type': 'nexpose'}) 74 | _create_field_and_append(xml, 'username', self.username) 75 | _create_field_and_append(xml, 'password', self.password) 76 | return xml 77 | 78 | 79 | class Credential_FTP(Credential): 80 | SERVICE_TYPE = 'ftp' 81 | DEFAULT_PORT = 21 82 | 83 | @staticmethod 84 | def CreateFromXML(xml): 85 | credential = Credential_FTP() 86 | credential.username = get_content_of(xml, "Field/[@name='username']", credential.username) 87 | credential.password = get_content_of(xml, "Field/[@name='password']", credential.password) 88 | return credential 89 | 90 | @staticmethod 91 | def Create(): 92 | credential = Credential_FTP() 93 | credential.id = -1 94 | return credential 95 | 96 | def __init__(self): 97 | self.username = '' 98 | self.password = '' 99 | 100 | def AsXML(self): 101 | xml = create_element('Account', {'type': 'nexpose'}) 102 | _create_field_and_append(xml, 'username', self.username) 103 | _create_field_and_append(xml, 'password', self.password) 104 | return xml 105 | 106 | 107 | class Credential_SSH_KEY(Credential): 108 | SERVICE_TYPE = 'ssh-key' 109 | DEFAULT_PORT = 22 110 | 111 | @staticmethod 112 | def CreateFromXML(xml): 113 | credential = Credential_SSH_KEY() 114 | credential.username = get_content_of(xml, "Field/[@name='username']", credential.username) 115 | credential.password = get_content_of(xml, "Field/[@name='password']", credential.password) 116 | credential.pemkey = get_content_of(xml, "Field/[@name='pemkey']", credential.pemkey) 117 | credential.privilege_elevation_username = get_content_of(xml, "Field/[@name='privilegeelevationusername']", credential.privilege_elevation_username) 118 | credential.privilege_elevation_password = get_content_of(xml, "Field/[@name='privilegeelevationpassword']", credential.privilege_elevation_password) 119 | credential.privilege_elevation_type = get_content_of(xml, "Field/[@name='privilegeelevationtype']", credential.privilege_elevation_type) 120 | return credential 121 | 122 | @staticmethod 123 | def Create(): 124 | credential = Credential_SSH_KEY() 125 | credential.id = -1 126 | return credential 127 | 128 | def __init__(self): 129 | self.username = '' 130 | self.password = '' 131 | self.pemkey = '' 132 | self.privilege_elevation_username = '' 133 | self.privilege_elevation_password = '' 134 | self.privilege_elevation_type = PrivilegeElevationType.NONE 135 | 136 | def AsXML(self): 137 | xml = create_element('Account', {'type': 'nexpose'}) 138 | _create_field_and_append(xml, 'username', self.username) 139 | _create_field_and_append(xml, 'password', self.password) 140 | _create_field_and_append(xml, 'pemkey', self.pemkey) 141 | _create_field_and_append(xml, 'privilegeelevationusername', self.privilege_elevation_username) 142 | _create_field_and_append(xml, 'privilegeelevationpassword', self.privilege_elevation_password) 143 | _create_field_and_append(xml, 'privilegeelevationtype', self.privilege_elevation_type) 144 | return xml 145 | 146 | 147 | class Credential_HTTP(Credential): 148 | SERVICE_TYPE = 'http' 149 | DEFAULT_PORT = 80 150 | 151 | @staticmethod 152 | def CreateFromXML(xml): 153 | credential = Credential_HTTP() 154 | credential.username = get_content_of(xml, "Field/[@name='username']", credential.username) 155 | credential.password = get_content_of(xml, "Field/[@name='password']", credential.password) 156 | credential.domain = get_content_of(xml, "Field/[@name='domain']", credential.domain) 157 | return credential 158 | 159 | @staticmethod 160 | def Create(): 161 | credential = Credential_HTTP() 162 | credential.id = -1 163 | return credential 164 | 165 | def __init__(self): 166 | self.username = '' 167 | self.password = '' 168 | self.domain = '' 169 | 170 | def AsXML(self): 171 | xml = create_element('Account', {'type': 'nexpose'}) 172 | _create_field_and_append(xml, 'username', self.username) 173 | _create_field_and_append(xml, 'password', self.password) 174 | _create_field_and_append(xml, 'domain', self.domain) 175 | return xml 176 | 177 | 178 | class Credential_CIFS(Credential): 179 | SERVICE_TYPE = 'cifs' 180 | DEFAULT_PORT = 445 181 | 182 | @staticmethod 183 | def CreateFromXML(xml): 184 | credential = Credential_CIFS() 185 | credential.username = get_content_of(xml, "Field/[@name='username']", credential.username) 186 | credential.password = get_content_of(xml, "Field/[@name='password']", credential.password) 187 | credential.domain = get_content_of(xml, "Field/[@name='domain']", credential.domain) 188 | return credential 189 | 190 | @staticmethod 191 | def Create(): 192 | credential = Credential_CIFS() 193 | credential.id = -1 194 | return credential 195 | 196 | def __init__(self): 197 | self.username = '' 198 | self.password = '' 199 | self.domain = '' 200 | 201 | def AsXML(self): 202 | xml = create_element('Account', {'type': 'nexpose'}) 203 | _create_field_and_append(xml, 'username', self.username) 204 | _create_field_and_append(xml, 'password', self.password) 205 | _create_field_and_append(xml, 'domain', self.domain) 206 | return xml 207 | 208 | 209 | class Credential_AS400(Credential): 210 | SERVICE_TYPE = 'as400' 211 | DEFAULT_PORT = 449 212 | 213 | @staticmethod 214 | def CreateFromXML(xml): 215 | credential = Credential_AS400() 216 | credential.username = get_content_of(xml, "Field/[@name='username']", credential.username) 217 | credential.password = get_content_of(xml, "Field/[@name='password']", credential.password) 218 | credential.domain = get_content_of(xml, "Field/[@name='domain']", credential.domain) 219 | return credential 220 | 221 | @staticmethod 222 | def Create(): 223 | credential = Credential_AS400() 224 | credential.id = -1 225 | return credential 226 | 227 | def __init__(self): 228 | self.username = '' 229 | self.password = '' 230 | self.domain = '' 231 | 232 | def AsXML(self): 233 | xml = create_element('Account', {'type': 'nexpose'}) 234 | _create_field_and_append(xml, 'username', self.username) 235 | _create_field_and_append(xml, 'password', self.password) 236 | _create_field_and_append(xml, 'domain', self.domain) 237 | return xml 238 | 239 | 240 | class Credential_Notes(Credential): 241 | SERVICE_TYPE = 'notes' 242 | DEFAULT_PORT = 1352 243 | 244 | @staticmethod 245 | def CreateFromXML(xml): 246 | credential = Credential_Notes() 247 | credential.password = get_content_of(xml, "Field/[@name='password']", credential.password) 248 | return credential 249 | 250 | @staticmethod 251 | def Create(): 252 | credential = Credential_Notes() 253 | credential.id = -1 254 | return credential 255 | 256 | def __init__(self): 257 | self.password = '' 258 | 259 | def AsXML(self): 260 | xml = create_element('Account', {'type': 'nexpose'}) 261 | _create_field_and_append(xml, 'password', self.password) 262 | return xml 263 | 264 | 265 | class Credential_SNMP(Credential): 266 | SERVICE_TYPE = 'snmp' 267 | DEFAULT_PORT = 161 268 | 269 | @staticmethod 270 | def CreateFromXML(xml): 271 | credential = Credential_SNMP() 272 | credential.password = get_content_of(xml, "Field/[@name='password']", credential.password) 273 | return credential 274 | 275 | @staticmethod 276 | def Create(): 277 | credential = Credential_SNMP() 278 | credential.id = -1 279 | return credential 280 | 281 | def __init__(self): 282 | self.password = '' 283 | 284 | def AsXML(self): 285 | xml = create_element('Account', {'type': 'nexpose'}) 286 | _create_field_and_append(xml, 'password', self.password) 287 | return xml 288 | 289 | 290 | class Credential_CVS(Credential): 291 | SERVICE_TYPE = 'cvs' 292 | DEFAULT_PORT = 2401 293 | 294 | @staticmethod 295 | def CreateFromXML(xml): 296 | credential = Credential_CVS() 297 | credential.username = get_content_of(xml, "Field/[@name='username']", credential.username) 298 | credential.password = get_content_of(xml, "Field/[@name='password']", credential.password) 299 | return credential 300 | 301 | @staticmethod 302 | def Create(): 303 | credential = Credential_CVS() 304 | credential.id = -1 305 | return credential 306 | 307 | def __init__(self): 308 | self.username = '' 309 | self.password = '' 310 | 311 | def AsXML(self): 312 | xml = create_element('Account', {'type': 'nexpose'}) 313 | _create_field_and_append(xml, 'username', self.username) 314 | _create_field_and_append(xml, 'password', self.password) 315 | return xml 316 | 317 | 318 | class Credential_POP(Credential): 319 | SERVICE_TYPE = 'pop' 320 | DEFAULT_PORT = 110 321 | 322 | @staticmethod 323 | def CreateFromXML(xml): 324 | credential = Credential_POP() 325 | credential.username = get_content_of(xml, "Field/[@name='username']", credential.username) 326 | credential.password = get_content_of(xml, "Field/[@name='password']", credential.password) 327 | return credential 328 | 329 | @staticmethod 330 | def Create(): 331 | credential = Credential_POP() 332 | credential.id = -1 333 | return credential 334 | 335 | def __init__(self): 336 | self.username = '' 337 | self.password = '' 338 | 339 | def AsXML(self): 340 | xml = create_element('Account', {'type': 'nexpose'}) 341 | _create_field_and_append(xml, 'username', self.username) 342 | _create_field_and_append(xml, 'password', self.password) 343 | return xml 344 | 345 | 346 | class Credential_Sybase(Credential): 347 | SERVICE_TYPE = 'sybase' 348 | DEFAULT_PORT = 5000 349 | 350 | @staticmethod 351 | def CreateFromXML(xml): 352 | credential = Credential_Sybase() 353 | credential.username = get_content_of(xml, "Field/[@name='username']", credential.username) 354 | credential.password = get_content_of(xml, "Field/[@name='password']", credential.password) 355 | credential.domain = get_content_of(xml, "Field/[@name='domain']", credential.domain) 356 | credential.database = get_content_of(xml, "Field/[@name='database']", credential.database) 357 | return credential 358 | 359 | @staticmethod 360 | def Create(): 361 | credential = Credential_Sybase() 362 | credential.id = -1 363 | return credential 364 | 365 | def __init__(self): 366 | self.username = '' 367 | self.password = '' 368 | self.domain = '' 369 | self.database = '' 370 | 371 | def AsXML(self): 372 | xml = create_element('Account', {'type': 'nexpose'}) 373 | _create_field_and_append(xml, 'username', self.username) 374 | _create_field_and_append(xml, 'password', self.password) 375 | _create_field_and_append(xml, 'domain', self.domain) 376 | _create_field_and_append(xml, 'database', self.database) 377 | return xml 378 | 379 | 380 | class Credential_DB2(Credential): 381 | SERVICE_TYPE = 'db2' 382 | DEFAULT_PORT = 50000 383 | 384 | @staticmethod 385 | def CreateFromXML(xml): 386 | credential = Credential_DB2() 387 | credential.username = get_content_of(xml, "Field/[@name='username']", credential.username) 388 | credential.password = get_content_of(xml, "Field/[@name='password']", credential.password) 389 | credential.database = get_content_of(xml, "Field/[@name='database']", credential.database) 390 | return credential 391 | 392 | @staticmethod 393 | def Create(): 394 | credential = Credential_DB2() 395 | credential.id = -1 396 | return credential 397 | 398 | def __init__(self): 399 | self.username = '' 400 | self.password = '' 401 | self.database = '' 402 | 403 | def AsXML(self): 404 | xml = create_element('Account', {'type': 'nexpose'}) 405 | _create_field_and_append(xml, 'username', self.username) 406 | _create_field_and_append(xml, 'password', self.password) 407 | _create_field_and_append(xml, 'database', self.database) 408 | return xml 409 | 410 | 411 | class Credential_Telnet(Credential): 412 | SERVICE_TYPE = 'telnet' 413 | DEFAULT_PORT = 23 414 | 415 | @staticmethod 416 | def CreateFromXML(xml): 417 | credential = Credential_Telnet() 418 | credential.username = get_content_of(xml, "Field/[@name='username']", credential.username) 419 | credential.password = get_content_of(xml, "Field/[@name='password']", credential.password) 420 | return credential 421 | 422 | @staticmethod 423 | def Create(): 424 | credential = Credential_Telnet() 425 | credential.id = -1 426 | return credential 427 | 428 | def __init__(self): 429 | self.username = '' 430 | self.password = '' 431 | 432 | def AsXML(self): 433 | xml = create_element('Account', {'type': 'nexpose'}) 434 | _create_field_and_append(xml, 'username', self.username) 435 | _create_field_and_append(xml, 'password', self.password) 436 | return xml 437 | 438 | 439 | class Credential_Oracle(Credential): 440 | SERVICE_TYPE = 'oracle' 441 | DEFAULT_PORT = 1521 442 | 443 | @staticmethod 444 | def CreateFromXML(xml): 445 | credential = Credential_Oracle() 446 | credential.username = get_content_of(xml, "Field/[@name='username']", credential.username) 447 | credential.password = get_content_of(xml, "Field/[@name='password']", credential.password) 448 | credential.database = get_content_of(xml, "Field/[@name='database']", credential.database) 449 | return credential 450 | 451 | @staticmethod 452 | def Create(): 453 | credential = Credential_Oracle() 454 | credential.id = -1 455 | return credential 456 | 457 | def __init__(self): 458 | self.username = '' 459 | self.password = '' 460 | self.database = '' 461 | 462 | def AsXML(self): 463 | xml = create_element('Account', {'type': 'nexpose'}) 464 | _create_field_and_append(xml, 'username', self.username) 465 | _create_field_and_append(xml, 'password', self.password) 466 | _create_field_and_append(xml, 'database', self.database) 467 | return xml 468 | 469 | 470 | class Credential_MySQL(Credential): 471 | SERVICE_TYPE = 'mysql' 472 | DEFAULT_PORT = 3306 473 | 474 | @staticmethod 475 | def CreateFromXML(xml): 476 | credential = Credential_MySQL() 477 | credential.username = get_content_of(xml, "Field/[@name='username']", credential.username) 478 | credential.password = get_content_of(xml, "Field/[@name='password']", credential.password) 479 | credential.database = get_content_of(xml, "Field/[@name='database']", credential.database) 480 | return credential 481 | 482 | @staticmethod 483 | def Create(): 484 | credential = Credential_MySQL() 485 | credential.id = -1 486 | return credential 487 | 488 | def __init__(self): 489 | self.username = '' 490 | self.password = '' 491 | self.database = '' 492 | 493 | def AsXML(self): 494 | xml = create_element('Account', {'type': 'nexpose'}) 495 | _create_field_and_append(xml, 'username', self.username) 496 | _create_field_and_append(xml, 'password', self.password) 497 | _create_field_and_append(xml, 'database', self.database) 498 | return xml 499 | 500 | 501 | class Credential_TDS(Credential): 502 | SERVICE_TYPE = 'tds' 503 | DEFAULT_PORT = 1433 504 | 505 | @staticmethod 506 | def CreateFromXML(xml): 507 | credential = Credential_TDS() 508 | credential.username = get_content_of(xml, "Field/[@name='username']", credential.username) 509 | credential.password = get_content_of(xml, "Field/[@name='password']", credential.password) 510 | credential.domain = get_content_of(xml, "Field/[@name='domain']", credential.domain) 511 | credential.database = get_content_of(xml, "Field/[@name='database']", credential.database) 512 | return credential 513 | 514 | @staticmethod 515 | def Create(): 516 | credential = Credential_TDS() 517 | credential.id = -1 518 | return credential 519 | 520 | def __init__(self): 521 | self.username = '' 522 | self.password = '' 523 | self.domain = '' 524 | self.database = '' 525 | 526 | def AsXML(self): 527 | xml = create_element('Account', {'type': 'nexpose'}) 528 | _create_field_and_append(xml, 'username', self.username) 529 | _create_field_and_append(xml, 'password', self.password) 530 | _create_field_and_append(xml, 'domain', self.domain) 531 | _create_field_and_append(xml, 'database', self.database) 532 | return xml 533 | 534 | 535 | class Credential_CIFS_Hash(Credential): 536 | SERVICE_TYPE = 'cifshash' 537 | DEFAULT_PORT = 445 538 | 539 | @staticmethod 540 | def CreateFromXML(xml): 541 | credential = Credential_CIFS_Hash() 542 | credential.username = get_content_of(xml, "Field/[@name='username']", credential.username) 543 | credential.domain = get_content_of(xml, "Field/[@name='domain']", credential.domain) 544 | credential.ntlm_hash = get_content_of(xml, "Field/[@name='ntlmhash']", credential.ntlm_hash) 545 | return credential 546 | 547 | @staticmethod 548 | def Create(): 549 | credential = Credential_CIFS_Hash() 550 | credential.id = -1 551 | return credential 552 | 553 | def __init__(self): 554 | self.username = '' 555 | self.domain = '' 556 | self.ntlm_hash = '' 557 | 558 | def AsXML(self): 559 | xml = create_element('Account', {'type': 'nexpose'}) 560 | _create_field_and_append(xml, 'username', self.username) 561 | _create_field_and_append(xml, 'domain', self.domain) 562 | _create_field_and_append(xml, 'ntlmhash', self.ntlm_hash) 563 | return xml 564 | 565 | 566 | class Credential_PostgreSQL(Credential): 567 | SERVICE_TYPE = 'postgresql' 568 | DEFAULT_PORT = 5432 569 | 570 | @staticmethod 571 | def CreateFromXML(xml): 572 | credential = Credential_PostgreSQL() 573 | credential.username = get_content_of(xml, "Field/[@name='username']", credential.username) 574 | credential.password = get_content_of(xml, "Field/[@name='password']", credential.password) 575 | credential.database = get_content_of(xml, "Field/[@name='database']", credential.database) 576 | return credential 577 | 578 | @staticmethod 579 | def Create(): 580 | credential = Credential_PostgreSQL() 581 | credential.id = -1 582 | return credential 583 | 584 | def __init__(self): 585 | self.username = '' 586 | self.password = '' 587 | self.database = '' 588 | 589 | def AsXML(self): 590 | xml = create_element('Account', {'type': 'nexpose'}) 591 | _create_field_and_append(xml, 'username', self.username) 592 | _create_field_and_append(xml, 'password', self.password) 593 | _create_field_and_append(xml, 'database', self.database) 594 | return xml 595 | 596 | 597 | class Credential_SSH(Credential): 598 | SERVICE_TYPE = 'ssh' 599 | DEFAULT_PORT = 22 600 | 601 | @staticmethod 602 | def CreateFromXML(xml): 603 | credential = Credential_SSH() 604 | credential.username = get_content_of(xml, "Field/[@name='username']", credential.username) 605 | credential.password = get_content_of(xml, "Field/[@name='password']", credential.password) 606 | credential.privilege_elevation_username = get_content_of(xml, "Field/[@name='privilegeelevationusername']", credential.privilege_elevation_username) 607 | credential.privilege_elevation_password = get_content_of(xml, "Field/[@name='privilegeelevationpassword']", credential.privilege_elevation_password) 608 | credential.privilege_elevation_type = get_content_of(xml, "Field/[@name='privilegeelevationtype']", credential.privilege_elevation_type) 609 | return credential 610 | 611 | @staticmethod 612 | def Create(): 613 | credential = Credential_SSH() 614 | credential.id = -1 615 | return credential 616 | 617 | def __init__(self): 618 | self.username = '' 619 | self.password = '' 620 | self.privilege_elevation_username = '' 621 | self.privilege_elevation_password = '' 622 | self.privilege_elevation_type = PrivilegeElevationType.NONE 623 | 624 | def AsXML(self): 625 | xml = create_element('Account', {'type': 'nexpose'}) 626 | _create_field_and_append(xml, 'username', self.username) 627 | _create_field_and_append(xml, 'password', self.password) 628 | _create_field_and_append(xml, 'privilegeelevationusername', self.privilege_elevation_username) 629 | _create_field_and_append(xml, 'privilegeelevationpassword', self.privilege_elevation_password) 630 | _create_field_and_append(xml, 'privilegeelevationtype', self.privilege_elevation_type) 631 | return xml 632 | 633 | 634 | class Credential_SNMPV3(Credential): 635 | SERVICE_TYPE = 'snmpv3' 636 | DEFAULT_PORT = 161 637 | 638 | @staticmethod 639 | def CreateFromXML(xml): 640 | credential = Credential_SNMPV3() 641 | credential.username = get_content_of(xml, "Field/[@name='username']", credential.username) 642 | credential.password = get_content_of(xml, "Field/[@name='password']", credential.password) 643 | credential.snmpv3_authentication_type = get_content_of(xml, "Field/[@name='snmpv3authtype']", credential.snmpv3_authentication_type) 644 | credential.snmpv3_private_type = get_content_of(xml, "Field/[@name='snmpv3privtype']", credential.snmpv3_private_type) 645 | credential.snmpv3_private_password = get_content_of(xml, "Field/[@name='snmpv3privpassword']", credential.snmpv3_private_password) 646 | return credential 647 | 648 | @staticmethod 649 | def Create(): 650 | credential = Credential_SNMPV3() 651 | credential.id = -1 652 | return credential 653 | 654 | def __init__(self): 655 | self.username = '' 656 | self.password = '' 657 | self.snmpv3_authentication_type = '' 658 | self.snmpv3_private_type = '' 659 | self.snmpv3_private_password = '' 660 | 661 | def AsXML(self): 662 | xml = create_element('Account', {'type': 'nexpose'}) 663 | _create_field_and_append(xml, 'username', self.username) 664 | _create_field_and_append(xml, 'password', self.password) 665 | _create_field_and_append(xml, 'snmpv3authtype', self.snmpv3_authentication_type) 666 | _create_field_and_append(xml, 'snmpv3privtype', self.snmpv3_private_type) 667 | _create_field_and_append(xml, 'snmpv3privpassword', self.snmpv3_private_password) 668 | return xml 669 | 670 | 671 | class PrivilegeElevationType(object): 672 | NONE = 'NONE' # none 673 | SUDO = 'SUDO' # sudo 674 | SUDOSU = 'SUDOSU' # sudo+su 675 | SU = 'SU' # su 676 | PBRUN = 'PBRUN' # pbrun 677 | -------------------------------------------------------------------------------- /nexpose/nexpose_criteria.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 | import sys 6 | from future import standard_library 7 | from .json_utils import JSON 8 | from .nexpose_criteria_fields import * # this will also import the operators and the constants 9 | from .python_utils import is_iterable, is_subclass_of 10 | standard_library.install_aliases() 11 | _current_module = sys.modules[__name__] 12 | _all_uppercase_names = [name for name in dir(_current_module) if name.isupper()] 13 | 14 | 15 | def _get_by_name(name): 16 | return _current_module.__dict__[name] 17 | 18 | 19 | def _get_filtered_classes(required_class): 20 | _all_uppercase_variables = [_get_by_name(name) for name in _all_uppercase_names] 21 | return [variable for variable in _all_uppercase_variables if is_subclass_of(variable, required_class)] 22 | 23 | 24 | def GetFields(): 25 | """Returns a list of supported field by Nexpose Criteria""" 26 | return _get_filtered_classes(NexposeCriteriaField) 27 | 28 | 29 | def GetFieldNames(): 30 | """Returns a list of supported field names by Nexpose Criteria""" 31 | return [field.Name for field in _get_filtered_classes(NexposeCriteriaField)] 32 | 33 | 34 | def GetFieldByName(name): 35 | """Gets a field (object) by name. 36 | Raises a LookupError if no field with the specified name exists.""" 37 | if name not in GetFieldNames(): 38 | raise LookupError("Criteria Field with name {0} not found!".format(name)) 39 | return _get_by_name(name) 40 | 41 | 42 | def GetOperators(): 43 | """Returns a list of supported operators by Nexpose Criteria""" 44 | return _get_filtered_classes(NexposeCriteriaOperator) 45 | 46 | 47 | def GetOperatorCodes(): 48 | """Returns a list of supported operator codes by Nexpose Criteria""" 49 | return [operator.Code for operator in _get_filtered_classes(NexposeCriteriaOperator)] 50 | 51 | 52 | def GetOperatorByCode(code): 53 | """Gets a operator (object) by code. 54 | Raises a LookupError if no field with the specified code exists.""" 55 | if code not in GetOperatorCodes(): 56 | raise LookupError("Criteria Operator with code {0} not found!".format(code)) 57 | return _get_by_name(name) 58 | 59 | 60 | def GetConstants(): 61 | """Returns a list of all available constant values used by some Nexpose Criteria""" 62 | return _get_filtered_classes(NexposeCriteriaConstant) 63 | 64 | 65 | def GetConstantNames(): 66 | """Returns a list of supported constant names by Nexpose Criteria""" 67 | return [constant.Name for constant in _get_filtered_classes(NexposeCriteriaConstant)] 68 | 69 | 70 | def GetConstantByName(name): 71 | """Gets a constant (object) by name. 72 | Raises a LookupError if no constant with the specified name exists.""" 73 | if name not in GetConstantNames(): 74 | raise LookupError("Criteria Constant with name {0} not found!".format(name)) 75 | return _get_by_name(name) 76 | 77 | 78 | class Criterion(JSON): 79 | @staticmethod 80 | def Create(field, operator, value=None): 81 | return Criterion(field, operator, value) 82 | 83 | def __init__(self, field, operator, value=None): 84 | assert is_subclass_of(field, NexposeCriteriaField) 85 | assert is_subclass_of(operator, NexposeCriteriaOperator) 86 | assert operator in field.ValidOperators 87 | if is_subclass_of(value, NexposeCriteriaConstant): 88 | value = NexposeCriteriaConstant.Value 89 | self.field = field 90 | self.operator = operator 91 | self.value = value if value is not None else '' 92 | 93 | def as_json(self): 94 | json_data = dict() 95 | json_data['metadata'] = dict() 96 | json_data['metadata']['fieldName'] = self.field.Code 97 | json_data['operator'] = self.operator.Code 98 | json_data['values'] = list(self.value) if is_iterable(self.value) else [self.value] 99 | return json_data 100 | 101 | 102 | class Criteria(object): 103 | @staticmethod 104 | def Create(criteria=None, operator=None): 105 | return Criteria(criteria, operator) 106 | 107 | def _as_operator(self, operator_or_code, default_operator): 108 | if operator_or_code is None: 109 | return default_operator 110 | if not is_subclass_of(operator_or_code, NexposeCriteriaOperator): 111 | operator_or_code = GetOperatorByCode(operator_or_code) 112 | assert is_subclass_of(operator_or_code, AND) or is_subclass_of(operator_or_code, OR) 113 | return operator_or_code 114 | 115 | def __init__(self, criteria=None, operator_or_code=None): 116 | if criteria is None: 117 | criteria = [] 118 | self.operator = self._as_operator(operator_or_code, AND) 119 | self.criteria = list(criteria) if is_iterable(criteria) else [criteria] 120 | 121 | def as_json(self): 122 | json_data = dict() 123 | json_data['operator'] = self.operator.Code 124 | json_data['criteria'] = [criterion.as_json() for criterion in self.criteria] 125 | return json_data 126 | 127 | 128 | # shortcut: 129 | def Create(criteria=None, operator=None): 130 | return Criteria.Create(criteria, operator) 131 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /nexpose/nexpose_criteria_fields.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 range 5 | from builtins import object 6 | from .nexpose_criteria_operators import * 7 | from .nexpose_criteria_constants import * 8 | from future import standard_library 9 | from future.utils import with_metaclass 10 | standard_library.install_aliases() 11 | 12 | 13 | def xrange_inclusive(start, included_stop): 14 | return range(start, included_stop + 1) 15 | 16 | 17 | class MetaNexposeCriteriaField(type): 18 | @property 19 | def Code(cls): 20 | return cls._Code if cls._Code else cls.Name 21 | 22 | @property 23 | def Name(cls): 24 | return cls.__name__ 25 | 26 | def __str__(cls): 27 | return cls.Name 28 | 29 | 30 | class NexposeCriteriaField(with_metaclass(MetaNexposeCriteriaField, object)): 31 | _Code = None 32 | ValidOperators = [] # Note: this list shouldn't be empty 33 | ValidValues = None # None indicates that any value is accepted 34 | 35 | 36 | class ASSET(NexposeCriteriaField): 37 | ValidOperators = (IS, IS_NOT, STARTS_WITH, ENDS_WITH, CONTAINS, NOT_CONTAINS) 38 | 39 | 40 | class CVE_ID(NexposeCriteriaField): 41 | ValidOperators = (IS, IS_NOT, CONTAINS, NOT_CONTAINS) 42 | 43 | 44 | class CVSS_ACCESS_COMPLEXITY(NexposeCriteriaField): 45 | ValidOperators = (IS, IS_NOT) 46 | ValidValues = ('LOW', 'MEDIUM', 'HIGH') # See Value::AccessComplexity? 47 | 48 | 49 | class CVSS_ACCESS_VECTOR(NexposeCriteriaField): 50 | ValidOperators = (IS, IS_NOT) 51 | ValidValues = ('LOCAL', 'ADJACENT', 'NETWORK') # See Value::AccessVector? 52 | 53 | 54 | class CVSS_AUTHENTICATION_REQUIRED(NexposeCriteriaField): 55 | ValidOperators = (IS, IS_NOT) 56 | ValidValues = ('NONE', 'SINGLE', 'MULTIPLE') # See Value::AuthenticationRequired? 57 | 58 | 59 | class CVSS_AVAILABILITY_IMPACT(NexposeCriteriaField): 60 | ValidOperators = (IS, IS_NOT) 61 | ValidValues = ('NONE', 'PARTIAL', 'COMPLETE') # See Value::CVSSImpact? 62 | 63 | 64 | # TODO: duplication with CVSS_AVAILABILITY_IMPACT 65 | class CVSS_CONFIDENTIALITY_IMPACT(NexposeCriteriaField): 66 | ValidOperators = (IS, IS_NOT) 67 | ValidValues = ('NONE', 'PARTIAL', 'COMPLETE') # See Value::CVSSImpact? 68 | 69 | 70 | # TODO: duplication with CVSS_AVAILABILITY_IMPACT 71 | class CVSS_INTEGRITY_IMPACT(NexposeCriteriaField): 72 | ValidOperators = (IS, IS_NOT) 73 | ValidValues = ('NONE', 'PARTIAL', 'COMPLETE') # See Value::CVSSImpact? 74 | 75 | 76 | class CVSS_SCORE(NexposeCriteriaField): 77 | ValidOperators = (IS, IS_NOT, IN_RANGE, GREATER_THAN, LESS_THAN) 78 | ValidValues = xrange_inclusive(0, 10) # TODO: are integers accepted or must they really be floats ? 79 | 80 | 81 | class HOST_TYPE(NexposeCriteriaField): 82 | ValidOperators = (IN, NOT_IN) 83 | ValidValues = ('UNKNOWN', 'VIRTUAL', 'HYPERVISOR', 'BARE_METAL') # See Value::HostType? 84 | 85 | 86 | class IP_ADDRESS_TYPE(NexposeCriteriaField): 87 | ValidOperators = (IN, NOT_IN) 88 | ValidValues = ('IPv4', 'IPv6') # See Value::IPType? 89 | 90 | 91 | # TODO: duplication with IP_ADDRESS_TYPE 92 | class IP_ALT_ADDRESS_TYPE(NexposeCriteriaField): 93 | ValidOperators = (IN, NOT_IN) 94 | ValidValues = ('IPv4', 'IPv6') # See Value::IPType? 95 | 96 | 97 | class IP_RANGE(NexposeCriteriaField): 98 | ValidOperators = (IS, IS_NOT, IN, NOT_IN) 99 | 100 | 101 | class OPEN_PORT(NexposeCriteriaField): 102 | ValidOperators = (IS, IS_NOT, IN_RANGE) 103 | ValidValues = xrange_inclusive(1, 65535) 104 | 105 | 106 | class OS(NexposeCriteriaField): 107 | ValidOperators = (CONTAINS, NOT_CONTAINS, IS_EMPTY, IS_NOT_EMPTY) 108 | 109 | 110 | class PCI_COMPLIANCE_STATUS(NexposeCriteriaField): 111 | ValidOperators = (IS,) 112 | ValidValues = ('PASS', 'FAIL') 113 | 114 | 115 | class RISK_SCORE(NexposeCriteriaField): 116 | ValidOperators = (IS, IS_NOT, IN_RANGE, GREATER_THAN, LESS_THAN) 117 | 118 | 119 | class SCAN_DATE(NexposeCriteriaField): 120 | ValidOperators = (ON_OR_BEFORE, ON_OR_AFTER, BETWEEN, EARLIER_THAN, WITHIN_THE_LAST) 121 | # TODO: ?? 122 | # ValueValues = FixNum for day arguments && Value::ScanDate::FORMAT for date arguments 123 | 124 | 125 | class SERVICE(NexposeCriteriaField): 126 | ValidOperators = (CONTAINS, NOT_CONTAINS) 127 | 128 | 129 | class SITE_ID(NexposeCriteriaField): 130 | _Code = 'SITE_NAME' # Note that underlying search uses Site ID, despite 'site name' value. 131 | ValidOperators = (IN, NOT_IN) 132 | 133 | 134 | class SOFTWARE(NexposeCriteriaField): 135 | ValidOperators = (CONTAINS, NOT_CONTAINS) 136 | 137 | 138 | class TAG_CRITICALITY(NexposeCriteriaField): 139 | ValidOperators = (IS, IS_NOT, GREATER_THAN, LESS_THAN, IS_APPLIED, IS_NOT_APPLIED) 140 | ValidValues = ('Very High', 'High', 'Medium', 'Low', 'Very Low') 141 | 142 | 143 | class USER_ADDED_CRITICALITY_LEVEL(TAG_CRITICALITY): # Added to be compatible with Rapid7's Nexpose Ruby API 144 | _Code = 'TAG_CRITICALITY' 145 | 146 | 147 | class TAG(NexposeCriteriaField): 148 | ValidOperators = (IS, IS_NOT, STARTS_WITH, ENDS_WITH, IS_APPLIED, IS_NOT_APPLIED, CONTAINS, NOT_CONTAINS) 149 | 150 | 151 | class USER_ADDED_CUSTOM_TAG(TAG): # Added to be compatible with Rapid7's Nexpose Ruby API 152 | _Code = 'TAG' 153 | 154 | 155 | class TAG_LOCATION(NexposeCriteriaField): 156 | ValidOperators = (IS, IS_NOT, STARTS_WITH, ENDS_WITH, IS_APPLIED, IS_NOT_APPLIED, CONTAINS, NOT_CONTAINS) 157 | 158 | 159 | class USER_ADDED_TAG_LOCATION(TAG_LOCATION): # Added to be compatible with Rapid7's Nexpose Ruby API 160 | _Code = 'TAG_LOCATION' 161 | 162 | 163 | class TAG_OWNER(NexposeCriteriaField): 164 | ValidOperators = (IS, IS_NOT, STARTS_WITH, ENDS_WITH, IS_APPLIED, IS_NOT_APPLIED, CONTAINS, NOT_CONTAINS) 165 | 166 | 167 | class USER_ADDED_TAG_OWNER(TAG_OWNER): # Added to be compatible with Rapid7's Nexpose Ruby API 168 | _Code = 'TAG_OWNER' 169 | 170 | 171 | class VULNERABILITY_VALIDATED_STATUS(NexposeCriteriaField): 172 | ValidOperators = (ARE,) 173 | ValidValues = ('PRESENT', 'NOT_PRESENT') 174 | 175 | 176 | class VALIDATED_VULNERABILITIES(VULNERABILITY_VALIDATED_STATUS): # Added to be compatible with Rapid7's Nexpose Ruby API 177 | _Code = 'VULNERABILITY_VALIDATED_STATUS' 178 | 179 | 180 | class VULNERABILITY(NexposeCriteriaField): 181 | ValidOperators = (CONTAINS, NOT_CONTAINS) 182 | 183 | 184 | class VULNERABILITY_EXPOSURES(NexposeCriteriaField): 185 | ValidOperators = (INCLUDE, DO_NOT_INCLUDE) 186 | ValidValues = ('MALWARE', 'METASPLOIT', 'DATABASE') # See Value::VulnerabilityExposure? 187 | 188 | 189 | class VULN_CATEGORY(NexposeCriteriaField): 190 | ValidOperators = (IS, IS_NOT, CONTAINS, NOT_CONTAINS, STARTS_WITH, ENDS_WITH) 191 | 192 | 193 | class VULNERABILITY_CATEGORY(VULN_CATEGORY): # Added to be compatible with Rapid7's Nexpose Ruby API 194 | _Code = 'VULN_CATEGORY' 195 | -------------------------------------------------------------------------------- /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_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_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_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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /nexpose/nexpose_role.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 copy import deepcopy 6 | from .xml_utils import create_element, get_attribute, get_content_of, get_children_of 7 | from future import standard_library 8 | standard_library.install_aliases() 9 | 10 | 11 | class RoleScope(object): 12 | Global = 'global' 13 | Silo = 'silo' 14 | 15 | 16 | class _RoleBase(object): 17 | def InitalizeFromXML(self, xml_data, description_fieldname, description_getter): 18 | self.id = int(get_attribute(xml_data, 'id', self.id)) 19 | self.name = get_attribute(xml_data, 'name', self.name) 20 | self.fullname = get_attribute(xml_data, 'full-name', self.fullname) 21 | self.description = description_getter(xml_data, description_fieldname, self.description) 22 | self.is_enabled = get_attribute(xml_data, 'enabled', self.is_enabled) in ['true', '1', True] 23 | self.scope = get_attribute(xml_data, 'scope', self.scope) 24 | 25 | def __init__(self): 26 | self.id = 0 27 | self.name = '' 28 | self.fullname = '' 29 | self.description = '' 30 | self.is_enabled = True 31 | self.scope = RoleScope.Silo 32 | 33 | 34 | class RoleSummary(_RoleBase): 35 | @staticmethod 36 | def CreateFromXML(xml_data): 37 | summary = RoleSummary() 38 | summary.InitalizeFromXML(xml_data, 'description', get_attribute) 39 | return summary 40 | 41 | def __init__(self): 42 | _RoleBase.__init__(self) 43 | 44 | 45 | class RoleDetails(RoleSummary): 46 | @staticmethod 47 | def CreateFromXML(xml_data): 48 | detail = RoleDetails() 49 | detail.InitalizeFromXML(xml_data, 'Description', get_content_of) 50 | detail.assetgroup_privileges = RoleDetails._ExtractPrivileges(xml_data, 'AssetGroupPrivileges') 51 | detail.global_privileges = RoleDetails._ExtractPrivileges(xml_data, 'GlobalPrivileges') 52 | detail.site_privileges = RoleDetails._ExtractPrivileges(xml_data, 'SitePrivileges') 53 | return detail 54 | 55 | @staticmethod 56 | def Create(): 57 | details = RoleDetails() 58 | details.id = -1 59 | return details 60 | 61 | @staticmethod 62 | def CreateNamed(name, fullname=None): 63 | details = RoleDetails.Create() 64 | details.name = name 65 | details.fullname = name if fullname is None else fullname 66 | return details 67 | 68 | @staticmethod 69 | def CreateNamedBasedOn(source, name, fullname=None): 70 | if not isinstance(source, RoleDetails): 71 | raise ValueError('source must be a nexpose.RoleDetails instance') 72 | details = deepcopy(source) 73 | details.id = -1 74 | details.name = name 75 | details.fullname = name if fullname is None else fullname 76 | return details 77 | 78 | def __init__(self): 79 | RoleSummary.__init__(self) 80 | self.assetgroup_privileges = {} 81 | self.global_privileges = {} 82 | self.site_privileges = {} 83 | 84 | def AsXML(self, exclude_id): 85 | attributes = {} 86 | if not exclude_id: 87 | attributes['id'] = self.id 88 | attributes['name'] = self.name 89 | attributes['full-name'] = self.fullname 90 | attributes['enabled'] = '1' if self.is_enabled else '0' 91 | attributes['scope'] = self.scope 92 | xml_description = create_element('Description') 93 | xml_data = create_element('Role', attributes) 94 | xml_description.text = self.description 95 | xml_data.append(xml_description) 96 | xml_data.append(RoleDetails._CreatePrivelegesElement('AssetGroupPrivileges', self.assetgroup_privileges)) 97 | xml_data.append(RoleDetails._CreatePrivelegesElement('GlobalPrivileges', self.global_privileges)) 98 | xml_data.append(RoleDetails._CreatePrivelegesElement('SitePrivileges', self.site_privileges)) 99 | return xml_data 100 | 101 | @staticmethod 102 | def _ExtractPrivileges(xml_data, tag): 103 | xml_children = get_children_of(xml_data, tag) 104 | return dict([(e.tag, e.attrib.get('enabled') in ['1', 'true']) for e in xml_children]) 105 | 106 | @staticmethod 107 | def _CreatePrivelegesElement(tag, privileges): 108 | xml_data = create_element(tag) 109 | for key, value in privileges.items(): 110 | attribute = {'enabled': 1 if value else 0} 111 | xml_subdata = create_element(key, attribute) 112 | xml_data.append(xml_subdata) 113 | return xml_data 114 | -------------------------------------------------------------------------------- /nexpose/nexpose_scansummary.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 map 5 | from builtins import object 6 | from .xml_utils import get_attribute, get_element, get_content_of 7 | from future import standard_library 8 | standard_library.install_aliases() 9 | 10 | 11 | class VulnerabilityStatus(object): 12 | vuln_exploit = 'vuln-exploit' 13 | vuln_version = 'vuln-version' 14 | vuln_potential = 'vuln-potential' 15 | not_vuln_exploit = 'not-vuln-exploit' 16 | not_vuln_version = 'not-vuln-version' 17 | error = 'error' 18 | disabled = 'disabled' 19 | other = 'other' 20 | 21 | 22 | class ScanSummaryVulnerability(object): 23 | @staticmethod 24 | def CreateFromXML(xml_data): 25 | vulnerability = ScanSummaryVulnerability() 26 | vulnerability.status = get_attribute(xml_data, 'status', vulnerability.status) 27 | vulnerability.severity = int(get_attribute(xml_data, 'severity', vulnerability.severity)) 28 | vulnerability.count = int(get_attribute(xml_data, 'count', vulnerability.count)) 29 | return vulnerability 30 | 31 | def __init__(self): 32 | self.status = '' 33 | self.severity = 0 34 | self.count = 0 35 | 36 | 37 | class ScanSummaryTaskCounts(object): 38 | @staticmethod 39 | def CreateFromXML(xml_data): 40 | task_counts = ScanSummaryTaskCounts() 41 | task_counts.pending = int(get_attribute(xml_data, 'pending', task_counts.pending)) 42 | task_counts.active = int(get_attribute(xml_data, 'active', task_counts.active)) 43 | task_counts.completed = int(get_attribute(xml_data, 'completed', task_counts.completed)) 44 | return task_counts 45 | 46 | def __init__(self): 47 | self.pending = 0 48 | self.active = 0 49 | self.completed = 0 50 | 51 | 52 | class ScanSummaryNodeCounts(object): 53 | @staticmethod 54 | def CreateFromXML(xml_data): 55 | node_counts = ScanSummaryNodeCounts() 56 | node_counts.live = int(get_attribute(xml_data, 'live', '0')) 57 | node_counts.dead = int(get_attribute(xml_data, 'dead', '0')) 58 | node_counts.filtered = int(get_attribute(xml_data, 'filtered', '0')) 59 | node_counts.unresolved = int(get_attribute(xml_data, 'unresolved', '0')) 60 | node_counts.other = int(get_attribute(xml_data, 'other', '0')) 61 | return node_counts 62 | 63 | def __init__(self): 64 | self.live = 0 65 | self.dead = 0 66 | self.filtered = 0 67 | self.unresolved = 0 68 | self.other = 0 69 | 70 | 71 | class ScanStatus(object): 72 | Running = 'running' 73 | Finished = 'finished' 74 | Stopped = 'stopped' 75 | Error = 'error' 76 | Dispatched = 'dispatched' 77 | Paused = 'paused' 78 | Aborted = 'aborted' 79 | Unknown = 'unknown' 80 | 81 | 82 | class ScanSummary(object): 83 | @staticmethod 84 | def CreateFromXML(xml_data): 85 | summary = ScanSummary() 86 | summary.id = int(get_attribute(xml_data, 'scan-id', summary.id)) 87 | summary.site_id = int(get_attribute(xml_data, 'site-id', summary.site_id)) 88 | summary.engine_id = int(get_attribute(xml_data, 'engine-id', summary.engine_id)) 89 | summary.scan_status = get_attribute(xml_data, 'status', summary.scan_status) 90 | summary.start_time = get_attribute(xml_data, 'startTime', summary.start_time) 91 | summary.end_time = get_attribute(xml_data, 'endTime', summary.end_time) 92 | summary.name = get_attribute(xml_data, 'name', summary.name) 93 | if get_content_of(xml_data, 'message') is not None: 94 | summary.message = get_content_of(xml_data, 'message', summary.message) 95 | else: 96 | summary.message = get_content_of(xml_data, 'Message', summary.message) 97 | if get_element(xml_data, 'tasks') is not None: 98 | summary.task_counts = ScanSummaryTaskCounts.CreateFromXML(get_element(xml_data, 'tasks', summary.task_counts)) 99 | else: 100 | summary.task_counts = ScanSummaryTaskCounts.CreateFromXML(get_element(xml_data, 'TaskSummary', summary.task_counts)) 101 | if get_element(xml_data, 'nodes') is not None: 102 | summary.node_counts = ScanSummaryNodeCounts.CreateFromXML(get_element(xml_data, 'nodes', summary.node_counts)) 103 | else: 104 | summary.node_counts = ScanSummaryNodeCounts.CreateFromXML(get_element(xml_data, 'NodeSummary', summary.node_counts)) 105 | if get_element(xml_data, 'vulnerabilities') is not None: 106 | summary.vulnerabilities = list(map(ScanSummaryVulnerability.CreateFromXML, xml_data.findall('vulnerabilities'))) 107 | else: 108 | summary.vulnerabilities = list(map(ScanSummaryVulnerability.CreateFromXML, xml_data.findall('VulnerabilitySummary'))) 109 | return summary 110 | 111 | def __init__(self): 112 | self.id = 0 113 | self.site_id = 0 114 | self.engine_id = 0 115 | self.scan_status = '' 116 | self.start_time = '' 117 | self.end_time = '' 118 | self.name = '' 119 | self.message = '' 120 | self.task_counts = None 121 | self.node_counts = None 122 | self.vulnerabilities = [] 123 | -------------------------------------------------------------------------------- /nexpose/nexpose_sharedcredential.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, get_element, get_children_of, create_element 6 | from .nexpose_credential import Credential 7 | from future import standard_library 8 | standard_library.install_aliases() 9 | 10 | """ 11 | sites = xml.add_element('Sites') 12 | sites.add_attribute('all', @all_sites ? 1 : 0) 13 | @sites.each do |s| 14 | site = sites.add_element('Site') 15 | site.add_attribute('id', s) 16 | site.add_attribute('enabled', 0) if @disabled.member? s 17 | end 18 | if @sites.empty? 19 | @disabled.each do |s| 20 | site = sites.add_element('Site') 21 | site.add_attribute('id', s) 22 | site.add_attribute('enabled', 0) 23 | end 24 | end 25 | 26 | xml 27 | end 28 | """ 29 | 30 | 31 | class SharedCredentialBase(object): 32 | def __init__(self): 33 | self.id = 0 34 | self.name = '' 35 | self.all_sites = True 36 | 37 | def _get_service(self): 38 | return '' 39 | 40 | @property 41 | def service(self): 42 | return self._get_service() 43 | 44 | 45 | class SharedCredentialSummary(SharedCredentialBase): 46 | @staticmethod 47 | def CreateFromJSON(json): 48 | credential = SharedCredentialSummary() 49 | 50 | # SharedCredentialBase: 51 | credential.id = int(json['credentialID']['ID']) 52 | credential.name = json['name'] 53 | credential.all_sites = json['scope'] == 'ALL_SITES_ENABLED_DEFAULT' 54 | 55 | # SharedCredentialSummary-specific: 56 | credential._service = json['service'] 57 | credential.username = json['username'] 58 | credential.domain = json['domain'] 59 | credential.privilege_username = json['privilegeElevationUsername'] 60 | credential.site_count = json['assignedSites'] 61 | credential.last_modified = json['lastModified']['time'] # TODO 62 | 63 | return credential 64 | 65 | def __init__(self): 66 | SharedCredentialBase.__init__(self) 67 | self.username = '' 68 | self.domain = '' 69 | self.privilege_username = '' 70 | self.site_count = 0 71 | self.last_modified = '' 72 | 73 | def _get_service(self): 74 | return self._service 75 | 76 | 77 | class SharedCredentialConfiguration(SharedCredentialBase): 78 | @staticmethod 79 | def CreateFromXML(xml): 80 | sites = [site for site in get_children_of(xml, 'Sites') if site.tag == 'Site'] 81 | 82 | service = get_attribute(get_element(xml, "Services/Service"), 'type') 83 | credential = SharedCredentialConfiguration() 84 | credential.id = int(get_attribute(xml, "id")) 85 | credential.name = get_content_of(xml, "Name") 86 | credential.description = get_content_of(xml, "Description") 87 | credential.credential = Credential.CreateFromXML(get_element(xml, "Account"), service) 88 | credential.restriction_host = get_content_of(xml, "Restrictions/Restriction/[@type='host']", credential.restriction_host) 89 | credential.restriction_port = int(get_content_of(xml, "Restrictions/Restriction/[@type='port']", credential.restriction_port)) 90 | credential.all_sites = get_attribute(get_element(xml, "Sites"), 'all') == '1' 91 | credential.enabled_sites = [get_attribute(site, 'id') for site in sites if get_attribute(site, 'enabled') == '1'] 92 | credential.disabled_sites = [get_attribute(site, 'id') for site in sites if get_attribute(site, 'enabled') != '1'] 93 | return credential 94 | 95 | @staticmethod 96 | def Create(): 97 | credential = SharedCredentialConfiguration() 98 | credential.id = -1 99 | return credential 100 | 101 | def _get_service(self): 102 | if self.credential: 103 | return self.credential.SERVICE_TYPE 104 | return None 105 | 106 | def __init__(self): 107 | SharedCredentialBase.__init__(self) 108 | self.description = '' 109 | self.credential = None 110 | self.restriction_host = '' 111 | self.restriction_port = 0 112 | self.enabled_sites = [] 113 | self.disabled_sites = [] 114 | 115 | def AsXML(self): 116 | xml = create_element('Credential', {'shared': 1, 'enabled': 0, 'id': self.id}) 117 | if self.credential: 118 | xml.append(self.credential.AsXML()) 119 | xml_name = create_element('Name') 120 | xml_name.text = self.name 121 | xml.append(xml_name) 122 | xml_description = create_element('Description') 123 | xml_description.text = self.description 124 | xml.append(xml_description) 125 | xml_services = create_element('Services') 126 | xml_services.append(create_element('Service', {'type': self.service})) 127 | xml.append(xml_services) 128 | xml_restrictions = create_element('Restrictions') 129 | if self.restriction_host: 130 | xml_restriction = create_element('Restriction', {'type': 'host'}) 131 | xml_restriction.text = self.restriction_host 132 | xml_restrictions.append(xml_restriction) 133 | if self.restriction_port: 134 | xml_restriction = create_element('Restriction', {'type': 'port'}) 135 | xml_restriction.text = self.restriction_port 136 | xml_restrictions.append(xml_restriction) 137 | xml.append(xml_restrictions) 138 | xml.append(create_element('Sites', {'all': 1 if self.all_sites else 0})) # TODO: enabled/disabled sites 139 | return xml 140 | -------------------------------------------------------------------------------- /nexpose/nexpose_site.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, get_children_of, create_element, as_string, as_xml, get_element 6 | from future import standard_library 7 | standard_library.install_aliases() 8 | 9 | 10 | class Range(object): 11 | def __init__(self, start, end): 12 | self.start = start 13 | self.end = end if end else start 14 | 15 | def AsXML(self): 16 | attributes = {} 17 | attributes['from'] = self.start 18 | if self.end != self.start: 19 | attributes['to'] = self.end 20 | xml_data = create_element('range', attributes) 21 | return xml_data 22 | 23 | 24 | class Host(object): 25 | def __init__(self, name): 26 | self.name = name 27 | 28 | def AsXML(self): 29 | xml_data = create_element('host') 30 | xml_data.text = self.name 31 | return xml_data 32 | 33 | 34 | def _host_to_object(host): 35 | if host.tag == "host": 36 | return Host(host.text) 37 | if host.tag == "range": 38 | return Range(get_attribute(host, 'from'), get_attribute(host, 'to')) 39 | raise ValueError('Unknown host type: {0}'.format(host.tag)) 40 | 41 | 42 | class ScanConfiguration(object): 43 | def __init__(self): 44 | self.id = 0 45 | self.name = '' 46 | self.version = 0 47 | self.template_id = "full-audit-without-web-spider" 48 | self.engine_id = 0 49 | 50 | 51 | class SiteBase(object): 52 | def InitalizeFromXML(self, xml_data): 53 | self.id = int(get_attribute(xml_data, 'id', self.id)) 54 | self.name = get_attribute(xml_data, 'name', self.name) 55 | self.short_description = get_attribute(xml_data, 'description', self.short_description) 56 | self.risk_factor = float(get_attribute(xml_data, 'riskfactor', self.risk_factor)) 57 | 58 | def __init__(self): 59 | self.id = 0 60 | self.name = '' 61 | self.short_description = '' # newlines are removed by Nexpose, use SiteConfiguration.description instead 62 | self.risk_factor = 1.0 63 | 64 | 65 | class SiteSummary(SiteBase): 66 | @staticmethod 67 | def CreateFromXML(xml_data): 68 | summary = SiteSummary() 69 | summary.InitalizeFromXML(xml_data) 70 | summary.risk_score = float(get_attribute(xml_data, 'riskscore', summary.risk_score)) 71 | return summary 72 | 73 | def __init__(self): 74 | SiteBase.__init__(self) 75 | self.risk_score = 0.0 76 | 77 | 78 | class SiteConfiguration(SiteBase): 79 | @staticmethod 80 | def CreateFromXML(xml_data): 81 | config = SiteConfiguration() 82 | config.InitalizeFromXML(xml_data) 83 | config.description = get_content_of(xml_data, 'Description', config.description) 84 | config.is_dynamic = get_attribute(xml_data, 'isDynamic', config.is_dynamic) in ['1', 'true', True] 85 | config.hosts = [_host_to_object(host) for host in get_children_of(xml_data, 'Hosts')] 86 | config.alerting = [alert for alert in get_children_of(xml_data, 'Alerting')] 87 | config.credentials = [credential for credential in get_children_of(xml_data, 'Credentials')] 88 | config.users = [user for user in get_children_of(xml_data, 'Users')] 89 | 90 | # Use scanconfig elements for the SiteConfiguration 91 | scanconfig = get_element(xml_data, "ScanConfig") 92 | config.configid = scanconfig.get("configID") 93 | config.configtemplateid = scanconfig.get("templateID") 94 | config.configname = scanconfig.get("name") 95 | config.configversion = scanconfig.get("configVersion") 96 | config.configengineid = scanconfig.get("engineID") 97 | config.schedules = [schedule for schedule in get_children_of(scanconfig, 'Schedules')] 98 | 99 | return config 100 | 101 | @staticmethod 102 | def Create(): 103 | config = SiteConfiguration() 104 | config.id = -1 105 | return config 106 | 107 | @staticmethod 108 | def CreateNamed(name): 109 | config = SiteConfiguration.Create() 110 | config.name = name 111 | return config 112 | 113 | def __init__(self): 114 | SiteBase.__init__(self) 115 | self.description = '' 116 | self.is_dynamic = False 117 | self.hosts = [] 118 | self.credentials = [] 119 | self.alerting = [] 120 | self.scan_configuration = [] # TODO 121 | self.configid = self.id 122 | self.configtemplateid = "full-audit-without-web-spider" 123 | self.configname = "Full audit without Web Spider" 124 | self.configversion = 3 125 | self.configengineid = 3 126 | self.users = [] 127 | self.schedules = [] 128 | 129 | def AsXML(self, exclude_id): 130 | attributes = {} 131 | if not exclude_id: 132 | attributes['id'] = self.id 133 | attributes['name'] = self.name 134 | attributes['description'] = self.short_description 135 | attributes['isDynamic'] = '1' if self.is_dynamic else '0' 136 | attributes['riskfactor'] = self.risk_factor 137 | 138 | xml_data = create_element('Site', attributes) 139 | 140 | xml_description = create_element('Description') 141 | xml_description.text = self.description 142 | xml_data.append(xml_description) 143 | 144 | xml_hosts = create_element('Hosts') 145 | for host in self.hosts: 146 | xml_hosts.append(host.AsXML()) 147 | xml_data.append(xml_hosts) 148 | 149 | xml_credentials = create_element('Credentials') 150 | for credential in self.credentials: 151 | xml_credentials.append(credential) 152 | xml_data.append(xml_credentials) 153 | 154 | xml_alerting = create_element('Alerting') 155 | for alert in self.alerting: 156 | xml_alerting.append(alert) 157 | xml_data.append(xml_alerting) 158 | 159 | xml_users = create_element('Users') 160 | for user in self.users: 161 | xml_users.append(user) 162 | xml_data.append(xml_users) 163 | 164 | # Include ScanConfig attributes 165 | attributes = {} 166 | attributes['configID'] = self.configid 167 | attributes['name'] = self.configname 168 | attributes['templateID'] = self.configtemplateid 169 | attributes['engineID'] = self.configengineid 170 | attributes['configVersion'] = self.configversion 171 | 172 | xml_scanconfig = create_element('ScanConfig', attributes) 173 | xml_scheduling = create_element('Schedules') 174 | for schedule in self.schedules: 175 | xml_scheduling.append(schedule) 176 | xml_scanconfig.append(xml_scheduling) 177 | xml_data.append(xml_scanconfig) 178 | 179 | # TODO: implement the xxxPrivileges 180 | # print(as_string(as_xml(as_string(xml_data)))) 181 | return xml_data 182 | -------------------------------------------------------------------------------- /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_tag.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 .json_utils import get_id as _get_id, HasID, JSON 6 | from future import standard_library 7 | standard_library.install_aliases() 8 | 9 | DEFAULT_SOURCENAME = "DavinsiLabs-Nexpose-Python" 10 | DEFAULT_TAGCOLOR = "#f6f6f6" 11 | 12 | 13 | class TagColors(object): 14 | DEFAULT = DEFAULT_TAGCOLOR 15 | BLUE = "#496a77" 16 | GREEN = "#7d8a58" 17 | ORANGE = "#de7200" 18 | RED = "#a0392e" 19 | PURPLE = "#844f7d" 20 | 21 | 22 | class TagAttribute(JSON, HasID): 23 | @staticmethod 24 | def CreateFromJSON(json_dict): 25 | name = json_dict["tag_attribute_name"] 26 | value = json_dict["tag_attribute_value"] 27 | 28 | attribute = TagAttribute(name, value) 29 | attribute.id = json_dict.get("tag_attribute_id", 0) 30 | return attribute 31 | 32 | @staticmethod 33 | def Create(name, value=None): 34 | attribute = TagAttribute(name, value) 35 | return attribute 36 | 37 | def __init__(self, name, value): 38 | self.id = 0 39 | self.name = name 40 | self.value = value 41 | 42 | def as_json(self): 43 | json_dict = {} 44 | if self.id: 45 | json_dict["tag_attribute_id"] = self.id 46 | json_dict["tag_attribute_name"] = self.name 47 | json_dict["tag_attribute_value"] = self.value 48 | return json_dict 49 | 50 | 51 | class TagConfiguration(JSON): 52 | @staticmethod 53 | def CreateFromJSON(json_dict): 54 | config = TagConfiguration() 55 | config.assetgroup_ids = json_dict["asset_group_ids"] 56 | config.site_ids = json_dict["site_ids"] 57 | config.associated_asset_ids = json_dict["tag_associated_asset_ids"] 58 | config.search_criteria = json_dict["search_criteria"] 59 | return config 60 | 61 | @staticmethod 62 | def Create(): 63 | return TagConfiguration() 64 | 65 | def __init__(self): 66 | self.assetgroup_ids = [] 67 | self.site_ids = [] 68 | self.associated_asset_ids = [] 69 | self.search_criteria = None 70 | 71 | def as_json(self): 72 | json_dict = {} 73 | json_dict["asset_group_ids"] = self.assetgroup_ids 74 | json_dict["site_ids"] = self.site_ids 75 | json_dict["tag_associated_asset_ids"] = self.associated_asset_ids 76 | json_dict["search_criteria"] = self.search_criteria 77 | return json_dict 78 | 79 | 80 | class Tag(JSON, HasID): 81 | @staticmethod 82 | def GetID(tag): 83 | return _get_id(tag, "tag_id") 84 | 85 | @staticmethod 86 | def CreateFromJSON(json_dict): 87 | name = json_dict["tag_name"] 88 | type = json_dict["tag_type"] 89 | config = json_dict.get("tag_config", None) 90 | 91 | tag = Tag(name, type) 92 | tag.id = json_dict["tag_id"] 93 | tag.config = TagConfiguration.CreateFromJSON(config) if config else None 94 | tag.asset_ids = json_dict["asset_ids"] 95 | tag.attributes = [] 96 | if json_dict.get("attributes"): 97 | for attr in json_dict["attributes"]: 98 | tag.attributes.append(TagAttribute.CreateFromJSON(attr)) 99 | return tag 100 | 101 | @staticmethod 102 | def Create(name, type, source_name=None): 103 | if source_name is None: 104 | source_name = DEFAULT_SOURCENAME 105 | 106 | tag = Tag(name, type) 107 | tag.config = TagConfiguration.Create() 108 | tag.asset_ids = [] 109 | tag.attributes = [TagAttribute("SOURCE", source_name)] 110 | return tag 111 | 112 | @staticmethod 113 | def CreateCustom(name, color=None, source_name=None): 114 | if color is None: 115 | color = DEFAULT_TAGCOLOR 116 | 117 | tag = Tag.Create(name, "CUSTOM", source_name) 118 | tag.attributes.append(TagAttribute("COLOR", color)) 119 | return tag 120 | 121 | def __init__(self, name, type): 122 | self.id = 0 123 | self.name = name 124 | self.type = type 125 | self.config = None 126 | self.asset_ids = None 127 | self.attributes = None 128 | 129 | def as_json(self): 130 | json_dict = {} 131 | if self.id: 132 | json_dict["tag_id"] = self.id 133 | json_dict["tag_name"] = self.name 134 | json_dict["tag_type"] = self.type 135 | json_dict["asset_ids"] = self.asset_ids 136 | json_dict["tag_config"] = self.config.as_json() if self.config else None 137 | if self.attributes: 138 | json_dict["attributes"] = [attr.as_json() for attr in self.attributes] 139 | return json_dict 140 | -------------------------------------------------------------------------------- /nexpose/nexpose_ticket.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 .xml_utils import get_attribute, get_element, get_content_of, create_element 7 | from future import standard_library 8 | standard_library.install_aliases() 9 | 10 | 11 | class TicketState(object): 12 | OPEN = 'O' 13 | ASSIGNED = 'A' 14 | MODIFIED = 'M' 15 | FIXED = 'X' 16 | PARTIAL = 'P' 17 | REJECTED_FIX = 'R' 18 | PRIORITIZED = 'Z' 19 | NOT_REPRODUCIBLE = 'F' 20 | NOT_ISSUE = 'I' 21 | CLOSED = 'C' 22 | UNKNOWN = 'U' 23 | 24 | 25 | class TicketPriority(object): 26 | LOW = 'low' 27 | MODERATE = 'moderate' 28 | NORMAL = 'normal' 29 | HIGH = 'high' 30 | CRITICAL = 'critical' 31 | 32 | 33 | class TicketEvent(object): 34 | @staticmethod 35 | def CreateFromXML(xml_data): 36 | xml_event = get_element(xml_data, 'Event') 37 | event = TicketEvent() 38 | event.title = xml_event.text 39 | event.author = get_attribute(xml_data, 'author', event.author) 40 | event.created_on = get_attribute(xml_data, 'created-on', event.created_on) # TODO: datetime object! 41 | event.state = get_attribute(xml_event, 'state', TicketState.UNKNOWN) 42 | event.comment = get_content_of(xml_data, 'Comment', event.comment) 43 | return event 44 | 45 | def __init__(self): 46 | self.title = '' 47 | self.author = '' 48 | self.created_on = '' # TODO: datetime object! 49 | self.state = TicketState.UNKNOWN 50 | self.comment = '' 51 | 52 | 53 | class _TicketBase(object): 54 | def InitalizeFromXML(self, xml_data): 55 | self.name = get_attribute(xml_data, 'name', self.name) 56 | self.asset_id = int(get_attribute(xml_data, 'device-id', self.asset_id)) 57 | self.assigned_to = get_attribute(xml_data, 'assigned-to', self.assigned_to) 58 | self.priority = get_attribute(xml_data, 'priority', self.priority) 59 | 60 | def __init__(self): 61 | self.name = '' 62 | self.asset_id = 0 63 | self.assigned_to = '' 64 | self.priority = TicketPriority.NORMAL 65 | 66 | 67 | class _TicketDetailsBase(object): 68 | def _InitializeFromXML(self, xml_data): 69 | self.vulnerabilities_ids = [get_attribute(xml_vulnerability, 'id') for xml_vulnerability in xml_data.findall('Vulnerabilities/Vulnerability')] 70 | 71 | def _VulnerabilitiesAsXML(self): 72 | xml_data = create_element('Vulnerabilities') 73 | for vulnerability_id in self.vulnerabilities_ids: 74 | xml_vulnerability = create_element('Vulnerability', {'id': vulnerability_id}) 75 | xml_data.append(xml_vulnerability) 76 | return xml_data 77 | 78 | def __init__(self): 79 | self.vulnerabilities_ids = [] 80 | 81 | 82 | class NewTicket(_TicketBase, _TicketDetailsBase): 83 | @staticmethod 84 | def Create(): 85 | return NewTicket() 86 | 87 | @staticmethod 88 | def CreatedNamed(name): 89 | ticket = NewTicket.Create() 90 | ticket.name = name 91 | return ticket 92 | 93 | def __init__(self): 94 | _TicketBase.__init__(self) 95 | _TicketDetailsBase.__init__(self) 96 | 97 | def AsXML(self): 98 | attributes = {} 99 | attributes['name'] = self.name 100 | attributes['device-id'] = self.asset_id 101 | attributes['assigned-to'] = self.assigned_to 102 | attributes['priority'] = self.priority 103 | xml_data = create_element("TicketCreate", attributes) 104 | xml_data.append(self._VulnerabilitiesAsXML()) 105 | return xml_data 106 | 107 | 108 | class TicketSummary(_TicketBase): 109 | def InitalizeFromXML(self, xml_data): 110 | _TicketBase.InitalizeFromXML(self, xml_data) 111 | self.id = int(get_attribute(xml_data, 'id', self.id)) 112 | self.author = get_attribute(xml_data, 'author', self.author) 113 | self.created_on = get_attribute(xml_data, 'created-on', self.created_on) # TODO: datetime object! 114 | self.state = get_attribute(xml_data, 'state', self.state) 115 | 116 | @staticmethod 117 | def CreateFromXML(xml_data): 118 | summary = TicketSummary() 119 | summary.InitalizeFromXML(xml_data) 120 | return summary 121 | 122 | def __init__(self): 123 | _TicketBase.__init__(self) 124 | self.id = 0 125 | self.author = '' 126 | self.created_on = '' # TODO: datetime object! 127 | self.state = TicketState.OPEN 128 | 129 | 130 | class TicketDetails(TicketSummary, _TicketDetailsBase): 131 | @staticmethod 132 | def CreateFromXML(xml_data): 133 | config = TicketDetails() 134 | config.InitalizeFromXML(xml_data) 135 | _TicketDetailsBase._InitializeFromXML(config, xml_data) 136 | config.events = [TicketEvent.CreateFromXML(xml_event) for xml_event in xml_data.findall('TicketHistory/Entry')] 137 | return config 138 | 139 | def __init__(self): 140 | TicketSummary.__init__(self) 141 | _TicketDetailsBase.__init__(self) 142 | self.events = [] 143 | -------------------------------------------------------------------------------- /nexpose/nexpose_user.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 UserRoles(object): 11 | global_admin = 'global-admin' 12 | security_manager = 'security-manager' 13 | site_admin = 'siteadmin' 14 | system_admin = 'system-admin' 15 | user = 'user' 16 | custom = 'custom' 17 | 18 | 19 | class UserSummaryStatistics(object): 20 | def __init__(self): 21 | self.site_count = 0 22 | self.assetgroup_count = 0 23 | 24 | 25 | class UserBase(object): 26 | def InitalizeFromXML(self, xml_data, user_fieldname): 27 | self.id = int(get_attribute(xml_data, 'id', self.id)) 28 | self.username = get_attribute(xml_data, user_fieldname, self.username) 29 | self.fullname = get_attribute(xml_data, 'fullname', self.fullname) 30 | self.email = get_attribute(xml_data, 'email', self.email) 31 | 32 | def __init__(self): 33 | self.id = 0 34 | self.username = '' 35 | self.fullname = '' 36 | self.email = '' 37 | 38 | 39 | class UserSummary(UserBase): 40 | @staticmethod 41 | def CreateFromXML(xml_data): 42 | summary = UserSummary() 43 | summary.InitalizeFromXML(xml_data, 'userName') 44 | summary.authenticator_source = get_attribute(xml_data, 'authSource', summary.authenticator_source) 45 | summary.authenticator_module = get_attribute(xml_data, 'authModule', summary.authenticator_module) 46 | summary.is_administrator = get_attribute(xml_data, 'administrator') == '1' 47 | summary.is_disabled = get_attribute(xml_data, 'disabled') == '1' 48 | summary.is_locked = get_attribute(xml_data, 'locked') == '1' 49 | summary.statistics.site_count = get_attribute(xml_data, 'siteCount', summary.statistics.site_count) 50 | summary.statistics.assetgroup_count = get_attribute(xml_data, 'groupCount', summary.statistics.assetgroup_count) 51 | return summary 52 | 53 | def __init__(self): 54 | UserBase.__init__(self) 55 | self.authenticator_source = '' 56 | self.authenticator_module = '' 57 | self.is_administrator = False 58 | self.is_disabled = False 59 | self.is_locked = False 60 | self.statistics = UserSummaryStatistics() 61 | 62 | 63 | class UserConfiguration(UserBase): 64 | @staticmethod 65 | def CreateFromXML(xml_data): 66 | config = UserConfiguration() 67 | config.InitalizeFromXML(xml_data, 'name') 68 | config.authenticator_id = int(get_attribute(xml_data, 'authsrcid', config.authenticator_id)) 69 | config.role_name = get_attribute(xml_data, 'role-name', config.role_name) 70 | config.password = get_attribute(xml_data, 'password', config.password) 71 | config.is_enabled = get_attribute(xml_data, 'enabled') == '1' 72 | config.has_access_to_all_sites = None # Due to a Nexpose bug this information is not returned 73 | config.has_access_to_all_assetgroups = None # Due to a Nexpose bug this information is not returned 74 | config.accessible_sites = None # Due to a Nexpose bug this information is not returned 75 | config.accessible_assetgroups = None # Due to a Nexpose bug this information is not returned 76 | return config 77 | 78 | @staticmethod 79 | def Create(): 80 | config = UserConfiguration() 81 | config.id = -1 82 | config.role_name = UserRoles.user 83 | config.has_access_to_all_sites = True 84 | config.has_access_to_all_assetgroups = True 85 | return config 86 | 87 | @staticmethod 88 | def CreateNamed(username, fullname): 89 | config = UserConfiguration.Create() 90 | config.username = username 91 | config.fullname = fullname 92 | return config 93 | 94 | def __init__(self): 95 | UserBase.__init__(self) 96 | self.authenticator_id = 0 97 | self.role_name = UserRoles.user 98 | self.password = '' 99 | self.is_enabled = True 100 | self.has_access_to_all_sites = False 101 | self.has_access_to_all_assetgroups = False 102 | self.accessible_sites = [] 103 | self.accessible_assetgroups = [] 104 | 105 | def AsXML(self, exclude_id): 106 | attributes = {} 107 | if not exclude_id: 108 | attributes['id'] = self.id 109 | attributes['name'] = self.username 110 | attributes['password'] = self.password 111 | attributes['enabled'] = '1' if self.is_enabled else '0' 112 | attributes['fullname'] = self.fullname 113 | attributes['email'] = self.email 114 | attributes['role-name'] = self.role_name 115 | attributes['authsrcid'] = self.authenticator_id if self.authenticator_id else 0 116 | attributes['allSites'] = True if self.has_access_to_all_sites else False 117 | attributes['allGroups'] = True if self.has_access_to_all_assetgroups else False 118 | xml_data = create_element('UserConfig', attributes) 119 | 120 | if not self.has_access_to_all_sites: 121 | xml_sites = create_element('Sites') # We are just nice, in practice Nexpose doesn't care for this subelement 122 | for site_id in self.accessible_sites: 123 | xml_site = create_element('site', {'id': site_id}) 124 | xml_sites.append(xml_site) 125 | xml_data.append(xml_sites) 126 | 127 | if not self.has_access_to_all_assetgroups: 128 | xml_assetgroups = create_element('Groups') # We are just nice, in practice Nexpose doesn't care for this subelement 129 | for site_id in self.accessible_assetgroups: 130 | xml_assetgroup = create_element('group', {'id': site_id}) 131 | xml_assetgroups.append(xml_assetgroup) 132 | xml_data.append(xml_assetgroups) 133 | 134 | return xml_data 135 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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_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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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('' + s + '' 63 | return from_large_string(s).getchildren()[0] 64 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | lxml>=3.6.0 2 | future>=0.16.0 -------------------------------------------------------------------------------- /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 -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [bdist_wheel] 2 | # Indicate that this library can be packaged universally for python 2 and 3. 3 | universal=1 -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /test_fixtures/ReportHistoryResponse.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /test_fixtures/ReportListingResponse.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /test_fixtures/UserAuthenticatorListingResponse.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /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/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} -------------------------------------------------------------------------------- /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 | } -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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_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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 = '