From 5b1df83fcc48c04e8be8ee92d09b0aa5371c1fa6 Mon Sep 17 00:00:00 2001 From: Chris Church Date: Mon, 21 Nov 2016 14:15:41 -0500 Subject: [PATCH] Add support for hiding settings based on whether features are enabled in the license. --- awx/conf/conf.py | 3 +++ awx/conf/license.py | 12 +++++++++++- awx/conf/registry.py | 15 +++++++++++++-- awx/conf/views.py | 9 +++++---- awx/main/conf.py | 2 ++ awx/sso/conf.py | 29 +++++++++++++++++++++++++++++ 6 files changed, 63 insertions(+), 7 deletions(-) diff --git a/awx/conf/conf.py b/awx/conf/conf.py index cd494f1dee..840bcaeb58 100644 --- a/awx/conf/conf.py +++ b/awx/conf/conf.py @@ -78,6 +78,9 @@ register( # the other settings change, the cached value for this setting will be # cleared to require it to be recomputed. depends_on=['ANSIBLE_COW_SELECTION'], + # Optional; licensed feature required to be able to view or modify this + # setting. + feature_required='rebranding', ) register( diff --git a/awx/conf/license.py b/awx/conf/license.py index 816b143e64..a5ac14e659 100644 --- a/awx/conf/license.py +++ b/awx/conf/license.py @@ -14,7 +14,8 @@ from rest_framework.exceptions import APIException from awx.main.task_engine import TaskEnhancer from awx.main.utils import memoize -__all__ = ['LicenseForbids', 'get_license', 'feature_enabled', 'feature_exists'] +__all__ = ['LicenseForbids', 'get_license', 'get_licensed_features', + 'feature_enabled', 'feature_exists'] class LicenseForbids(APIException): @@ -42,6 +43,15 @@ def get_license(show_key=False): return license_data +def get_licensed_features(): + """Return a set of all features enabled by the active license.""" + features = set() + for feature, enabled in _get_validated_license_data().get('features', {}).items(): + if enabled: + features.add(feature) + return 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) diff --git a/awx/conf/registry.py b/awx/conf/registry.py index 0c48895c1c..8f178a0967 100644 --- a/awx/conf/registry.py +++ b/awx/conf/registry.py @@ -50,7 +50,7 @@ class SettingsRegistry(object): def get_dependent_settings(self, setting): return self._dependent_settings.get(setting, set()) - def get_registered_categories(self): + def get_registered_categories(self, features_enabled=None): categories = { 'all': _('All'), 'changed': _('Changed'), @@ -59,6 +59,10 @@ class SettingsRegistry(object): category_slug = kwargs.get('category_slug', None) if category_slug is None or category_slug in categories: continue + if features_enabled is not None: + feature_required = kwargs.get('feature_required', None) + if feature_required and feature_required not in features_enabled: + continue if category_slug == 'user': categories['user'] = _('User') categories['user-defaults'] = _('User-Defaults') @@ -66,7 +70,7 @@ class SettingsRegistry(object): categories[category_slug] = kwargs.get('category', None) or category_slug return categories - def get_registered_settings(self, category_slug=None, read_only=None): + def get_registered_settings(self, category_slug=None, read_only=None, features_enabled=None): setting_names = [] if category_slug == 'user-defaults': category_slug = 'user' @@ -79,6 +83,10 @@ class SettingsRegistry(object): # Note: Doesn't catch fields that set read_only via __init__; # read-only field kwargs should always include read_only=True. continue + if features_enabled is not None: + feature_required = kwargs.get('feature_required', None) + if feature_required and feature_required not in features_enabled: + continue setting_names.append(setting) return setting_names @@ -95,6 +103,7 @@ class SettingsRegistry(object): category = field_kwargs.pop('category', None) depends_on = frozenset(field_kwargs.pop('depends_on', None) or []) placeholder = field_kwargs.pop('placeholder', empty) + feature_required = field_kwargs.pop('feature_required', empty) if getattr(field_kwargs.get('child', None), 'source', None) is not None: field_kwargs['child'].source = None field_instance = field_class(**field_kwargs) @@ -103,6 +112,8 @@ class SettingsRegistry(object): field_instance.depends_on = depends_on if placeholder is not empty: field_instance.placeholder = placeholder + if feature_required is not empty: + field_instance.feature_required = feature_required original_field_instance = field_instance if field_class != original_field_class: original_field_instance = original_field_class(**field_kwargs) diff --git a/awx/conf/views.py b/awx/conf/views.py index b18e5c5704..a02510f9cd 100644 --- a/awx/conf/views.py +++ b/awx/conf/views.py @@ -19,6 +19,7 @@ from rest_framework import status # Tower from awx.api.generics import * # noqa from awx.main.utils import * # noqa +from awx.conf.license import get_licensed_features from awx.conf.models import Setting from awx.conf.serializers import SettingCategorySerializer, SettingSingletonSerializer from awx.conf import settings_registry @@ -37,7 +38,7 @@ class SettingCategoryList(ListAPIView): def get_queryset(self): setting_categories = [] - categories = settings_registry.get_registered_categories() + categories = settings_registry.get_registered_categories(features_enabled=get_licensed_features()) if self.request.user.is_superuser or self.request.user.is_system_auditor: pass # categories = categories elif 'user' in categories: @@ -60,7 +61,7 @@ class SettingSingletonDetail(RetrieveUpdateDestroyAPIView): def get_queryset(self): self.category_slug = self.kwargs.get('category_slug', 'all') - all_category_slugs = settings_registry.get_registered_categories().keys() + all_category_slugs = settings_registry.get_registered_categories(features_enabled=get_licensed_features()).keys() if self.request.user.is_superuser or getattr(self.request.user, 'is_system_auditor', False): category_slugs = all_category_slugs else: @@ -70,7 +71,7 @@ class SettingSingletonDetail(RetrieveUpdateDestroyAPIView): if self.category_slug not in category_slugs: raise PermissionDenied() - registered_settings = settings_registry.get_registered_settings(category_slug=self.category_slug, read_only=False) + registered_settings = settings_registry.get_registered_settings(category_slug=self.category_slug, read_only=False, features_enabled=get_licensed_features()) if self.category_slug == 'user': return Setting.objects.filter(key__in=registered_settings, user=self.request.user) else: @@ -78,7 +79,7 @@ class SettingSingletonDetail(RetrieveUpdateDestroyAPIView): def get_object(self): settings_qs = self.get_queryset() - registered_settings = settings_registry.get_registered_settings(category_slug=self.category_slug) + registered_settings = settings_registry.get_registered_settings(category_slug=self.category_slug, features_enabled=get_licensed_features()) all_settings = {} for setting in settings_qs: all_settings[setting.key] = setting.value diff --git a/awx/main/conf.py b/awx/main/conf.py index b2ed431e87..17e28e77db 100644 --- a/awx/main/conf.py +++ b/awx/main/conf.py @@ -18,6 +18,7 @@ register( help_text=_('Enable capturing activity for the Tower activity stream.'), category=_('System'), category_slug='system', + feature_required='activity_streams', ) register( @@ -27,6 +28,7 @@ register( help_text=_('Enable capturing activity for the Tower activity stream when running inventory sync.'), category=_('System'), category_slug='system', + feature_required='activity_streams', ) register( diff --git a/awx/sso/conf.py b/awx/sso/conf.py index 0651d9d726..75c522dea7 100644 --- a/awx/sso/conf.py +++ b/awx/sso/conf.py @@ -182,6 +182,7 @@ register( category=_('LDAP'), category_slug='ldap', placeholder='ldaps://ldap.example.com:636', + feature_required='ldap', ) register( @@ -198,6 +199,7 @@ register( 'for other user information.'), category=_('LDAP'), category_slug='ldap', + feature_required='ldap', ) register( @@ -209,6 +211,7 @@ register( help_text=_('Password used to bind LDAP user account.'), category=_('LDAP'), category_slug='ldap', + feature_required='ldap', ) register( @@ -219,6 +222,7 @@ register( help_text=_('Whether to enable TLS when the LDAP connection is not using SSL.'), category=_('LDAP'), category_slug='ldap', + feature_required='ldap', ) register( @@ -237,6 +241,7 @@ register( placeholder=collections.OrderedDict([ ('OPT_REFERRALS', 0), ]), + feature_required='ldap', ) register( @@ -257,6 +262,7 @@ register( 'SCOPE_SUBTREE', '(sAMAccountName=%(user)s)', ), + feature_required='ldap', ) register( @@ -273,6 +279,7 @@ register( category=_('LDAP'), category_slug='ldap', placeholder='uid=%(user)s,OU=Users,DC=example,DC=com', + feature_required='ldap', ) register( @@ -292,6 +299,7 @@ register( ('last_name', 'sn'), ('email', 'mail'), ]), + feature_required='ldap', ) register( @@ -310,6 +318,7 @@ register( 'SCOPE_SUBTREE', '(objectClass=group)', ), + feature_required='ldap', ) register( @@ -321,6 +330,7 @@ register( 'http://pythonhosted.org/django-auth-ldap/groups.html#types-of-groups'), category=_('LDAP'), category_slug='ldap', + feature_required='ldap', ) register( @@ -336,6 +346,7 @@ register( category=_('LDAP'), category_slug='ldap', placeholder='CN=Tower Users,OU=Users,DC=example,DC=com', + feature_required='ldap', ) register( @@ -350,6 +361,7 @@ register( category=_('LDAP'), category_slug='ldap', placeholder='CN=Disabled Users,OU=Users,DC=example,DC=com', + feature_required='ldap', ) register( @@ -368,6 +380,7 @@ register( placeholder=collections.OrderedDict([ ('is_superuser', 'CN=Domain Admins,CN=Users,DC=example,DC=com'), ]), + feature_required='ldap', ) register( @@ -416,6 +429,7 @@ register( ('remove_admins', True), ])), ]), + feature_required='ldap', ) register( @@ -454,6 +468,7 @@ register( ('remove', False), ])), ]), + feature_required='ldap', ) ############################################################################### @@ -471,6 +486,7 @@ register( category=_('RADIUS'), category_slug='radius', placeholder='radius.example.com', + feature_required='enterprise_auth', ) register( @@ -483,6 +499,7 @@ register( help_text=_('Port of RADIUS server.'), category=_('RADIUS'), category_slug='radius', + feature_required='enterprise_auth', ) register( @@ -494,6 +511,7 @@ register( help_text=_('Shared secret for authenticating to RADIUS server.'), category=_('RADIUS'), category_slug='radius', + feature_required='enterprise_auth', ) ############################################################################### @@ -882,6 +900,7 @@ register( category=_('SAML'), category_slug='saml', depends_on=['TOWER_URL_BASE'], + feature_required='enterprise_auth', ) register( @@ -894,6 +913,7 @@ register( 'metadata file, you can download one from this URL.'), category=_('SAML'), category_slug='saml', + feature_required='enterprise_auth', ) register( @@ -907,6 +927,7 @@ register( 'valid URL; only used as a unique ID).'), category=_('SAML'), category_slug='saml', + feature_required='enterprise_auth', ) register( @@ -920,6 +941,7 @@ register( 'and include the certificate content here.'), category=_('SAML'), category_slug='saml', + feature_required='enterprise_auth', ) register( @@ -933,6 +955,7 @@ register( 'and include the private key content here.'), category=_('SAML'), category_slug='saml', + feature_required='enterprise_auth', ) register( @@ -950,6 +973,7 @@ register( ('url', 'http://www.example.com'), ])), ]), + feature_required='enterprise_auth', ) register( @@ -965,6 +989,7 @@ register( ('givenName', 'Technical Contact'), ('emailAddress', 'techsup@example.com'), ]), + feature_required='enterprise_auth', ) register( @@ -980,6 +1005,7 @@ register( ('givenName', 'Support Contact'), ('emailAddress', 'support@example.com'), ]), + feature_required='enterprise_auth', ) register( @@ -1017,6 +1043,7 @@ register( ('attr_email', 'User.email'), ])), ]), + feature_required='enterprise_auth', ) register( @@ -1029,6 +1056,7 @@ register( category=_('SAML'), category_slug='saml', placeholder=SOCIAL_AUTH_ORGANIZATION_MAP_PLACEHOLDER, + feature_required='enterprise_auth', ) register( @@ -1041,4 +1069,5 @@ register( category=_('SAML'), category_slug='saml', placeholder=SOCIAL_AUTH_TEAM_MAP_PLACEHOLDER, + feature_required='enterprise_auth', )