From 8486944eaa2372c23fa77cc97145968c5a196078 Mon Sep 17 00:00:00 2001 From: Matthew Jones Date: Tue, 11 Jul 2017 12:01:24 -0400 Subject: [PATCH 1/4] Switch out existing obfuscated license with external module This creates a new fallback license module called StubLicense that will be used in the event that the tower_license module is not installed. All existing license mechanisms are routed through the get_licenser() util method --- Makefile | 3 ++ awx/api/views.py | 4 +- awx/conf/license.py | 9 ++-- awx/main/access.py | 7 ++-- .../management/commands/inventory_import.py | 8 ++-- awx/main/tasks.py | 7 ++-- awx/main/tests/base.py | 11 ++--- awx/main/tests/conftest.py | 6 +-- .../tests/functional/core/test_licenses.py | 41 +++++++++++-------- awx/main/tests/unit/test_access.py | 2 +- awx/main/tests/unit/test_tasks.py | 9 ++-- awx/main/utils/common.py | 19 ++++++++- tools/docker-compose/Dockerfile | 2 + 13 files changed, 83 insertions(+), 45 deletions(-) diff --git a/Makefile b/Makefile index 13645bee09..aefafe2dc8 100644 --- a/Makefile +++ b/Makefile @@ -971,6 +971,9 @@ docker-compose-test: docker-auth docker-compose-build: tower-devel-build tower-isolated-build tower-devel-build: + rm -rf tools/docker-compose/tower-license + git clone git@github.com:ansible/tower-license.git tools/docker-compose/tower-license + cd tools/docker-compose/tower-license && $(PYTHON) setup.py sdist docker build -t ansible/tower_devel -f tools/docker-compose/Dockerfile . docker tag ansible/tower_devel gcr.io/ansible-tower-engineering/tower_devel:$(COMPOSE_TAG) #docker push gcr.io/ansible-tower-engineering/tower_devel:$(COMPOSE_TAG) diff --git a/awx/api/views.py b/awx/api/views.py index 3554315174..b01742ed09 100644 --- a/awx/api/views.py +++ b/awx/api/views.py @@ -324,9 +324,9 @@ class ApiV1ConfigView(APIView): extra=dict(actor=request.user.username)) return Response({"error": _("Invalid JSON")}, status=status.HTTP_400_BAD_REQUEST) try: - from awx.main.task_engine import TaskEnhancer + from awx.main.utils.common import get_licenser license_data = json.loads(data_actual) - license_data_validated = TaskEnhancer(**license_data).validate_enhancements() + license_data_validated = get_licenser(**license_data).validate() except Exception: logger.warning(smart_text(u"Invalid license submitted."), extra=dict(actor=request.user.username)) diff --git a/awx/conf/license.py b/awx/conf/license.py index 0df047caaa..d6772eb4e8 100644 --- a/awx/conf/license.py +++ b/awx/conf/license.py @@ -8,7 +8,7 @@ from django.utils.translation import ugettext_lazy as _ from rest_framework.exceptions import APIException # Tower -from awx.main.task_engine import TaskEnhancer +from awx.main.utils.common import get_licenser __all__ = ['LicenseForbids', 'get_license', 'get_licensed_features', 'feature_enabled', 'feature_exists'] @@ -20,7 +20,7 @@ class LicenseForbids(APIException): def _get_validated_license_data(): - return TaskEnhancer().validate_enhancements() + return get_licenser().validate() def get_license(show_key=False): @@ -42,7 +42,10 @@ def get_licensed_features(): def feature_enabled(name): """Return True if the requested feature is enabled, False otherwise.""" - return _get_validated_license_data().get('features', {}).get(name, False) + validated_license_data = _get_validated_license_data() + if validated_license_data['license_type'] == 'open': + return True + return validated_license_data.get('features', {}).get(name, False) def feature_exists(name): diff --git a/awx/main/access.py b/awx/main/access.py index 417421e5f4..a3b11c26e3 100644 --- a/awx/main/access.py +++ b/awx/main/access.py @@ -21,8 +21,6 @@ from awx.main.utils import * # noqa from awx.main.models import * # noqa from awx.main.models.unified_jobs import ACTIVE_STATES from awx.main.models.mixins import ResourceMixin -from awx.main.task_engine import TaskEnhancer -from awx.conf.license import LicenseForbids __all__ = ['get_user_queryset', 'check_user_access', 'check_user_access_with_errors', 'user_accessible_objects', 'consumer_access', @@ -255,7 +253,10 @@ class BaseAccess(object): return True # User has access to both, permission check passed def check_license(self, add_host_name=None, feature=None, check_expiration=True): - validation_info = TaskEnhancer().validate_enhancements() + validation_info = get_licenser().validate() + if validation_info['license_type'] == 'open': + return + if ('test' in sys.argv or 'py.test' in sys.argv[0] or 'jenkins' in sys.argv) and not os.environ.get('SKIP_LICENSE_FIXUP_FOR_TEST', ''): validation_info['free_instances'] = 99999999 validation_info['time_remaining'] = 99999999 diff --git a/awx/main/management/commands/inventory_import.py b/awx/main/management/commands/inventory_import.py index 691fbaca78..abbb91f406 100644 --- a/awx/main/management/commands/inventory_import.py +++ b/awx/main/management/commands/inventory_import.py @@ -22,12 +22,12 @@ from django.utils.encoding import smart_text # AWX from awx.main.models import * # noqa -from awx.main.task_engine import TaskEnhancer from awx.main.utils import ( ignore_inventory_computed_fields, check_proot_installed, wrap_args_with_proot, - build_proot_temp_dir + build_proot_temp_dir, + get_licenser ) from awx.main.utils.mem_inventory import MemInventory, dict_to_mem_data from awx.main.signals import disable_activity_stream @@ -845,10 +845,12 @@ class Command(NoArgsCommand): self._create_update_group_hosts() def check_license(self): - license_info = TaskEnhancer().validate_enhancements() + license_info = get_licenser().validate() if license_info.get('license_key', 'UNLICENSED') == 'UNLICENSED': logger.error(LICENSE_NON_EXISTANT_MESSAGE) raise CommandError('No license found!') + elif license_info['license_type'] == 'open': + return available_instances = license_info.get('available_instances', 0) free_instances = license_info.get('free_instances', 0) time_remaining = license_info.get('time_remaining', 0) diff --git a/awx/main/tasks.py b/awx/main/tasks.py index a20db257c3..815cbd4ae1 100644 --- a/awx/main/tasks.py +++ b/awx/main/tasks.py @@ -47,10 +47,9 @@ from awx.main.constants import CLOUD_PROVIDERS from awx.main.models import * # noqa from awx.main.models.unified_jobs import ACTIVE_STATES from awx.main.queue import CallbackQueueDispatcher -from awx.main.task_engine import TaskEnhancer from awx.main.isolated import run, isolated_manager from awx.main.utils import (get_ansible_version, get_ssh_version, decrypt_field, update_scm_url, - check_proot_installed, build_proot_temp_dir, + check_proot_installed, build_proot_temp_dir, get_licenser, wrap_args_with_proot, get_system_task_capacity, OutputEventFilter, parse_yaml_or_json, ignore_inventory_computed_fields, ignore_inventory_group_removal) from awx.main.utils.reload import restart_local_services, stop_local_services @@ -140,8 +139,8 @@ def run_administrative_checks(self): logger.warn("Running administrative checks.") if not settings.TOWER_ADMIN_ALERTS: return - validation_info = TaskEnhancer().validate_enhancements() - if validation_info.get('instance_count', 0) < 1: + validation_info = get_licenser().validate() + if validation_info['license_type'] != 'open' and validation_info.get('instance_count', 0) < 1: return used_percentage = float(validation_info.get('current_instances', 0)) / float(validation_info.get('instance_count', 100)) tower_admin_emails = User.objects.filter(is_superuser=True).values_list('email', flat=True) diff --git a/awx/main/tests/base.py b/awx/main/tests/base.py index ca5f8237d2..e2d52ce808 100644 --- a/awx/main/tests/base.py +++ b/awx/main/tests/base.py @@ -29,11 +29,12 @@ from django.utils.encoding import force_text # AWX from awx.main.models import * # noqa -from awx.main.task_engine import TaskEnhancer from awx.main.utils import get_ansible_version from awx.sso.backends import LDAPSettings from awx.main.tests.URI import URI # noqa +from tower_license import TowerLicense + TEST_PLAYBOOK = '''- hosts: mygroup gather_facts: false tasks: @@ -179,7 +180,7 @@ class BaseTestMixin(MockCommonlySlowTestMixin): return __name__ + '-generated-' + string + rnd_str def create_test_license_file(self, instance_count=10000, license_date=int(time.time() + 3600), features={}): - settings.LICENSE = TaskEnhancer( + settings.LICENSE = TowerLicense( company_name='AWX', contact_name='AWX Admin', contact_email='awx@example.com', @@ -187,17 +188,17 @@ class BaseTestMixin(MockCommonlySlowTestMixin): instance_count=instance_count, license_type='enterprise', features=features, - ).enhance() + ).generate() def create_basic_license_file(self, instance_count=100, license_date=int(time.time() + 3600)): - settings.LICENSE = TaskEnhancer( + settings.LICENSE = TowerLicense( company_name='AWX', contact_name='AWX Admin', contact_email='awx@example.com', license_date=license_date, instance_count=instance_count, license_type='basic', - ).enhance() + ).generate() def create_expired_license_file(self, instance_count=1000, grace_period=False): license_date = time.time() - 1 diff --git a/awx/main/tests/conftest.py b/awx/main/tests/conftest.py index 6118bf83bd..44ffed5ca2 100644 --- a/awx/main/tests/conftest.py +++ b/awx/main/tests/conftest.py @@ -101,12 +101,12 @@ def job_template_with_survey_passwords_unit(job_template_with_survey_passwords_f @pytest.fixture def enterprise_license(): - from awx.main.task_engine import TaskEnhancer - return TaskEnhancer( + from tower_license import TowerLicense + return TowerLicense( company_name='AWX', contact_name='AWX Admin', contact_email='awx@example.com', license_date=int(time.time() + 3600), instance_count=10000, license_type='enterprise', - ).enhance() + ).generate() diff --git a/awx/main/tests/functional/core/test_licenses.py b/awx/main/tests/functional/core/test_licenses.py index 7432dbbdcd..948bc64683 100644 --- a/awx/main/tests/functional/core/test_licenses.py +++ b/awx/main/tests/functional/core/test_licenses.py @@ -6,19 +6,20 @@ import pytest from datetime import datetime from awx.main.models import Host -from awx.main.task_engine import TaskEnhancer +from awx.main.utils import get_licenser, StubLicense +from tower_license import TowerLicense @pytest.mark.django_db def test_license_writer(inventory, admin): - task_enhancer = TaskEnhancer( + license_actual = TowerLicense( company_name='acmecorp', contact_name='Michael DeHaan', contact_email='michael@ansibleworks.com', license_date=25000, # seconds since epoch instance_count=500) - data = task_enhancer.enhance() + data = license_actual.generate() Host.objects.bulk_create( [ @@ -38,7 +39,7 @@ def test_license_writer(inventory, admin): assert data['license_date'] == 25000 assert data['license_key'] == "11bae31f31c6a6cdcb483a278cdbe98bd8ac5761acd7163a50090b0f098b3a13" - vdata = task_enhancer.validate_enhancements() + vdata = license_actual.validate() assert vdata['available_instances'] == 500 assert vdata['current_instances'] == 12 @@ -52,43 +53,51 @@ def test_license_writer(inventory, admin): assert vdata['subscription_name'] +def test_stub_license(): + license_actual = StubLicense() + assert license_actual['license_key'] == 'OPEN' + assert license_actual['valid_key'] + assert license_actual['compliant'] + assert license_actual['license_type'] == 'open' + + @pytest.mark.django_db def test_expired_licenses(): - task_enhancer = TaskEnhancer( + license_actual = TowerLicense( company_name='Tower', contact_name='Tower Admin', contact_email='tower@ansible.com', license_date=int(time.time() - 3600), instance_count=100, trial=True) - task_enhancer.enhance() - vdata = task_enhancer.validate_enhancements() + license_actual.generate() + vdata = license_actual.validate() assert vdata['compliant'] is False assert vdata['grace_period_remaining'] < 0 - task_enhancer = TaskEnhancer( + license_actual = TowerLicense( company_name='Tower', contact_name='Tower Admin', contact_email='tower@ansible.com', license_date=int(time.time() - 2592001), instance_count=100, trial=False) - task_enhancer.enhance() - vdata = task_enhancer.validate_enhancements() + license_actual.generate() + vdata = license_actual.validate() assert vdata['compliant'] is False assert vdata['grace_period_remaining'] < 0 - task_enhancer = TaskEnhancer( + license_actual = TowerLicense( company_name='Tower', contact_name='Tower Admin', contact_email='tower@ansible.com', license_date=int(time.time() - 3600), instance_count=100, trial=False) - task_enhancer.enhance() - vdata = task_enhancer.validate_enhancements() + license_actual.generate() + vdata = license_actual.validate() assert vdata['compliant'] is False assert vdata['grace_period_remaining'] > 0 @@ -96,9 +105,9 @@ def test_expired_licenses(): @pytest.mark.django_db def test_cloudforms_license(mocker): - with mocker.patch('awx.main.task_engine.TaskEnhancer._check_cloudforms_subscription', return_value=True): - task_enhancer = TaskEnhancer() - vdata = task_enhancer.validate_enhancements() + with mocker.patch('tower_license.TowerLicense._check_cloudforms_subscription', return_value=True): + license_actual = TowerLicense() + vdata = license_actual.validate() assert vdata['compliant'] is True assert vdata['subscription_name'] == "Red Hat CloudForms License" assert vdata['available_instances'] == 9999999 diff --git a/awx/main/tests/unit/test_access.py b/awx/main/tests/unit/test_access.py index 5d12cde0fb..09154c3f1e 100644 --- a/awx/main/tests/unit/test_access.py +++ b/awx/main/tests/unit/test_access.py @@ -268,7 +268,7 @@ class TestCheckLicense: def exists(self): return host_exists - mocker.patch('awx.main.tasks.TaskEnhancer.validate_enhancements', return_value={'free_instances': free_instances, 'available_instances': available_instances, 'date_warning': True}) + mocker.patch('tower_license.TowerLicense.validate', return_value={'free_instances': free_instances, 'available_instances': available_instances, 'date_warning': True}) mock_filter = MockFilter() mocker.patch('awx.main.models.Host.objects.filter', return_value=mock_filter) diff --git a/awx/main/tests/unit/test_tasks.py b/awx/main/tests/unit/test_tasks.py index ff655b4902..f649a7d2b4 100644 --- a/awx/main/tests/unit/test_tasks.py +++ b/awx/main/tests/unit/test_tasks.py @@ -29,9 +29,10 @@ from awx.main.models import ( ) from awx.main import tasks -from awx.main.task_engine import TaskEnhancer from awx.main.utils import encrypt_field +from tower_license import TowerLicense + @contextmanager def apply_patches(_patches): @@ -83,9 +84,9 @@ def test_run_admin_checks_usage(mocker, current_instances, call_count): patches = list() patches.append(mocker.patch('awx.main.tasks.User')) - mock_te = mocker.Mock(spec=TaskEnhancer) - mock_te.validate_enhancements.return_value = {'instance_count': 100, 'current_instances': current_instances, 'date_warning': True} - patches.append(mocker.patch('awx.main.tasks.TaskEnhancer', return_value=mock_te)) + mock_te = mocker.Mock(spec=TowerLicense) + mock_te.validate.return_value = {'instance_count': 100, 'current_instances': current_instances, 'date_warning': True} + patches.append(mocker.patch('tower_license.TowerLicense', return_value=mock_te)) mock_sm = mocker.Mock() patches.append(mocker.patch('awx.main.tasks.send_mail', wraps=mock_sm)) diff --git a/awx/main/utils/common.py b/awx/main/utils/common.py index 473683a668..5447f32f0f 100644 --- a/awx/main/utils/common.py +++ b/awx/main/utils/common.py @@ -33,7 +33,7 @@ from django.apps import apps logger = logging.getLogger('awx.main.utils') __all__ = ['get_object_or_400', 'get_object_or_403', 'camelcase_to_underscore', 'memoize', - 'get_ansible_version', 'get_ssh_version', 'get_awx_version', 'update_scm_url', + 'get_ansible_version', 'get_ssh_version', 'get_licenser', 'get_awx_version', 'update_scm_url', 'get_type_for_model', 'get_model_for_type', 'copy_model_by_class', 'copy_m2m_relationships' ,'cache_list_capabilities', 'to_python_boolean', 'ignore_inventory_computed_fields', 'ignore_inventory_group_removal', @@ -161,6 +161,23 @@ def get_awx_version(): return __version__ +class StubLicense(object): + + def validate(self): + return dict(license_key='OPEN', + valid_key=True, + compliant=True, + license_type='open') + + +def get_licenser(*args, **kwargs): + try: + from tower_license import TowerLicense + return TowerLicense(*args, **kwargs) + except ImportError: + return StubLicense(*args, **kwargs) + + def update_scm_url(scm_type, url, username=True, password=True, check_special_cases=True, scp_format=False): ''' diff --git a/tools/docker-compose/Dockerfile b/tools/docker-compose/Dockerfile index 93712f3dba..d55ecd9a46 100644 --- a/tools/docker-compose/Dockerfile +++ b/tools/docker-compose/Dockerfile @@ -2,6 +2,7 @@ FROM centos:7 ADD Makefile /tmp/Makefile RUN mkdir /tmp/requirements +COPY tools/docker-compose/tower-license/dist/tower-license-0.1.tar.gz /tmp/tower-license.tar.gz ADD requirements/requirements.txt \ requirements/requirements_git.txt \ requirements/requirements_ansible.txt \ @@ -33,6 +34,7 @@ RUN openssl req -nodes -newkey rsa:2048 -keyout /etc/nginx/nginx.key -out /etc/n RUN openssl x509 -req -days 365 -in /etc/nginx/nginx.csr -signkey /etc/nginx/nginx.key -out /etc/nginx/nginx.crt WORKDIR /tmp RUN SWIG_FEATURES="-cpperraswarn -includeall -D__`uname -m`__ -I/usr/include/openssl" VENV_BASE="/venv" make requirements_dev +RUN /venv/tower/bin/pip install /tmp/tower-license.tar.gz RUN localedef -c -i en_US -f UTF-8 en_US.UTF-8 ENV LANG en_US.UTF-8 ENV LANGUAGE en_US:en From 96a9d048b8e9a704335a9314a86db54029284fab Mon Sep 17 00:00:00 2001 From: Matthew Jones Date: Wed, 12 Jul 2017 16:20:35 -0400 Subject: [PATCH 2/4] Remove any references to internal licensing utilities --- Makefile | 3 - awx/main/tests/base.py | 30 ----- awx/main/tests/conftest.py | 13 --- .../tests/functional/api/test_settings.py | 36 +----- .../tests/functional/core/test_licenses.py | 104 +----------------- awx/main/tests/unit/test_access.py | 34 ------ awx/main/tests/unit/test_tasks.py | 22 ---- tools/docker-compose/Dockerfile | 2 - 8 files changed, 5 insertions(+), 239 deletions(-) diff --git a/Makefile b/Makefile index aefafe2dc8..13645bee09 100644 --- a/Makefile +++ b/Makefile @@ -971,9 +971,6 @@ docker-compose-test: docker-auth docker-compose-build: tower-devel-build tower-isolated-build tower-devel-build: - rm -rf tools/docker-compose/tower-license - git clone git@github.com:ansible/tower-license.git tools/docker-compose/tower-license - cd tools/docker-compose/tower-license && $(PYTHON) setup.py sdist docker build -t ansible/tower_devel -f tools/docker-compose/Dockerfile . docker tag ansible/tower_devel gcr.io/ansible-tower-engineering/tower_devel:$(COMPOSE_TAG) #docker push gcr.io/ansible-tower-engineering/tower_devel:$(COMPOSE_TAG) diff --git a/awx/main/tests/base.py b/awx/main/tests/base.py index e2d52ce808..4cceb8bef9 100644 --- a/awx/main/tests/base.py +++ b/awx/main/tests/base.py @@ -33,8 +33,6 @@ from awx.main.utils import get_ansible_version from awx.sso.backends import LDAPSettings from awx.main.tests.URI import URI # noqa -from tower_license import TowerLicense - TEST_PLAYBOOK = '''- hosts: mygroup gather_facts: false tasks: @@ -179,34 +177,6 @@ class BaseTestMixin(MockCommonlySlowTestMixin): rnd_str = '____' + str(random.randint(1, 9999999)) return __name__ + '-generated-' + string + rnd_str - def create_test_license_file(self, instance_count=10000, license_date=int(time.time() + 3600), features={}): - settings.LICENSE = TowerLicense( - company_name='AWX', - contact_name='AWX Admin', - contact_email='awx@example.com', - license_date=license_date, - instance_count=instance_count, - license_type='enterprise', - features=features, - ).generate() - - def create_basic_license_file(self, instance_count=100, license_date=int(time.time() + 3600)): - settings.LICENSE = TowerLicense( - company_name='AWX', - contact_name='AWX Admin', - contact_email='awx@example.com', - license_date=license_date, - instance_count=instance_count, - license_type='basic', - ).generate() - - def create_expired_license_file(self, instance_count=1000, grace_period=False): - license_date = time.time() - 1 - if not grace_period: - license_date -= 2592000 - self.create_test_license_file(instance_count, license_date) - os.environ['SKIP_LICENSE_FIXUP_FOR_TEST'] = '1' - def assertElapsedLessThan(self, seconds): elapsed = time.time() - self._start_time self.assertTrue(elapsed < seconds, 'elapsed time of %0.3fs is greater than %0.3fs' % (elapsed, seconds)) diff --git a/awx/main/tests/conftest.py b/awx/main/tests/conftest.py index 44ffed5ca2..a8e504be22 100644 --- a/awx/main/tests/conftest.py +++ b/awx/main/tests/conftest.py @@ -97,16 +97,3 @@ def get_ssh_version(mocker): @pytest.fixture def job_template_with_survey_passwords_unit(job_template_with_survey_passwords_factory): return job_template_with_survey_passwords_factory(persisted=False) - - -@pytest.fixture -def enterprise_license(): - from tower_license import TowerLicense - return TowerLicense( - company_name='AWX', - contact_name='AWX Admin', - contact_email='awx@example.com', - license_date=int(time.time() + 3600), - instance_count=10000, - license_type='enterprise', - ).generate() diff --git a/awx/main/tests/functional/api/test_settings.py b/awx/main/tests/functional/api/test_settings.py index 0eb21711fc..f67d9f0fe7 100644 --- a/awx/main/tests/functional/api/test_settings.py +++ b/awx/main/tests/functional/api/test_settings.py @@ -27,26 +27,6 @@ def mock_no_license_file(mocker): return None -@pytest.mark.django_db -def test_license_cannot_be_removed_via_system_settings(mock_no_license_file, get, put, patch, delete, admin, enterprise_license): - url = reverse('api:setting_singleton_detail', kwargs={'category_slug': 'system'}) - response = get(url, user=admin, expect=200) - assert not response.data['LICENSE'] - Setting.objects.create(key='TOWER_URL_BASE', value='https://towerhost') - Setting.objects.create(key='LICENSE', value=enterprise_license) - response = get(url, user=admin, expect=200) - assert response.data['LICENSE'] - put(url, user=admin, data=response.data, expect=200) - response = get(url, user=admin, expect=200) - assert response.data['LICENSE'] - patch(url, user=admin, data={}, expect=200) - response = get(url, user=admin, expect=200) - assert response.data['LICENSE'] - delete(url, user=admin, expect=204) - response = get(url, user=admin, expect=200) - assert response.data['LICENSE'] - - @pytest.mark.django_db def test_url_base_defaults_to_request(options, admin): # If TOWER_URL_BASE is not set, default to the Tower request hostname @@ -76,10 +56,8 @@ def test_jobs_settings(get, put, patch, delete, admin): @pytest.mark.django_db -def test_ldap_settings(get, put, patch, delete, admin, enterprise_license): +def test_ldap_settings(get, put, patch, delete, admin): url = reverse('api:setting_singleton_detail', kwargs={'category_slug': 'ldap'}) - get(url, user=admin, expect=404) - Setting.objects.create(key='LICENSE', value=enterprise_license) get(url, user=admin, expect=200) # The PUT below will fail at the moment because AUTH_LDAP_GROUP_TYPE # defaults to None but cannot be set to None. @@ -102,11 +80,8 @@ def test_ldap_settings(get, put, patch, delete, admin, enterprise_license): 'AUTH_LDAP_DENY_GROUP', ]) @pytest.mark.django_db -def test_empty_ldap_dn(get, put, patch, delete, admin, enterprise_license, - setting): +def test_empty_ldap_dn(get, put, patch, delete, admin, setting): url = reverse('api:setting_singleton_detail', kwargs={'category_slug': 'ldap'}) - Setting.objects.create(key='LICENSE', value=enterprise_license) - patch(url, user=admin, data={setting: ''}, expect=200) resp = get(url, user=admin, expect=200) assert resp.data[setting] is None @@ -117,10 +92,8 @@ def test_empty_ldap_dn(get, put, patch, delete, admin, enterprise_license, @pytest.mark.django_db -def test_radius_settings(get, put, patch, delete, admin, enterprise_license, settings): +def test_radius_settings(get, put, patch, delete, admin, settings): url = reverse('api:setting_singleton_detail', kwargs={'category_slug': 'radius'}) - get(url, user=admin, expect=404) - Setting.objects.create(key='LICENSE', value=enterprise_license) response = get(url, user=admin, expect=200) put(url, user=admin, data=response.data, expect=200) # Set secret via the API. @@ -151,12 +124,11 @@ def test_radius_settings(get, put, patch, delete, admin, enterprise_license, set @pytest.mark.django_db -def test_ui_settings(get, put, patch, delete, admin, enterprise_license): +def test_ui_settings(get, put, patch, delete, admin): url = reverse('api:setting_singleton_detail', kwargs={'category_slug': 'ui'}) response = get(url, user=admin, expect=200) assert 'CUSTOM_LOGO' not in response.data assert 'CUSTOM_LOGIN_INFO' not in response.data - Setting.objects.create(key='LICENSE', value=enterprise_license) response = get(url, user=admin, expect=200) assert not response.data['CUSTOM_LOGO'] assert not response.data['CUSTOM_LOGIN_INFO'] diff --git a/awx/main/tests/functional/core/test_licenses.py b/awx/main/tests/functional/core/test_licenses.py index 948bc64683..b828f49110 100644 --- a/awx/main/tests/functional/core/test_licenses.py +++ b/awx/main/tests/functional/core/test_licenses.py @@ -1,56 +1,7 @@ # Copyright (c) 2015 Ansible, Inc. # All Rights Reserved. -import time -import pytest -from datetime import datetime - -from awx.main.models import Host -from awx.main.utils import get_licenser, StubLicense -from tower_license import TowerLicense - - -@pytest.mark.django_db -def test_license_writer(inventory, admin): - license_actual = TowerLicense( - company_name='acmecorp', - contact_name='Michael DeHaan', - contact_email='michael@ansibleworks.com', - license_date=25000, # seconds since epoch - instance_count=500) - - data = license_actual.generate() - - Host.objects.bulk_create( - [ - Host( - name='host.%d' % n, - inventory=inventory, - created_by=admin, - modified=datetime.now(), - created=datetime.now()) - for n in range(12) - ] - ) - - assert data['instance_count'] == 500 - assert data['contact_name'] == 'Michael DeHaan' - assert data['contact_email'] == 'michael@ansibleworks.com' - assert data['license_date'] == 25000 - assert data['license_key'] == "11bae31f31c6a6cdcb483a278cdbe98bd8ac5761acd7163a50090b0f098b3a13" - - vdata = license_actual.validate() - - assert vdata['available_instances'] == 500 - assert vdata['current_instances'] == 12 - assert vdata['free_instances'] == 488 - assert vdata['date_warning'] is True - assert vdata['date_expired'] is True - assert vdata['license_date'] == 25000 - assert vdata['time_remaining'] < 0 - assert vdata['valid_key'] is True - assert vdata['compliant'] is False - assert vdata['subscription_name'] +from awx.main.utils.common import StubLicense def test_stub_license(): @@ -60,56 +11,3 @@ def test_stub_license(): assert license_actual['compliant'] assert license_actual['license_type'] == 'open' - -@pytest.mark.django_db -def test_expired_licenses(): - license_actual = TowerLicense( - company_name='Tower', - contact_name='Tower Admin', - contact_email='tower@ansible.com', - license_date=int(time.time() - 3600), - instance_count=100, - trial=True) - license_actual.generate() - vdata = license_actual.validate() - - assert vdata['compliant'] is False - assert vdata['grace_period_remaining'] < 0 - - license_actual = TowerLicense( - company_name='Tower', - contact_name='Tower Admin', - contact_email='tower@ansible.com', - license_date=int(time.time() - 2592001), - instance_count=100, - trial=False) - license_actual.generate() - vdata = license_actual.validate() - - assert vdata['compliant'] is False - assert vdata['grace_period_remaining'] < 0 - - license_actual = TowerLicense( - company_name='Tower', - contact_name='Tower Admin', - contact_email='tower@ansible.com', - license_date=int(time.time() - 3600), - instance_count=100, - trial=False) - license_actual.generate() - vdata = license_actual.validate() - - assert vdata['compliant'] is False - assert vdata['grace_period_remaining'] > 0 - - -@pytest.mark.django_db -def test_cloudforms_license(mocker): - with mocker.patch('tower_license.TowerLicense._check_cloudforms_subscription', return_value=True): - license_actual = TowerLicense() - vdata = license_actual.validate() - assert vdata['compliant'] is True - assert vdata['subscription_name'] == "Red Hat CloudForms License" - assert vdata['available_instances'] == 9999999 - assert vdata['license_type'] == 'enterprise' - assert vdata['features']['ha'] is True diff --git a/awx/main/tests/unit/test_access.py b/awx/main/tests/unit/test_access.py index 09154c3f1e..b733895744 100644 --- a/awx/main/tests/unit/test_access.py +++ b/awx/main/tests/unit/test_access.py @@ -257,40 +257,6 @@ class TestWorkflowAccessMethods: assert access.can_add({'organization': 1}) -class TestCheckLicense: - @pytest.fixture - def validate_enhancements_mocker(self, mocker): - os.environ['SKIP_LICENSE_FIXUP_FOR_TEST'] = '1' - - def fn(available_instances=1, free_instances=0, host_exists=False): - - class MockFilter: - def exists(self): - return host_exists - - mocker.patch('tower_license.TowerLicense.validate', return_value={'free_instances': free_instances, 'available_instances': available_instances, 'date_warning': True}) - - mock_filter = MockFilter() - mocker.patch('awx.main.models.Host.objects.filter', return_value=mock_filter) - - return fn - - def test_check_license_add_host_duplicate(self, validate_enhancements_mocker, user_unit): - validate_enhancements_mocker(available_instances=1, free_instances=0, host_exists=True) - - BaseAccess(None).check_license(add_host_name='blah', check_expiration=False) - - def test_check_license_add_host_new_exceed_licence(self, validate_enhancements_mocker, user_unit, mocker): - validate_enhancements_mocker(available_instances=1, free_instances=0, host_exists=False) - exception = None - - try: - BaseAccess(None).check_license(add_host_name='blah', check_expiration=False) - except PermissionDenied as e: - exception = e - - assert "License count of 1 instances has been reached." == str(exception) - def test_user_capabilities_method(): """Unit test to verify that the user_capabilities method will defer diff --git a/awx/main/tests/unit/test_tasks.py b/awx/main/tests/unit/test_tasks.py index f649a7d2b4..8fc0b5db01 100644 --- a/awx/main/tests/unit/test_tasks.py +++ b/awx/main/tests/unit/test_tasks.py @@ -31,7 +31,6 @@ from awx.main.models import ( from awx.main import tasks from awx.main.utils import encrypt_field -from tower_license import TowerLicense @contextmanager @@ -79,27 +78,6 @@ def test_send_notifications_list(mocker): assert mock_job.notifications.add.called_with(*mock_notifications) -@pytest.mark.parametrize("current_instances,call_count", [(91, 2), (89,1)]) -def test_run_admin_checks_usage(mocker, current_instances, call_count): - patches = list() - patches.append(mocker.patch('awx.main.tasks.User')) - - mock_te = mocker.Mock(spec=TowerLicense) - mock_te.validate.return_value = {'instance_count': 100, 'current_instances': current_instances, 'date_warning': True} - patches.append(mocker.patch('tower_license.TowerLicense', return_value=mock_te)) - - mock_sm = mocker.Mock() - patches.append(mocker.patch('awx.main.tasks.send_mail', wraps=mock_sm)) - - with apply_patches(patches): - tasks.run_administrative_checks() - assert mock_sm.called - if call_count == 2: - assert '90%' in mock_sm.call_args_list[0][0][0] - else: - assert 'expire' in mock_sm.call_args_list[0][0][0] - - @pytest.mark.parametrize("key,value", [ ('REST_API_TOKEN', 'SECRET'), ('SECRET_KEY', 'SECRET'), diff --git a/tools/docker-compose/Dockerfile b/tools/docker-compose/Dockerfile index d55ecd9a46..93712f3dba 100644 --- a/tools/docker-compose/Dockerfile +++ b/tools/docker-compose/Dockerfile @@ -2,7 +2,6 @@ FROM centos:7 ADD Makefile /tmp/Makefile RUN mkdir /tmp/requirements -COPY tools/docker-compose/tower-license/dist/tower-license-0.1.tar.gz /tmp/tower-license.tar.gz ADD requirements/requirements.txt \ requirements/requirements_git.txt \ requirements/requirements_ansible.txt \ @@ -34,7 +33,6 @@ RUN openssl req -nodes -newkey rsa:2048 -keyout /etc/nginx/nginx.key -out /etc/n RUN openssl x509 -req -days 365 -in /etc/nginx/nginx.csr -signkey /etc/nginx/nginx.key -out /etc/nginx/nginx.crt WORKDIR /tmp RUN SWIG_FEATURES="-cpperraswarn -includeall -D__`uname -m`__ -I/usr/include/openssl" VENV_BASE="/venv" make requirements_dev -RUN /venv/tower/bin/pip install /tmp/tower-license.tar.gz RUN localedef -c -i en_US -f UTF-8 en_US.UTF-8 ENV LANG en_US.UTF-8 ENV LANGUAGE en_US:en From c1ce79d0a59cc96915bd25a5eb2db219d89a4e7f Mon Sep 17 00:00:00 2001 From: Matthew Jones Date: Thu, 13 Jul 2017 10:27:43 -0400 Subject: [PATCH 3/4] Fix up some issues on feature validation for licenses Also purging and fixing up some unit test behavior --- awx/main/tests/conftest.py | 1 - awx/main/tests/functional/api/test_settings.py | 3 --- awx/main/tests/functional/core/test_licenses.py | 2 +- awx/main/tests/unit/test_access.py | 2 -- awx/main/utils/common.py | 13 +++++++++++++ 5 files changed, 14 insertions(+), 7 deletions(-) diff --git a/awx/main/tests/conftest.py b/awx/main/tests/conftest.py index a8e504be22..679577858f 100644 --- a/awx/main/tests/conftest.py +++ b/awx/main/tests/conftest.py @@ -1,6 +1,5 @@ # Python -import time import pytest import mock from contextlib import contextmanager diff --git a/awx/main/tests/functional/api/test_settings.py b/awx/main/tests/functional/api/test_settings.py index f67d9f0fe7..8083fb2d2d 100644 --- a/awx/main/tests/functional/api/test_settings.py +++ b/awx/main/tests/functional/api/test_settings.py @@ -127,9 +127,6 @@ def test_radius_settings(get, put, patch, delete, admin, settings): def test_ui_settings(get, put, patch, delete, admin): url = reverse('api:setting_singleton_detail', kwargs={'category_slug': 'ui'}) response = get(url, user=admin, expect=200) - assert 'CUSTOM_LOGO' not in response.data - assert 'CUSTOM_LOGIN_INFO' not in response.data - response = get(url, user=admin, expect=200) assert not response.data['CUSTOM_LOGO'] assert not response.data['CUSTOM_LOGIN_INFO'] put(url, user=admin, data=response.data, expect=200) diff --git a/awx/main/tests/functional/core/test_licenses.py b/awx/main/tests/functional/core/test_licenses.py index b828f49110..f59318502c 100644 --- a/awx/main/tests/functional/core/test_licenses.py +++ b/awx/main/tests/functional/core/test_licenses.py @@ -5,7 +5,7 @@ from awx.main.utils.common import StubLicense def test_stub_license(): - license_actual = StubLicense() + license_actual = StubLicense().validate() assert license_actual['license_key'] == 'OPEN' assert license_actual['valid_key'] assert license_actual['compliant'] diff --git a/awx/main/tests/unit/test_access.py b/awx/main/tests/unit/test_access.py index b733895744..61660afeb0 100644 --- a/awx/main/tests/unit/test_access.py +++ b/awx/main/tests/unit/test_access.py @@ -1,11 +1,9 @@ import pytest import mock -import os from django.contrib.auth.models import User from django.forms.models import model_to_dict from rest_framework.exceptions import ParseError -from rest_framework.exceptions import PermissionDenied from awx.main.access import ( BaseAccess, diff --git a/awx/main/utils/common.py b/awx/main/utils/common.py index 5447f32f0f..3d51e488a9 100644 --- a/awx/main/utils/common.py +++ b/awx/main/utils/common.py @@ -163,10 +163,23 @@ def get_awx_version(): class StubLicense(object): + features = { + 'activity_streams': True, + 'ha': True, + 'ldap': True, + 'multiple_organizations': True, + 'surveys': True, + 'system_tracking': True, + 'rebranding': True, + 'enterprise_auth': True, + 'workflows': True, + } + def validate(self): return dict(license_key='OPEN', valid_key=True, compliant=True, + features=self.features, license_type='open') From d5346ec6d0ded094861df22c2c175d677ad7fb1f Mon Sep 17 00:00:00 2001 From: Matthew Jones Date: Thu, 13 Jul 2017 10:57:56 -0400 Subject: [PATCH 4/4] Hide eula if stub license is used --- awx/api/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/awx/api/views.py b/awx/api/views.py index b01742ed09..c3879319e7 100644 --- a/awx/api/views.py +++ b/awx/api/views.py @@ -280,7 +280,7 @@ class ApiV1ConfigView(APIView): license_info=license_data, version=get_awx_version(), ansible_version=get_ansible_version(), - eula=render_to_string("eula.md"), + eula=render_to_string("eula.md") if license_data['license_type'] != 'open' else '', analytics_status=pendo_state )