mirror of
https://github.com/ansible/awx.git
synced 2026-05-02 23:25:29 -02:30
Remove TACACS+ authentication (#15547)
Remove TACACS+ authentication from AWX. Co-authored-by: Hao Liu <44379968+TheRealHaoLiu@users.noreply.github.com>
This commit is contained in:
committed by
jessicamack
parent
f22b192fb4
commit
e4c11561cc
26
awx/conf/migrations/0011_remove_tacacs_plus_auth_conf.py
Normal file
26
awx/conf/migrations/0011_remove_tacacs_plus_auth_conf.py
Normal file
@@ -0,0 +1,26 @@
|
||||
from django.db import migrations
|
||||
|
||||
TACACS_PLUS_AUTH_CONF_KEYS = [
|
||||
'TACACSPLUS_HOST',
|
||||
'TACACSPLUS_PORT',
|
||||
'TACACSPLUS_SECRET',
|
||||
'TACACSPLUS_SESSION_TIMEOUT',
|
||||
'TACACSPLUS_AUTH_PROTOCOL',
|
||||
'TACACSPLUS_REM_ADDR',
|
||||
]
|
||||
|
||||
|
||||
def remove_tacacs_plus_auth_conf(apps, scheme_editor):
|
||||
setting = apps.get_model('conf', 'Setting')
|
||||
setting.objects.filter(key__in=TACACS_PLUS_AUTH_CONF_KEYS).delete()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('conf', '0010_change_to_JSONField'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(remove_tacacs_plus_auth_conf),
|
||||
]
|
||||
@@ -98,21 +98,6 @@ def test_radius_settings(get, put, patch, delete, admin, settings):
|
||||
assert settings.RADIUS_SECRET == ''
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_tacacsplus_settings(get, put, patch, admin):
|
||||
url = reverse('api:setting_singleton_detail', kwargs={'category_slug': 'tacacsplus'})
|
||||
response = get(url, user=admin, expect=200)
|
||||
put(url, user=admin, data=response.data, expect=200)
|
||||
patch(url, user=admin, data={'TACACSPLUS_SECRET': 'mysecret'}, expect=200)
|
||||
patch(url, user=admin, data={'TACACSPLUS_SECRET': ''}, expect=200)
|
||||
patch(url, user=admin, data={'TACACSPLUS_HOST': 'localhost'}, expect=400)
|
||||
patch(url, user=admin, data={'TACACSPLUS_SECRET': 'mysecret'}, expect=200)
|
||||
patch(url, user=admin, data={'TACACSPLUS_HOST': 'localhost'}, expect=200)
|
||||
patch(url, user=admin, data={'TACACSPLUS_HOST': '', 'TACACSPLUS_SECRET': ''}, expect=200)
|
||||
patch(url, user=admin, data={'TACACSPLUS_HOST': 'localhost', 'TACACSPLUS_SECRET': ''}, expect=400)
|
||||
patch(url, user=admin, data={'TACACSPLUS_HOST': 'localhost', 'TACACSPLUS_SECRET': 'mysecret'}, expect=200)
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_ui_settings(get, put, patch, delete, admin):
|
||||
url = reverse('api:setting_singleton_detail', kwargs={'category_slug': 'ui'})
|
||||
|
||||
@@ -393,7 +393,6 @@ REST_FRAMEWORK = {
|
||||
|
||||
AUTHENTICATION_BACKENDS = (
|
||||
'awx.sso.backends.RADIUSBackend',
|
||||
'awx.sso.backends.TACACSPlusBackend',
|
||||
'social_core.backends.google.GoogleOAuth2',
|
||||
'social_core.backends.github.GithubOAuth2',
|
||||
'social_core.backends.github.GithubOrganizationOAuth2',
|
||||
@@ -424,15 +423,6 @@ RADIUS_SERVER = ''
|
||||
RADIUS_PORT = 1812
|
||||
RADIUS_SECRET = ''
|
||||
|
||||
# TACACS+ settings (default host to empty string to skip using TACACS+ auth).
|
||||
# Note: These settings may be overridden by database settings.
|
||||
TACACSPLUS_HOST = ''
|
||||
TACACSPLUS_PORT = 49
|
||||
TACACSPLUS_SECRET = ''
|
||||
TACACSPLUS_SESSION_TIMEOUT = 5
|
||||
TACACSPLUS_AUTH_PROTOCOL = 'ascii'
|
||||
TACACSPLUS_REM_ADDR = False
|
||||
|
||||
# Enable / Disable HTTP Basic Authentication used in the API browser
|
||||
# Note: Session limits are not enforced when using HTTP Basic Authentication.
|
||||
# Note: This setting may be overridden by database settings.
|
||||
|
||||
@@ -13,9 +13,6 @@ from django.http import HttpResponse
|
||||
# radiusauth
|
||||
from radiusauth.backends import RADIUSBackend as BaseRADIUSBackend
|
||||
|
||||
# tacacs+ auth
|
||||
import tacacs_plus
|
||||
|
||||
# social
|
||||
from social_core.backends.saml import OID_USERID
|
||||
from social_core.backends.saml import SAMLAuth as BaseSAMLAuth
|
||||
@@ -69,54 +66,6 @@ class RADIUSBackend(BaseRADIUSBackend):
|
||||
return _get_or_set_enterprise_user(force_str(username), force_str(password), 'radius')
|
||||
|
||||
|
||||
class TACACSPlusBackend(object):
|
||||
"""
|
||||
Custom TACACS+ auth backend for AWX
|
||||
"""
|
||||
|
||||
def authenticate(self, request, username, password):
|
||||
if not django_settings.TACACSPLUS_HOST:
|
||||
return None
|
||||
try:
|
||||
# Upstream TACACS+ client does not accept non-string, so convert if needed.
|
||||
tacacs_client = tacacs_plus.TACACSClient(
|
||||
django_settings.TACACSPLUS_HOST,
|
||||
django_settings.TACACSPLUS_PORT,
|
||||
django_settings.TACACSPLUS_SECRET,
|
||||
timeout=django_settings.TACACSPLUS_SESSION_TIMEOUT,
|
||||
)
|
||||
auth_kwargs = {'authen_type': tacacs_plus.TAC_PLUS_AUTHEN_TYPES[django_settings.TACACSPLUS_AUTH_PROTOCOL]}
|
||||
if django_settings.TACACSPLUS_AUTH_PROTOCOL:
|
||||
client_ip = self._get_client_ip(request)
|
||||
if client_ip:
|
||||
auth_kwargs['rem_addr'] = client_ip
|
||||
auth = tacacs_client.authenticate(username, password, **auth_kwargs)
|
||||
except Exception as e:
|
||||
logger.exception("TACACS+ Authentication Error: %s" % str(e))
|
||||
return None
|
||||
if auth.valid:
|
||||
return _get_or_set_enterprise_user(username, password, 'tacacs+')
|
||||
|
||||
def get_user(self, user_id):
|
||||
if not django_settings.TACACSPLUS_HOST:
|
||||
return None
|
||||
try:
|
||||
return User.objects.get(pk=user_id)
|
||||
except User.DoesNotExist:
|
||||
return None
|
||||
|
||||
def _get_client_ip(self, request):
|
||||
if not request or not hasattr(request, 'META'):
|
||||
return None
|
||||
|
||||
x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
|
||||
if x_forwarded_for:
|
||||
ip = x_forwarded_for.split(',')[0]
|
||||
else:
|
||||
ip = request.META.get('REMOTE_ADDR')
|
||||
return ip
|
||||
|
||||
|
||||
class TowerSAMLIdentityProvider(BaseSAMLIdentityProvider):
|
||||
"""
|
||||
Custom Identity Provider to make attributes to what we expect.
|
||||
|
||||
@@ -186,11 +186,9 @@ def get_external_account(user):
|
||||
def is_remote_auth_enabled():
|
||||
from django.conf import settings
|
||||
|
||||
# Append Radius, TACACS+ and SAML options
|
||||
settings_that_turn_on_remote_auth = [
|
||||
'SOCIAL_AUTH_SAML_ENABLED_IDPS',
|
||||
'RADIUS_SERVER',
|
||||
'TACACSPLUS_HOST',
|
||||
]
|
||||
# Also include any SOCAIL_AUTH_*KEY (except SAML)
|
||||
for social_auth_key in dir(settings):
|
||||
|
||||
@@ -7,11 +7,8 @@ from django.conf import settings
|
||||
from django.urls import reverse
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
# Django REST Framework
|
||||
from rest_framework import serializers
|
||||
|
||||
# AWX
|
||||
from awx.conf import register, register_validate, fields
|
||||
from awx.conf import register, fields
|
||||
from awx.sso.fields import (
|
||||
AuthenticationBackendsField,
|
||||
SAMLContactField,
|
||||
@@ -25,7 +22,6 @@ from awx.sso.fields import (
|
||||
SocialTeamMapField,
|
||||
)
|
||||
from awx.main.validators import validate_private_key, validate_certificate
|
||||
from awx.sso.validators import validate_tacacsplus_disallow_nonascii # noqa
|
||||
|
||||
|
||||
class SocialAuthCallbackURL(object):
|
||||
@@ -187,79 +183,6 @@ if settings.ALLOW_LOCAL_RESOURCE_MANAGEMENT:
|
||||
encrypted=True,
|
||||
)
|
||||
|
||||
###############################################################################
|
||||
# TACACSPLUS AUTHENTICATION SETTINGS
|
||||
###############################################################################
|
||||
|
||||
register(
|
||||
'TACACSPLUS_HOST',
|
||||
field_class=fields.CharField,
|
||||
allow_blank=True,
|
||||
default='',
|
||||
label=_('TACACS+ Server'),
|
||||
help_text=_('Hostname of TACACS+ server.'),
|
||||
category=_('TACACS+'),
|
||||
category_slug='tacacsplus',
|
||||
)
|
||||
|
||||
register(
|
||||
'TACACSPLUS_PORT',
|
||||
field_class=fields.IntegerField,
|
||||
min_value=1,
|
||||
max_value=65535,
|
||||
default=49,
|
||||
label=_('TACACS+ Port'),
|
||||
help_text=_('Port number of TACACS+ server.'),
|
||||
category=_('TACACS+'),
|
||||
category_slug='tacacsplus',
|
||||
)
|
||||
|
||||
register(
|
||||
'TACACSPLUS_SECRET',
|
||||
field_class=fields.CharField,
|
||||
allow_blank=True,
|
||||
default='',
|
||||
validators=[validate_tacacsplus_disallow_nonascii],
|
||||
label=_('TACACS+ Secret'),
|
||||
help_text=_('Shared secret for authenticating to TACACS+ server.'),
|
||||
category=_('TACACS+'),
|
||||
category_slug='tacacsplus',
|
||||
encrypted=True,
|
||||
)
|
||||
|
||||
register(
|
||||
'TACACSPLUS_SESSION_TIMEOUT',
|
||||
field_class=fields.IntegerField,
|
||||
min_value=0,
|
||||
default=5,
|
||||
label=_('TACACS+ Auth Session Timeout'),
|
||||
help_text=_('TACACS+ session timeout value in seconds, 0 disables timeout.'),
|
||||
category=_('TACACS+'),
|
||||
category_slug='tacacsplus',
|
||||
unit=_('seconds'),
|
||||
)
|
||||
|
||||
register(
|
||||
'TACACSPLUS_AUTH_PROTOCOL',
|
||||
field_class=fields.ChoiceField,
|
||||
choices=['ascii', 'pap'],
|
||||
default='ascii',
|
||||
label=_('TACACS+ Authentication Protocol'),
|
||||
help_text=_('Choose the authentication protocol used by TACACS+ client.'),
|
||||
category=_('TACACS+'),
|
||||
category_slug='tacacsplus',
|
||||
)
|
||||
|
||||
register(
|
||||
'TACACSPLUS_REM_ADDR',
|
||||
field_class=fields.BooleanField,
|
||||
default=True,
|
||||
label=_('TACACS+ client address sending enabled'),
|
||||
help_text=_('Enable the client address sending by TACACS+ client.'),
|
||||
category=_('TACACS+'),
|
||||
category_slug='tacacsplus',
|
||||
)
|
||||
|
||||
###############################################################################
|
||||
# GOOGLE OAUTH2 AUTHENTICATION SETTINGS
|
||||
###############################################################################
|
||||
@@ -1344,21 +1267,3 @@ if settings.ALLOW_LOCAL_RESOURCE_MANAGEMENT:
|
||||
category=_('Authentication'),
|
||||
category_slug='authentication',
|
||||
)
|
||||
|
||||
def tacacs_validate(serializer, attrs):
|
||||
if not serializer.instance or not hasattr(serializer.instance, 'TACACSPLUS_HOST') or not hasattr(serializer.instance, 'TACACSPLUS_SECRET'):
|
||||
return attrs
|
||||
errors = []
|
||||
host = serializer.instance.TACACSPLUS_HOST
|
||||
if 'TACACSPLUS_HOST' in attrs:
|
||||
host = attrs['TACACSPLUS_HOST']
|
||||
secret = serializer.instance.TACACSPLUS_SECRET
|
||||
if 'TACACSPLUS_SECRET' in attrs:
|
||||
secret = attrs['TACACSPLUS_SECRET']
|
||||
if host and not secret:
|
||||
errors.append('TACACSPLUS_SECRET is required when TACACSPLUS_HOST is provided.')
|
||||
if errors:
|
||||
raise serializers.ValidationError(_('\n'.join(errors)))
|
||||
return attrs
|
||||
|
||||
register_validate('tacacsplus', tacacs_validate)
|
||||
|
||||
@@ -14,7 +14,6 @@ from rest_framework.fields import empty, Field, SkipField
|
||||
# AWX
|
||||
from awx.conf import fields
|
||||
from awx.main.validators import validate_certificate
|
||||
from awx.sso.validators import validate_tacacsplus_disallow_nonascii # noqa
|
||||
|
||||
|
||||
def get_subclasses(cls):
|
||||
|
||||
@@ -7,6 +7,7 @@ from django.contrib.auth.models import User
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
|
||||
# todo: this model to be removed as part of sso removal issue AAP-28380
|
||||
class UserEnterpriseAuth(models.Model):
|
||||
"""Enterprise Auth association model"""
|
||||
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
import pytest
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
|
||||
from awx.sso.backends import TACACSPlusBackend
|
||||
from awx.sso.models import UserEnterpriseAuth
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def tacacsplus_backend():
|
||||
return TACACSPlusBackend()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def existing_normal_user():
|
||||
try:
|
||||
user = User.objects.get(username="alice")
|
||||
except User.DoesNotExist:
|
||||
user = User(username="alice", password="password")
|
||||
user.save()
|
||||
return user
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def existing_tacacsplus_user():
|
||||
try:
|
||||
user = User.objects.get(username="foo")
|
||||
except User.DoesNotExist:
|
||||
user = User(username="foo")
|
||||
user.set_unusable_password()
|
||||
user.save()
|
||||
enterprise_auth = UserEnterpriseAuth(user=user, provider='tacacs+')
|
||||
enterprise_auth.save()
|
||||
return user
|
||||
@@ -324,7 +324,7 @@ class TestCommonFunctions:
|
||||
if enable_enterprise:
|
||||
from awx.sso.models import UserEnterpriseAuth
|
||||
|
||||
enterprise_auth = UserEnterpriseAuth(user=user, provider='tacacs+')
|
||||
enterprise_auth = UserEnterpriseAuth(user=user, provider='saml')
|
||||
enterprise_auth.save()
|
||||
|
||||
assert get_external_account(user) == expected_results
|
||||
@@ -336,7 +336,6 @@ class TestCommonFunctions:
|
||||
('JUNK_SETTING', False),
|
||||
('SOCIAL_AUTH_SAML_ENABLED_IDPS', True),
|
||||
('RADIUS_SERVER', True),
|
||||
('TACACSPLUS_HOST', True),
|
||||
# Set some SOCIAL_SOCIAL_AUTH_OIDC_KEYAUTH_*_KEY settings
|
||||
('SOCIAL_AUTH_AZUREAD_OAUTH2_KEY', True),
|
||||
('SOCIAL_AUTH_GITHUB_ENTERPRISE_KEY', True),
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
# Python
|
||||
import pytest
|
||||
from unittest import mock
|
||||
|
||||
# AWX
|
||||
from awx.sso.backends import _get_or_set_enterprise_user
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_fetch_user_if_exist(existing_tacacsplus_user):
|
||||
with mock.patch('awx.sso.backends.logger') as mocked_logger:
|
||||
new_user = _get_or_set_enterprise_user("foo", "password", "tacacs+")
|
||||
mocked_logger.debug.assert_not_called()
|
||||
mocked_logger.warning.assert_not_called()
|
||||
assert new_user == existing_tacacsplus_user
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_create_user_if_not_exist(existing_tacacsplus_user):
|
||||
with mock.patch('awx.sso.backends.logger') as mocked_logger:
|
||||
new_user = _get_or_set_enterprise_user("bar", "password", "tacacs+")
|
||||
mocked_logger.debug.assert_called_once_with(u'Created enterprise user bar via TACACS+ backend.')
|
||||
assert new_user != existing_tacacsplus_user
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_created_user_has_no_usable_password():
|
||||
new_user = _get_or_set_enterprise_user("bar", "password", "tacacs+")
|
||||
assert not new_user.has_usable_password()
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_non_enterprise_user_does_not_get_pass(existing_normal_user):
|
||||
with mock.patch('awx.sso.backends.logger') as mocked_logger:
|
||||
new_user = _get_or_set_enterprise_user("alice", "password", "tacacs+")
|
||||
mocked_logger.warning.assert_called_once_with(u'Enterprise user alice already defined in Tower.')
|
||||
assert new_user is None
|
||||
@@ -1,116 +0,0 @@
|
||||
from unittest import mock
|
||||
import pytest
|
||||
|
||||
|
||||
def test_empty_host_fails_auth(tacacsplus_backend):
|
||||
with mock.patch('awx.sso.backends.django_settings') as settings:
|
||||
settings.TACACSPLUS_HOST = ''
|
||||
ret_user = tacacsplus_backend.authenticate(None, u"user", u"pass")
|
||||
assert ret_user is None
|
||||
|
||||
|
||||
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(
|
||||
'tacacs_plus.TACACSClient', return_value=client
|
||||
):
|
||||
settings.TACACSPLUS_HOST = 'localhost'
|
||||
settings.TACACSPLUS_AUTH_PROTOCOL = 'ascii'
|
||||
ret_user = tacacsplus_backend.authenticate(None, u"user", u"pass")
|
||||
assert ret_user is None
|
||||
logger.exception.assert_called_once_with("TACACS+ Authentication Error: foo")
|
||||
|
||||
|
||||
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('tacacs_plus.TACACSClient', return_value=client):
|
||||
settings.TACACSPLUS_HOST = 'localhost'
|
||||
settings.TACACSPLUS_AUTH_PROTOCOL = 'ascii'
|
||||
ret_user = tacacsplus_backend.authenticate(None, u"user", u"pass")
|
||||
assert ret_user is None
|
||||
|
||||
|
||||
def test_client_return_valid_passes_auth(tacacsplus_backend):
|
||||
auth = mock.MagicMock()
|
||||
auth.valid = True
|
||||
client = mock.MagicMock()
|
||||
client.authenticate.return_value = auth
|
||||
user = mock.MagicMock()
|
||||
user.has_usable_password = mock.MagicMock(return_value=False)
|
||||
with mock.patch('awx.sso.backends.django_settings') as settings, 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'
|
||||
settings.TACACSPLUS_AUTH_PROTOCOL = 'ascii'
|
||||
ret_user = tacacsplus_backend.authenticate(None, u"user", u"pass")
|
||||
assert ret_user == user
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"client_ip_header,client_ip_header_value,expected_client_ip",
|
||||
[('HTTP_X_FORWARDED_FOR', '12.34.56.78, 23.45.67.89', '12.34.56.78'), ('REMOTE_ADDR', '12.34.56.78', '12.34.56.78')],
|
||||
)
|
||||
def test_remote_addr_is_passed_to_client_if_available_and_setting_enabled(tacacsplus_backend, client_ip_header, client_ip_header_value, expected_client_ip):
|
||||
auth = mock.MagicMock()
|
||||
auth.valid = True
|
||||
client = mock.MagicMock()
|
||||
client.authenticate.return_value = auth
|
||||
user = mock.MagicMock()
|
||||
user.has_usable_password = mock.MagicMock(return_value=False)
|
||||
request = mock.MagicMock()
|
||||
request.META = {
|
||||
client_ip_header: client_ip_header_value,
|
||||
}
|
||||
with mock.patch('awx.sso.backends.django_settings') as settings, 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'
|
||||
settings.TACACSPLUS_AUTH_PROTOCOL = 'ascii'
|
||||
settings.TACACSPLUS_REM_ADDR = True
|
||||
tacacsplus_backend.authenticate(request, u"user", u"pass")
|
||||
|
||||
client.authenticate.assert_called_once_with('user', 'pass', authen_type=1, rem_addr=expected_client_ip)
|
||||
|
||||
|
||||
def test_remote_addr_is_completely_ignored_in_client_call_if_setting_is_disabled(tacacsplus_backend):
|
||||
auth = mock.MagicMock()
|
||||
auth.valid = True
|
||||
client = mock.MagicMock()
|
||||
client.authenticate.return_value = auth
|
||||
user = mock.MagicMock()
|
||||
user.has_usable_password = mock.MagicMock(return_value=False)
|
||||
request = mock.MagicMock()
|
||||
request.META = {}
|
||||
with mock.patch('awx.sso.backends.django_settings') as settings, 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'
|
||||
settings.TACACSPLUS_AUTH_PROTOCOL = 'ascii'
|
||||
settings.TACACSPLUS_REM_ADDR = False
|
||||
tacacsplus_backend.authenticate(request, u"user", u"pass")
|
||||
|
||||
client.authenticate.assert_called_once_with('user', 'pass', authen_type=1)
|
||||
|
||||
|
||||
def test_remote_addr_is_completely_ignored_in_client_call_if_unavailable_and_setting_enabled(tacacsplus_backend):
|
||||
auth = mock.MagicMock()
|
||||
auth.valid = True
|
||||
client = mock.MagicMock()
|
||||
client.authenticate.return_value = auth
|
||||
user = mock.MagicMock()
|
||||
user.has_usable_password = mock.MagicMock(return_value=False)
|
||||
request = mock.MagicMock()
|
||||
request.META = {}
|
||||
with mock.patch('awx.sso.backends.django_settings') as settings, 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'
|
||||
settings.TACACSPLUS_AUTH_PROTOCOL = 'ascii'
|
||||
settings.TACACSPLUS_REM_ADDR = True
|
||||
tacacsplus_backend.authenticate(request, u"user", u"pass")
|
||||
|
||||
client.authenticate.assert_called_once_with('user', 'pass', authen_type=1)
|
||||
@@ -2,13 +2,4 @@
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
__all__ = [
|
||||
'validate_tacacsplus_disallow_nonascii',
|
||||
]
|
||||
|
||||
|
||||
def validate_tacacsplus_disallow_nonascii(value):
|
||||
try:
|
||||
value.encode('ascii')
|
||||
except (UnicodeEncodeError, UnicodeDecodeError):
|
||||
raise ValidationError(_('TACACS+ secret does not allow non-ascii characters'))
|
||||
__all__ = []
|
||||
|
||||
Reference in New Issue
Block a user