Source code for tenable.base.platform
'''
Base Platform
=============
The APIPlatform class is the base class that all platform packages will inherit
from. Throughout pyTenable v1, packages will be transitioning to using this
base class over the original APISession class.
.. autoclass:: APIPlatform
:members:
:inherited-members:
'''
from restfly import APISession as Base
from tenable.utils import url_validator
from tenable.version import version
import os, warnings
[docs]class APIPlatform(Base):
'''
Base class for all API Platform packages. This class handles all of the
base connection logic.
Args:
adaptor (Object, optional):
A Requests Session adaptor to bind to the session object.
backoff (float, optional):
If a 429 response is returned, how much do we want to backoff
if the response didn't send a Retry-After header. If left
unspecified, the default is 1 second.
box (bool, optional):
Should responses be passed through Box? If left unspecified, the
defaut is ``True``.
box_attrs (dict, optional):
Any additional attributes to pass to the Box constructor for this
session? For a list of attributes that can be sent, please refer
to the `Box documentation <https://github.com/cdgriffith/Box/wiki>`_
for more information.
build (str, optional):
The build number to put into the User-Agent string.
product (str, optional):
The product name to put into the User-Agent string.
proxies (dict, optional):
A dictionary detailing what proxy should be used for what
transport protocol. This value will be passed to the session
object after it has been either attached or created. For
details on the structure of this dictionary, consult the
:requests:`proxies <user/advanced/#proxies>` section of the
Requests documentation.
retries (int, optional):
The number of retries to make before failing a request. The
default is 5.
session (requests.Session, optional):
Provide a pre-built session instead of creating a requests
session at instantiation.
squash_camel (bool, optional):
Should the responses have CamelCase responses be squashed into
snake_case? If left unspecified, the default value is ``False``.
Note that this will only work when Box is enabled.
ssl_verify (bool, optional):
If SSL Verification needs to be disabled (for example when using
a self-signed certificate), then this parameter should be set to
``False`` to disable verification and mask the Certificate
warnings.
url (str, optional):
The base URL that the paths will be appended onto.
vendor (str, optional):
The vendor name to put into the User-Agent string.
'''
_lib_name = 'pyTenable'
_lib_version = version
_box = True
_backoff = 1
_retries = 5
_env_base = ''
_port = 443
_scheme = 'https'
_address = None
_auth = (None, None)
_auth_mech = None
_box_attrs = dict()
def __init__(self, **kwargs):
# Constructing the URL from the various parameters.
self._url = '{}://{}:{}'.format(
kwargs.get('scheme', self._scheme),
kwargs.get('address', os.getenv(
'{}_ADDRESS'.format(self._env_base), self._address)),
kwargs.get('port', os.getenv(
'{}_PORT'.format(self._env_base), self._port)),
)
# if the constructed URL isn't valid, then we will throw a TypeError
# to inform the caller that something isn't right here.
if not url_validator(self._url):
raise TypeError('{url} is not a valid URL'.format(url=self._url))
# CamelCase squashing is an optional parameter thanks to Box. if the
# user has requested it, then we should add the appropriate parameter to
# the box_attrs.
if kwargs.get('squash_camel'):
box_attrs = kwargs.get('box_attrs', {})
box_attrs['camel_killer_box'] = bool(kwargs.pop('squash_camel'))
kwargs['box_attrs'] = box_attrs
# Call the RESTfly constructor
super(APIPlatform, self).__init__(**kwargs)
def _authenticate(self, **kwargs):
'''
This method handles authentication for both API Keys and for session
authentication.
'''
# These functions determine how authentication is to be handled within
# for both session authentication and key-based authentication. They
# have been broken down into these functions for easy overloading.
def key_auth():
'''
Default API Key Auth Behavior
'''
self._session.headers.update({
'X-APIKeys': 'accessKey={}; secretKey={}'.format(*keys)
})
self._auth_mech = 'keys'
def s_auth():
'''
Default Session auth behavior
'''
self.post('session', json={
'username': self._auth[0],
'password': self._auth[1]
})
self._auth_mech = 'user'
# Here we are grafting the authentication functions into the keyword
# arguments for later usage. If a function is provided in the keywords
# under the key names below, we will use those instead. This should
# essentially allow for the authentication logic to be overridden with
# minimal effort.
kwargs['key_auth_func'] = kwargs.get('key_auth_func', key_auth)
kwargs['session_auth_func'] = kwargs.get('session_auth_func', s_auth)
# Pull the API keys from the keyword arguments passed to the constructor
# and build the keys tuple. As API Keys will be injected directly into
# the session, there is no need to store these.
keys = (
kwargs.get('access_key', os.getenv(
'{}_ACCESS_KEY'.format(self._env_base))),
kwargs.get('secret_key', os.getenv(
'{}_SECRET_KEY'.format(self._env_base)))
)
# The session authentication tuple. We will be storing these as its
# possible for the session to timeout on the user. This would require
# re-authentication.
self._auth = (
kwargs.get('username', os.getenv(
'{}_USERNAME'.format(self._env_base))),
kwargs.get('password', os.getenv(
'{}_PASSWORD'.format(self._env_base)))
)
# Run the desired authentication function. As API keys are generally
# preferred over session authentication, we will first check to see that
# keys have been
if None not in keys:
kwargs['key_auth_func']()
elif None not in self._auth:
kwargs['session_auth_func']()
else:
warnings.warn('Starting an unauthenticated session')
self._log.warning('Starting an unauthenticated session.')
def _deauthenticate(self):
'''
This method handles de-authentication. This is only necessary for
session-based authentication.
'''
if self._auth_mech == 'user':
self.delete('session')
self._auth = (None, None)
self._auth_mech = None