Basic License feature gating changes

This commit is contained in:
beeankha
2019-04-01 17:24:55 -04:00
committed by mabashian
parent 58966d7368
commit de34a64115
61 changed files with 125 additions and 1015 deletions

View File

@@ -31,7 +31,6 @@ from social_core.backends.saml import SAMLAuth as BaseSAMLAuth
from social_core.backends.saml import SAMLIdentityProvider as BaseSAMLIdentityProvider
# Ansible Tower
from awx.conf.license import feature_enabled
from awx.sso.models import UserEnterpriseAuth
logger = logging.getLogger('awx.sso.backends')
@@ -94,9 +93,6 @@ class LDAPBackend(BaseLDAPBackend):
if not self.settings.SERVER_URI:
return None
if not feature_enabled('ldap'):
logger.error("Unable to authenticate, license does not support LDAP authentication")
return None
try:
user = User.objects.get(username=username)
if user and (not user.profile or not user.profile.ldap_dn):
@@ -121,9 +117,6 @@ class LDAPBackend(BaseLDAPBackend):
def get_user(self, user_id):
if not self.settings.SERVER_URI:
return None
if not feature_enabled('ldap'):
logger.error("Unable to get_user, license does not support LDAP authentication")
return None
return super(LDAPBackend, self).get_user(user_id)
# Disable any LDAP based authorization / permissions checking.
@@ -188,20 +181,14 @@ class RADIUSBackend(BaseRADIUSBackend):
Custom Radius backend to verify license status
'''
def authenticate(self, username, password):
def authenticate(self, username, password):
if not django_settings.RADIUS_SERVER:
return None
if not feature_enabled('enterprise_auth'):
logger.error("Unable to authenticate, license does not support RADIUS authentication")
return None
return super(RADIUSBackend, self).authenticate(None, username, password)
def get_user(self, user_id):
if not django_settings.RADIUS_SERVER:
return None
if not feature_enabled('enterprise_auth'):
logger.error("Unable to get_user, license does not support RADIUS authentication")
return None
user = super(RADIUSBackend, self).get_user(user_id)
if not user.has_usable_password():
return user
@@ -218,9 +205,6 @@ class TACACSPlusBackend(object):
def authenticate(self, username, password):
if not django_settings.TACACSPLUS_HOST:
return None
if not feature_enabled('enterprise_auth'):
logger.error("Unable to authenticate, license does not support TACACS+ authentication")
return None
try:
# Upstream TACACS+ client does not accept non-string, so convert if needed.
auth = tacacs_plus.TACACSClient(
@@ -241,9 +225,6 @@ class TACACSPlusBackend(object):
def get_user(self, user_id):
if not django_settings.TACACSPLUS_HOST:
return None
if not feature_enabled('enterprise_auth'):
logger.error("Unable to get user, license does not support TACACS+ authentication")
return None
try:
return User.objects.get(pk=user_id)
except User.DoesNotExist:
@@ -294,9 +275,6 @@ class SAMLAuth(BaseSAMLAuth):
django_settings.SOCIAL_AUTH_SAML_TECHNICAL_CONTACT, django_settings.SOCIAL_AUTH_SAML_SUPPORT_CONTACT,
django_settings.SOCIAL_AUTH_SAML_ENABLED_IDPS]):
return None
if not feature_enabled('enterprise_auth'):
logger.error("Unable to authenticate, license does not support SAML authentication")
return None
user = super(SAMLAuth, self).authenticate(*args, **kwargs)
# Comes from https://github.com/omab/python-social-auth/blob/v0.2.21/social/backends/base.py#L91
if getattr(user, 'is_new', False):
@@ -311,9 +289,6 @@ class SAMLAuth(BaseSAMLAuth):
django_settings.SOCIAL_AUTH_SAML_TECHNICAL_CONTACT, django_settings.SOCIAL_AUTH_SAML_SUPPORT_CONTACT,
django_settings.SOCIAL_AUTH_SAML_ENABLED_IDPS]):
return None
if not feature_enabled('enterprise_auth'):
logger.error("Unable to get_user, license does not support SAML authentication")
return None
return super(SAMLAuth, self).get_user(user_id)

View File

@@ -157,7 +157,6 @@ def _register_ldap(append=None):
category=_('LDAP'),
category_slug='ldap',
placeholder='ldaps://ldap.example.com:636',
feature_required='ldap',
)
register(
@@ -172,7 +171,6 @@ def _register_ldap(append=None):
' user information. Refer to the Ansible Tower documentation for example syntax.'),
category=_('LDAP'),
category_slug='ldap',
feature_required='ldap',
)
register(
@@ -184,7 +182,6 @@ def _register_ldap(append=None):
help_text=_('Password used to bind LDAP user account.'),
category=_('LDAP'),
category_slug='ldap',
feature_required='ldap',
encrypted=True,
)
@@ -196,7 +193,6 @@ def _register_ldap(append=None):
help_text=_('Whether to enable TLS when the LDAP connection is not using SSL.'),
category=_('LDAP'),
category_slug='ldap',
feature_required='ldap',
)
register(
@@ -216,7 +212,6 @@ def _register_ldap(append=None):
('OPT_REFERRALS', 0),
('OPT_NETWORK_TIMEOUT', 30)
]),
feature_required='ldap',
)
register(
@@ -237,7 +232,6 @@ def _register_ldap(append=None):
'SCOPE_SUBTREE',
'(sAMAccountName=%(user)s)',
),
feature_required='ldap',
)
register(
@@ -255,7 +249,6 @@ def _register_ldap(append=None):
category=_('LDAP'),
category_slug='ldap',
placeholder='uid=%(user)s,OU=Users,DC=example,DC=com',
feature_required='ldap',
)
register(
@@ -274,7 +267,6 @@ def _register_ldap(append=None):
('last_name', 'sn'),
('email', 'mail'),
]),
feature_required='ldap',
)
register(
@@ -292,7 +284,6 @@ def _register_ldap(append=None):
'SCOPE_SUBTREE',
'(objectClass=group)',
),
feature_required='ldap',
)
register(
@@ -304,7 +295,6 @@ def _register_ldap(append=None):
'https://django-auth-ldap.readthedocs.io/en/stable/groups.html#types-of-groups'),
category=_('LDAP'),
category_slug='ldap',
feature_required='ldap',
default='MemberDNGroupType',
depends_on=['AUTH_LDAP{}_GROUP_TYPE_PARAMS'.format(append_str)],
)
@@ -325,7 +315,6 @@ def _register_ldap(append=None):
('member_attr', 'member'),
('name_attr', 'cn'),
]),
feature_required='ldap',
depends_on=['AUTH_LDAP{}_GROUP_TYPE'.format(append_str)],
)
@@ -343,7 +332,6 @@ def _register_ldap(append=None):
category=_('LDAP'),
category_slug='ldap',
placeholder='CN=Tower Users,OU=Users,DC=example,DC=com',
feature_required='ldap',
)
register(
@@ -359,7 +347,6 @@ def _register_ldap(append=None):
category=_('LDAP'),
category_slug='ldap',
placeholder='CN=Disabled Users,OU=Users,DC=example,DC=com',
feature_required='ldap',
)
register(
@@ -376,7 +363,6 @@ def _register_ldap(append=None):
('is_superuser', 'CN=Domain Admins,CN=Users,DC=example,DC=com'),
('is_system_auditor', 'CN=Domain Auditors,CN=Users,DC=example,DC=com'),
]),
feature_required='ldap',
)
register(
@@ -404,7 +390,6 @@ def _register_ldap(append=None):
('remove_admins', True),
])),
]),
feature_required='ldap',
)
register(
@@ -428,7 +413,6 @@ def _register_ldap(append=None):
('remove', False),
])),
]),
feature_required='ldap',
)
@@ -454,7 +438,6 @@ register(
category=_('RADIUS'),
category_slug='radius',
placeholder='radius.example.com',
feature_required='enterprise_auth',
)
register(
@@ -467,7 +450,6 @@ register(
help_text=_('Port of RADIUS server.'),
category=_('RADIUS'),
category_slug='radius',
feature_required='enterprise_auth',
)
register(
@@ -479,7 +461,6 @@ register(
help_text=_('Shared secret for authenticating to RADIUS server.'),
category=_('RADIUS'),
category_slug='radius',
feature_required='enterprise_auth',
encrypted=True,
)
@@ -496,7 +477,6 @@ register(
help_text=_('Hostname of TACACS+ server.'),
category=_('TACACS+'),
category_slug='tacacsplus',
feature_required='enterprise_auth',
)
register(
@@ -509,7 +489,6 @@ register(
help_text=_('Port number of TACACS+ server.'),
category=_('TACACS+'),
category_slug='tacacsplus',
feature_required='enterprise_auth',
)
register(
@@ -522,7 +501,6 @@ register(
help_text=_('Shared secret for authenticating to TACACS+ server.'),
category=_('TACACS+'),
category_slug='tacacsplus',
feature_required='enterprise_auth',
encrypted=True,
)
@@ -535,7 +513,6 @@ register(
help_text=_('TACACS+ session timeout value in seconds, 0 disables timeout.'),
category=_('TACACS+'),
category_slug='tacacsplus',
feature_required='enterprise_auth',
)
register(
@@ -547,7 +524,6 @@ register(
help_text=_('Choose the authentication protocol used by TACACS+ client.'),
category=_('TACACS+'),
category_slug='tacacsplus',
feature_required='enterprise_auth',
)
###############################################################################
@@ -953,7 +929,6 @@ register(
category=_('SAML'),
category_slug='saml',
depends_on=['TOWER_URL_BASE'],
feature_required='enterprise_auth',
)
register(
@@ -966,7 +941,6 @@ register(
'metadata file, you can download one from this URL.'),
category=_('SAML'),
category_slug='saml',
feature_required='enterprise_auth',
)
register(
@@ -980,7 +954,6 @@ register(
'This is usually the URL for Tower.'),
category=_('SAML'),
category_slug='saml',
feature_required='enterprise_auth',
depends_on=['TOWER_URL_BASE'],
)
@@ -995,7 +968,6 @@ register(
'and include the certificate content here.'),
category=_('SAML'),
category_slug='saml',
feature_required='enterprise_auth',
)
register(
@@ -1009,7 +981,6 @@ register(
'and include the private key content here.'),
category=_('SAML'),
category_slug='saml',
feature_required='enterprise_auth',
encrypted=True,
)
@@ -1029,7 +1000,6 @@ register(
('url', 'http://www.example.com'),
])),
]),
feature_required='enterprise_auth',
)
register(
@@ -1047,7 +1017,6 @@ register(
('givenName', 'Technical Contact'),
('emailAddress', 'techsup@example.com'),
]),
feature_required='enterprise_auth',
)
register(
@@ -1065,7 +1034,6 @@ register(
('givenName', 'Support Contact'),
('emailAddress', 'support@example.com'),
]),
feature_required='enterprise_auth',
)
register(
@@ -1102,7 +1070,6 @@ register(
('attr_email', 'User.email'),
])),
]),
feature_required='enterprise_auth',
)
register(
@@ -1135,7 +1102,6 @@ register(
("signatureAlgorithm", "http://www.w3.org/2000/09/xmldsig#rsa-sha1"),
("digestAlgorithm", "http://www.w3.org/2000/09/xmldsig#sha1"),
]),
feature_required='enterprise_auth',
)
register(
@@ -1149,7 +1115,6 @@ register(
category=_('SAML'),
category_slug='saml',
placeholder=collections.OrderedDict(),
feature_required='enterprise_auth',
)
register(
@@ -1167,7 +1132,6 @@ register(
('department', 'department'),
('manager_full_name', 'manager_full_name')
],
feature_required='enterprise_auth',
)
register(
@@ -1180,7 +1144,6 @@ register(
category=_('SAML'),
category_slug='saml',
placeholder=SOCIAL_AUTH_ORGANIZATION_MAP_PLACEHOLDER,
feature_required='enterprise_auth',
)
register(
@@ -1193,7 +1156,6 @@ register(
category=_('SAML'),
category_slug='saml',
placeholder=SOCIAL_AUTH_TEAM_MAP_PLACEHOLDER,
feature_required='enterprise_auth',
)
register(
@@ -1211,7 +1173,6 @@ register(
('remove', True),
('remove_admins', True),
]),
feature_required='enterprise_auth',
)
register(
@@ -1253,7 +1214,6 @@ register(
]),
]),
]),
feature_required='enterprise_auth',
)

View File

@@ -25,7 +25,6 @@ from awx.sso.ldap_group_types import PosixUIDGroupType # noqa
# Tower
from awx.conf import fields
from awx.conf.license import feature_enabled
from awx.main.validators import validate_certificate
from awx.sso.validators import ( # noqa
validate_ldap_dn,
@@ -164,13 +163,12 @@ class AuthenticationBackendsField(fields.StringListField):
except AttributeError:
backends = self.REQUIRED_BACKEND_SETTINGS.keys()
# Filter which authentication backends are enabled based on their
# required settings being defined and non-empty. Also filter available
# backends based on license features.
# required settings being defined and non-empty.
for backend, required_settings in self.REQUIRED_BACKEND_SETTINGS.items():
if backend not in backends:
continue
required_feature = self.REQUIRED_BACKEND_FEATURE.get(backend, '')
if not required_feature or feature_enabled(required_feature):
if not required_feature:
if all([getattr(settings, rs, None) for rs in required_settings]):
continue
backends = [x for x in backends if x != backend]
@@ -782,4 +780,3 @@ class SAMLTeamAttrField(BaseDictWithChildField):
'remove': fields.BooleanField(required=False),
'saml_attr': fields.CharField(required=False, allow_null=True),
}

View File

@@ -13,9 +13,6 @@ from social_core.exceptions import AuthException
from django.utils.translation import ugettext_lazy as _
from django.db.models import Q
# Tower
from awx.conf.license import feature_enabled
logger = logging.getLogger('awx.sso.pipeline')
@@ -83,18 +80,11 @@ def _update_m2m_from_expression(user, rel, expr, remove=True):
def _update_org_from_attr(user, rel, attr, remove, remove_admins):
from awx.main.models import Organization
multiple_orgs = feature_enabled('multiple_organizations')
org_ids = []
for org_name in attr:
if multiple_orgs:
org = Organization.objects.get_or_create(name=org_name)[0]
else:
try:
org = Organization.objects.order_by('pk')[0]
except IndexError:
continue
org = Organization.objects.get_or_create(name=org_name)[0]
org_ids.append(org.id)
getattr(org, rel).members.add(user)
@@ -116,19 +106,10 @@ def update_user_orgs(backend, details, user=None, *args, **kwargs):
if not user:
return
from awx.main.models import Organization
multiple_orgs = feature_enabled('multiple_organizations')
org_map = backend.setting('ORGANIZATION_MAP') or {}
for org_name, org_opts in org_map.items():
org = Organization.objects.get_or_create(name=org_name)[0]
# Get or create the org to update. If the license only allows for one
# org, always use the first active org, unless no org exists.
if multiple_orgs:
org = Organization.objects.get_or_create(name=org_name)[0]
else:
try:
org = Organization.objects.order_by('pk')[0]
except IndexError:
continue
# Update org admins from expression(s).
remove = bool(org_opts.get('remove', True))
@@ -150,21 +131,13 @@ def update_user_teams(backend, details, user=None, *args, **kwargs):
if not user:
return
from awx.main.models import Organization, Team
multiple_orgs = feature_enabled('multiple_organizations')
team_map = backend.setting('TEAM_MAP') or {}
for team_name, team_opts in team_map.items():
# Get or create the org to update.
if 'organization' not in team_opts:
continue
org = Organization.objects.get_or_create(name=team_opts['organization'])[0]
# Get or create the org to update. If the license only allows for one
# org, always use the first active org, unless no org exists.
if multiple_orgs:
if 'organization' not in team_opts:
continue
org = Organization.objects.get_or_create(name=team_opts['organization'])[0]
else:
try:
org = Organization.objects.order_by('pk')[0]
except IndexError:
continue
# Update team members from expression(s).
team = Team.objects.get_or_create(name=team_name, organization=org)[0]
@@ -196,7 +169,6 @@ def update_user_teams_by_saml_attr(backend, details, user=None, *args, **kwargs)
return
from awx.main.models import Organization, Team
from django.conf import settings
multiple_orgs = feature_enabled('multiple_organizations')
team_map = settings.SOCIAL_AUTH_SAML_TEAM_ATTR
if team_map.get('saml_attr') is None:
return
@@ -210,17 +182,11 @@ def update_user_teams_by_saml_attr(backend, details, user=None, *args, **kwargs)
for team_name_map in team_map.get('team_org_map', []):
team_name = team_name_map.get('team', '')
if team_name in saml_team_names:
if multiple_orgs:
if not team_name_map.get('organization', ''):
# Settings field validation should prevent this.
logger.error("organization name invalid for team {}".format(team_name))
continue
org = Organization.objects.get_or_create(name=team_name_map['organization'])[0]
else:
try:
org = Organization.objects.order_by('pk')[0]
except IndexError:
continue
if not team_name_map.get('organization', ''):
# Settings field validation should prevent this.
logger.error("organization name invalid for team {}".format(team_name))
continue
org = Organization.objects.get_or_create(name=team_name_map['organization'])[0]
team = Team.objects.get_or_create(name=team_name, organization=org)[0]
team_ids.append(team.id)

View File

@@ -31,21 +31,3 @@ def existing_tacacsplus_user():
enterprise_auth = UserEnterpriseAuth(user=user, provider='tacacs+')
enterprise_auth.save()
return user
@pytest.fixture
def feature_enabled():
def func(feature):
def inner(name):
return name == feature
return inner
return func
@pytest.fixture
def feature_disabled():
def func(feature):
def inner(name):
return False
return inner
return func

View File

@@ -8,24 +8,11 @@ def test_empty_host_fails_auth(tacacsplus_backend):
assert ret_user is None
def test_disabled_enterprise_auth_fails_auth(tacacsplus_backend, feature_disabled):
with mock.patch('awx.sso.backends.django_settings') as settings,\
mock.patch('awx.sso.backends.logger') as logger,\
mock.patch('awx.sso.backends.feature_enabled', feature_disabled('enterprise_auth')):
settings.TACACSPLUS_HOST = 'localhost'
ret_user = tacacsplus_backend.authenticate(u"user", u"pass")
assert ret_user is None
logger.error.assert_called_once_with(
"Unable to authenticate, license does not support TACACS+ authentication"
)
def test_client_raises_exception(tacacsplus_backend, feature_enabled):
def test_client_raises_exception(tacacsplus_backend):
client = mock.MagicMock()
client.authenticate.side_effect=Exception("foo")
with mock.patch('awx.sso.backends.django_settings') as settings,\
mock.patch('awx.sso.backends.logger') as logger,\
mock.patch('awx.sso.backends.feature_enabled', feature_enabled('enterprise_auth')),\
mock.patch('tacacs_plus.TACACSClient', return_value=client):
settings.TACACSPLUS_HOST = 'localhost'
settings.TACACSPLUS_AUTH_PROTOCOL = 'ascii'
@@ -36,13 +23,12 @@ def test_client_raises_exception(tacacsplus_backend, feature_enabled):
)
def test_client_return_invalid_fails_auth(tacacsplus_backend, feature_enabled):
def test_client_return_invalid_fails_auth(tacacsplus_backend):
auth = mock.MagicMock()
auth.valid = False
client = mock.MagicMock()
client.authenticate.return_value = auth
with mock.patch('awx.sso.backends.django_settings') as settings,\
mock.patch('awx.sso.backends.feature_enabled', feature_enabled('enterprise_auth')),\
mock.patch('tacacs_plus.TACACSClient', return_value=client):
settings.TACACSPLUS_HOST = 'localhost'
settings.TACACSPLUS_AUTH_PROTOCOL = 'ascii'
@@ -50,7 +36,7 @@ def test_client_return_invalid_fails_auth(tacacsplus_backend, feature_enabled):
assert ret_user is None
def test_client_return_valid_passes_auth(tacacsplus_backend, feature_enabled):
def test_client_return_valid_passes_auth(tacacsplus_backend):
auth = mock.MagicMock()
auth.valid = True
client = mock.MagicMock()
@@ -58,7 +44,6 @@ def test_client_return_valid_passes_auth(tacacsplus_backend, feature_enabled):
user = mock.MagicMock()
user.has_usable_password = mock.MagicMock(return_value=False)
with mock.patch('awx.sso.backends.django_settings') as settings,\
mock.patch('awx.sso.backends.feature_enabled', feature_enabled('enterprise_auth')),\
mock.patch('tacacs_plus.TACACSClient', return_value=client),\
mock.patch('awx.sso.backends._get_or_set_enterprise_user', return_value=user):
settings.TACACSPLUS_HOST = 'localhost'