diff --git a/docs/api/ad/license.rst b/docs/api/ad/license.rst new file mode 100644 index 000000000..f0ac2ed10 --- /dev/null +++ b/docs/api/ad/license.rst @@ -0,0 +1 @@ +.. automodule:: tenable.ad.license.api diff --git a/tenable/ad/__init__.py b/tenable/ad/__init__.py index b90e7250c..3edddf3a7 100644 --- a/tenable/ad/__init__.py +++ b/tenable/ad/__init__.py @@ -24,6 +24,7 @@ event infrastructure ldap_configuration + license lockout_policy preference profiles diff --git a/tenable/ad/base/schema.py b/tenable/ad/base/schema.py index b2d3a354c..0c22badab 100644 --- a/tenable/ad/base/schema.py +++ b/tenable/ad/base/schema.py @@ -76,6 +76,7 @@ def convert_snake_to_camel(self, data, **kwargs) -> Dict: class BoolInt(fields.Boolean): '''Schema to return an integer value for given boolean value''' + def _serialize(self, value, attr, obj, **kwargs) -> int: return int(value) if value else 0 diff --git a/tenable/ad/license/__init__.py b/tenable/ad/license/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tenable/ad/license/api.py b/tenable/ad/license/api.py new file mode 100644 index 000000000..c89788601 --- /dev/null +++ b/tenable/ad/license/api.py @@ -0,0 +1,53 @@ +''' +License +============= + +Methods described in this section relate to the license API. +These methods can be accessed at ``TenableAD.license``. + +.. rst-class:: hide-signature +.. autoclass:: LicenseAPI + :members: +''' +from typing import Dict +from tenable.ad.license.schema import LicenseSchema +from tenable.base.endpoint import APIEndpoint + + +class LicenseAPI(APIEndpoint): + _path = 'license' + _schema = LicenseSchema() + + def details(self) -> Dict: + ''' + Get license singleton + + Returns: + dict: + The license object + + Examples: + >>> tad.license.details() + ''' + return self._schema.load(self._get()) + + def create(self, license: str) -> Dict: + ''' + Create new license singleton + + Args: + license (str): + The license string object. + + Return: + The license object + + Example: + >>> tad.license.create( + ... license='license' + ... ) + ''' + payload = self._schema.dump(self._schema.load({ + 'license': license + })) + return self._schema.load(self._post(json=payload)) diff --git a/tenable/ad/license/schema.py b/tenable/ad/license/schema.py new file mode 100644 index 000000000..4e7fca9ae --- /dev/null +++ b/tenable/ad/license/schema.py @@ -0,0 +1,18 @@ +from marshmallow import fields +from tenable.ad.base.schema import CamelCaseSchema, last_word_uppercase + + +class LicenseSchema(CamelCaseSchema): + class Meta: + case_convertors = { + 'expiration_date_utc': last_word_uppercase + } + + customer_name = fields.Str() + max_active_user_count = fields.Int() + current_active_user_count = fields.Int() + expiration_date_utc = fields.DateTime() + in_app_eula = fields.Bool() + features = fields.List(fields.Str()) + license = fields.Str() + license_type = fields.Str(data_key='type') diff --git a/tenable/ad/session.py b/tenable/ad/session.py index 923167287..932eef0c5 100644 --- a/tenable/ad/session.py +++ b/tenable/ad/session.py @@ -18,6 +18,7 @@ from .event.api import EventAPI from .infrastructure.api import InfrastructureAPI from .ldap_configuration.api import LDAPConfigurationAPI +from .license.api import LicenseAPI from .lockout_policy.api import LockoutPolicyAPI from .preference.api import PreferenceAPI from .profiles.api import ProfilesAPI @@ -151,6 +152,14 @@ def ldap_configuration(self): ''' return LDAPConfigurationAPI(self) + @property + def license(self): + ''' + The interface object for the + :doc:`Tenable.ad License APIs `. + ''' + return LicenseAPI(self) + @property def lockout_policy(self): ''' diff --git a/tests/ad/license/test_license_api.py b/tests/ad/license/test_license_api.py new file mode 100644 index 000000000..485d075d7 --- /dev/null +++ b/tests/ad/license/test_license_api.py @@ -0,0 +1,56 @@ +import datetime +import responses + +from tests.ad.conftest import RE_BASE + + +@responses.activate +def test_license_details(api): + responses.add(responses.GET, + f'{RE_BASE}/license', + json={ + 'customerName': 'pytenable', + 'maxActiveUserCount': 100, + 'currentActiveUserCount': 0, + 'expirationDateUTC': '2021-11-17T13:44:24.259Z', + 'inAppEula': False, + 'features': ['something'], + 'type': 'license type' + } + ) + resp = api.license.details() + assert isinstance(resp, dict) + assert resp['customer_name'] == 'pytenable' + assert resp['max_active_user_count'] == 100 + assert resp['current_active_user_count'] == 0 + assert resp['expiration_date_utc'] == datetime.datetime( + 2021, 11, 17, 13, 44, 24, 259000, tzinfo=datetime.timezone.utc) + assert resp['in_app_eula'] == False + assert resp['features'][0] == 'something' + assert resp['license_type'] == 'license type' + + +@responses.activate +def test_license_create(api): + responses.add(responses.POST, + f'{RE_BASE}/license', + json={ + 'customerName': 'pytenable', + 'maxActiveUserCount': 100, + 'currentActiveUserCount': 0, + 'expirationDateUTC': '2021-11-17T13:44:24.259Z', + 'inAppEula': False, + 'features': ['something'], + 'type': 'license type' + } + ) + resp = api.license.create(license='license') + assert isinstance(resp, dict) + assert resp['customer_name'] == 'pytenable' + assert resp['max_active_user_count'] == 100 + assert resp['current_active_user_count'] == 0 + assert resp['expiration_date_utc'] == datetime.datetime( + 2021, 11, 17, 13, 44, 24, 259000, tzinfo=datetime.timezone.utc) + assert resp['in_app_eula'] == False + assert resp['features'][0] == 'something' + assert resp['license_type'] == 'license type' diff --git a/tests/ad/license/test_license_schema.py b/tests/ad/license/test_license_schema.py new file mode 100644 index 000000000..07f93f56e --- /dev/null +++ b/tests/ad/license/test_license_schema.py @@ -0,0 +1,51 @@ +''' +Testing the license schemas +''' +import datetime +import pytest +from tenable.ad.license.schema import LicenseSchema + + +@pytest.fixture() +def license_schema_request_payload(): + return { + 'license': 'license' + } + + +def test_license_schema_request_payload(license_schema_request_payload): + ''' + test license create payload input to schema + ''' + schema = LicenseSchema() + req = schema.dump(schema.load(license_schema_request_payload)) + assert req['license'] == 'license' + + +@pytest.fixture() +def license_schema_response(): + return { + 'customerName': 'pytenable', + 'maxActiveUserCount': 100, + 'currentActiveUserCount': 0, + 'expirationDateUTC': '2021-11-17T13:44:24.259Z', + 'inAppEula': False, + 'features': ['something'], + 'type': 'license type' + } + + +def test_license_schema_response(license_schema_response): + ''' + test license get response to schema + ''' + schema = LicenseSchema() + req = schema.load(license_schema_response) + assert req['customer_name'] == 'pytenable' + assert req['max_active_user_count'] == 100 + assert req['current_active_user_count'] == 0 + assert req['expiration_date_utc'] == datetime.datetime( + 2021, 11, 17, 13, 44, 24, 259000, tzinfo=datetime.timezone.utc) + assert req['in_app_eula'] is False + assert req['features'][0] == 'something' + assert req['license_type'] == 'license type'