Merge branch 'license_module_switch' into devel

* license_module_switch:
  Hide eula if stub license is used
  Fix up some issues on feature validation for licenses
  Remove any references to internal licensing utilities
  Switch out existing obfuscated license with external module
This commit is contained in:
Matthew Jones 2017-07-13 11:08:47 -04:00
commit 8e8324983b
12 changed files with 64 additions and 253 deletions

View File

@ -279,7 +279,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
)
@ -323,9 +323,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))

View File

@ -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):

View File

@ -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

View File

@ -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)

View File

@ -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
@ -142,8 +141,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)

View File

@ -29,7 +29,6 @@ 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
@ -178,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 = TaskEnhancer(
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,
).enhance()
def create_basic_license_file(self, instance_count=100, license_date=int(time.time() + 3600)):
settings.LICENSE = TaskEnhancer(
company_name='AWX',
contact_name='AWX Admin',
contact_email='awx@example.com',
license_date=license_date,
instance_count=instance_count,
license_type='basic',
).enhance()
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))

View File

@ -1,6 +1,5 @@
# Python
import time
import pytest
import mock
from contextlib import contextmanager
@ -97,16 +96,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 awx.main.task_engine import TaskEnhancer
return TaskEnhancer(
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()

View File

@ -29,26 +29,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
@ -98,10 +78,8 @@ def test_awx_task_env_validity(get, patch, admin, value, expected):
@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.
@ -124,11 +102,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
@ -139,10 +114,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.
@ -173,13 +146,9 @@ 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']
put(url, user=admin, data=response.data, expect=200)

View File

@ -1,106 +1,13 @@
# 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.task_engine import TaskEnhancer
from awx.main.utils.common import StubLicense
@pytest.mark.django_db
def test_license_writer(inventory, admin):
task_enhancer = TaskEnhancer(
company_name='acmecorp',
contact_name='Michael DeHaan',
contact_email='michael@ansibleworks.com',
license_date=25000, # seconds since epoch
instance_count=500)
def test_stub_license():
license_actual = StubLicense().validate()
assert license_actual['license_key'] == 'OPEN'
assert license_actual['valid_key']
assert license_actual['compliant']
assert license_actual['license_type'] == 'open'
data = task_enhancer.enhance()
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 = task_enhancer.validate_enhancements()
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']
@pytest.mark.django_db
def test_expired_licenses():
task_enhancer = TaskEnhancer(
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()
assert vdata['compliant'] is False
assert vdata['grace_period_remaining'] < 0
task_enhancer = TaskEnhancer(
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()
assert vdata['compliant'] is False
assert vdata['grace_period_remaining'] < 0
task_enhancer = TaskEnhancer(
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()
assert vdata['compliant'] is False
assert vdata['grace_period_remaining'] > 0
@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()
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

View File

@ -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,
@ -257,40 +255,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('awx.main.tasks.TaskEnhancer.validate_enhancements', 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

View File

@ -29,10 +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
@contextmanager
def apply_patches(_patches):
[p.start() for p in _patches]
@ -78,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=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_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'),

View File

@ -34,7 +34,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',
@ -162,6 +162,36 @@ def get_awx_version():
return __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')
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):
'''