mirror of
https://github.com/ansible/awx.git
synced 2026-01-11 18:09:57 -03:30
Merge pull request #10102 from jbradberry/disable-local-users
Add the ability to disable local authentication SUMMARY When an external authentication system is enabled, users would like the ability to disable local authentication for enhanced security. related #4553 TODO create a configure-Tower-in-Tower setting, DISABLE_LOCAL_AUTH expose the setting in the settings UI be able to query out all local-only users User.objects.filter(Q(profile__isnull=True) | Q(profile__ldap_dn=''), enterprise_auth__isnull=True, social_auth__isnull=True) see: awx/main/utils/common.py, get_external_account write a thin wrapper around the Django model-based auth backend update the UI tests to include the new setting be able to trigger a side-effect when this setting changes revoke all OAuth2 tokens for users that do not have a remote auth backend associated with them revoke sessions for local-only users ultimately I did this by adding a new middleware that checks the value of this new setting and force-logouts any local-only user making a request after it is enabled settings API endpoint raises a validation error if there are no external users or auth sources configured The remote user existence validation has been removed, since ultimately we can't know for sure if a sysadmin-level user will still have access to the UI. This is being dealt with by using a confirmation modal, see below. add a modal asking the user to confirm that they want to turn this setting on ISSUE TYPE Feature Pull Request COMPONENT NAME API UI AWX VERSION Reviewed-by: Jeff Bradberry <None> Reviewed-by: Bianca Henderson <beeankha@gmail.com> Reviewed-by: Mat Wilson <mawilson@redhat.com> Reviewed-by: Michael Abashian <None> Reviewed-by: Chris Meyers <None>
This commit is contained in:
commit
c29a7ccf8b
@ -1,8 +1,12 @@
|
||||
# Django
|
||||
from django.conf import settings
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
# Django REST Framework
|
||||
from rest_framework import serializers
|
||||
|
||||
# AWX
|
||||
from awx.conf import fields, register
|
||||
from awx.conf import fields, register, register_validate
|
||||
from awx.api.fields import OAuth2ProviderField
|
||||
from oauth2_provider.settings import oauth2_settings
|
||||
|
||||
@ -27,6 +31,17 @@ register(
|
||||
category=_('Authentication'),
|
||||
category_slug='authentication',
|
||||
)
|
||||
register(
|
||||
'DISABLE_LOCAL_AUTH',
|
||||
field_class=fields.BooleanField,
|
||||
label=_('Disable the built-in authentication system'),
|
||||
help_text=_(
|
||||
"Controls whether users are prevented from using the built-in authentication system. "
|
||||
"You probably want to do this if you are using an LDAP or SAML integration."
|
||||
),
|
||||
category=_('Authentication'),
|
||||
category_slug='authentication',
|
||||
)
|
||||
register(
|
||||
'AUTH_BASIC_ENABLED',
|
||||
field_class=fields.BooleanField,
|
||||
@ -81,3 +96,23 @@ register(
|
||||
category=_('Authentication'),
|
||||
category_slug='authentication',
|
||||
)
|
||||
|
||||
|
||||
def authentication_validate(serializer, attrs):
|
||||
remote_auth_settings = [
|
||||
'AUTH_LDAP_SERVER_URI',
|
||||
'SOCIAL_AUTH_GOOGLE_OAUTH2_KEY',
|
||||
'SOCIAL_AUTH_GITHUB_KEY',
|
||||
'SOCIAL_AUTH_GITHUB_ORG_KEY',
|
||||
'SOCIAL_AUTH_GITHUB_TEAM_KEY',
|
||||
'SOCIAL_AUTH_SAML_ENABLED_IDPS',
|
||||
'RADIUS_SERVER',
|
||||
'TACACSPLUS_HOST',
|
||||
]
|
||||
if attrs.get('DISABLE_LOCAL_AUTH', False):
|
||||
if not any(getattr(settings, s, None) for s in remote_auth_settings):
|
||||
raise serializers.ValidationError(_("There are no remote authentication systems configured."))
|
||||
return attrs
|
||||
|
||||
|
||||
register_validate('authentication', authentication_validate)
|
||||
|
||||
@ -3,9 +3,9 @@ import logging
|
||||
|
||||
# Django
|
||||
from django.conf import settings
|
||||
from django.core.cache import cache
|
||||
from django.core.signals import setting_changed
|
||||
from django.db.models.signals import post_save, pre_delete, post_delete
|
||||
from django.core.cache import cache
|
||||
from django.dispatch import receiver
|
||||
|
||||
# AWX
|
||||
@ -25,7 +25,7 @@ def handle_setting_change(key, for_delete=False):
|
||||
# Note: Doesn't handle multiple levels of dependencies!
|
||||
setting_keys.append(dependent_key)
|
||||
# NOTE: This block is probably duplicated.
|
||||
cache_keys = set([Setting.get_cache_key(k) for k in setting_keys])
|
||||
cache_keys = {Setting.get_cache_key(k) for k in setting_keys}
|
||||
cache.delete_many(cache_keys)
|
||||
|
||||
# Send setting_changed signal with new value for each setting.
|
||||
@ -58,3 +58,18 @@ def on_post_delete_setting(sender, **kwargs):
|
||||
key = getattr(instance, '_saved_key_', None)
|
||||
if key:
|
||||
handle_setting_change(key, True)
|
||||
|
||||
|
||||
@receiver(setting_changed)
|
||||
def disable_local_auth(**kwargs):
|
||||
if (kwargs['setting'], kwargs['value']) == ('DISABLE_LOCAL_AUTH', True):
|
||||
from django.contrib.auth.models import User
|
||||
from oauth2_provider.models import RefreshToken
|
||||
from awx.main.models.oauth import OAuth2AccessToken
|
||||
from awx.main.management.commands.revoke_oauth2_tokens import revoke_tokens
|
||||
|
||||
logger.warning("Triggering token invalidation for local users.")
|
||||
|
||||
qs = User.objects.filter(profile__ldap_dn='', enterprise_auth__isnull=True, social_auth__isnull=True)
|
||||
revoke_tokens(RefreshToken.objects.filter(revoked=None, user__in=qs))
|
||||
revoke_tokens(OAuth2AccessToken.objects.filter(user__in=qs))
|
||||
|
||||
14
awx/main/backends.py
Normal file
14
awx/main/backends.py
Normal file
@ -0,0 +1,14 @@
|
||||
import logging
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.backends import ModelBackend
|
||||
|
||||
logger = logging.getLogger('awx.main.backends')
|
||||
|
||||
|
||||
class AWXModelBackend(ModelBackend):
|
||||
def authenticate(self, request, **kwargs):
|
||||
if settings.DISABLE_LOCAL_AUTH:
|
||||
logger.warning(f"User '{kwargs['username']}' attempted login through the disabled local authentication system.")
|
||||
return
|
||||
return super().authenticate(request, **kwargs)
|
||||
@ -36,7 +36,7 @@ register(
|
||||
'ORG_ADMINS_CAN_SEE_ALL_USERS',
|
||||
field_class=fields.BooleanField,
|
||||
label=_('All Users Visible to Organization Admins'),
|
||||
help_text=_('Controls whether any Organization Admin can view all users and teams, ' 'even those not associated with their Organization.'),
|
||||
help_text=_('Controls whether any Organization Admin can view all users and teams, even those not associated with their Organization.'),
|
||||
category=_('System'),
|
||||
category_slug='system',
|
||||
)
|
||||
@ -59,7 +59,7 @@ register(
|
||||
schemes=('http', 'https'),
|
||||
allow_plain_hostname=True, # Allow hostname only without TLD.
|
||||
label=_('Base URL of the service'),
|
||||
help_text=_('This setting is used by services like notifications to render ' 'a valid url to the service.'),
|
||||
help_text=_('This setting is used by services like notifications to render a valid url to the service.'),
|
||||
category=_('System'),
|
||||
category_slug='system',
|
||||
)
|
||||
@ -94,13 +94,12 @@ register(
|
||||
category_slug='system',
|
||||
)
|
||||
|
||||
|
||||
register(
|
||||
'LICENSE',
|
||||
field_class=fields.DictField,
|
||||
default=lambda: {},
|
||||
label=_('License'),
|
||||
help_text=_('The license controls which features and functionality are ' 'enabled. Use /api/v2/config/ to update or change ' 'the license.'),
|
||||
help_text=_('The license controls which features and functionality are enabled. Use /api/v2/config/ to update or change the license.'),
|
||||
category=_('System'),
|
||||
category_slug='system',
|
||||
)
|
||||
@ -194,7 +193,7 @@ register(
|
||||
'CUSTOM_VENV_PATHS',
|
||||
field_class=fields.StringListPathField,
|
||||
label=_('Custom virtual environment paths'),
|
||||
help_text=_('Paths where Tower will look for custom virtual environments ' '(in addition to /var/lib/awx/venv/). Enter one path per line.'),
|
||||
help_text=_('Paths where Tower will look for custom virtual environments (in addition to /var/lib/awx/venv/). Enter one path per line.'),
|
||||
category=_('System'),
|
||||
category_slug='system',
|
||||
default=[],
|
||||
@ -318,7 +317,7 @@ register(
|
||||
field_class=fields.BooleanField,
|
||||
default=False,
|
||||
label=_('Ignore Ansible Galaxy SSL Certificate Verification'),
|
||||
help_text=_('If set to true, certificate validation will not be done when ' 'installing content from any Galaxy server.'),
|
||||
help_text=_('If set to true, certificate validation will not be done when installing content from any Galaxy server.'),
|
||||
category=_('Jobs'),
|
||||
category_slug='jobs',
|
||||
)
|
||||
@ -433,7 +432,7 @@ register(
|
||||
allow_null=False,
|
||||
default=200,
|
||||
label=_('Maximum number of forks per job'),
|
||||
help_text=_('Saving a Job Template with more than this number of forks will result in an error. ' 'When set to 0, no limit is applied.'),
|
||||
help_text=_('Saving a Job Template with more than this number of forks will result in an error. When set to 0, no limit is applied.'),
|
||||
category=_('Jobs'),
|
||||
category_slug='jobs',
|
||||
)
|
||||
@ -454,7 +453,7 @@ register(
|
||||
allow_null=True,
|
||||
default=None,
|
||||
label=_('Logging Aggregator Port'),
|
||||
help_text=_('Port on Logging Aggregator to send logs to (if required and not' ' provided in Logging Aggregator).'),
|
||||
help_text=_('Port on Logging Aggregator to send logs to (if required and not provided in Logging Aggregator).'),
|
||||
category=_('Logging'),
|
||||
category_slug='logging',
|
||||
required=False,
|
||||
@ -561,7 +560,7 @@ register(
|
||||
field_class=fields.IntegerField,
|
||||
default=5,
|
||||
label=_('TCP Connection Timeout'),
|
||||
help_text=_('Number of seconds for a TCP connection to external log ' 'aggregator to timeout. Applies to HTTPS and TCP log ' 'aggregator protocols.'),
|
||||
help_text=_('Number of seconds for a TCP connection to external log aggregator to timeout. Applies to HTTPS and TCP log aggregator protocols.'),
|
||||
category=_('Logging'),
|
||||
category_slug='logging',
|
||||
unit=_('seconds'),
|
||||
@ -627,7 +626,7 @@ register(
|
||||
field_class=fields.BooleanField,
|
||||
default=False,
|
||||
label=_('Enable rsyslogd debugging'),
|
||||
help_text=_('Enabled high verbosity debugging for rsyslogd. ' 'Useful for debugging connection issues for external log aggregation.'),
|
||||
help_text=_('Enabled high verbosity debugging for rsyslogd. Useful for debugging connection issues for external log aggregation.'),
|
||||
category=_('Logging'),
|
||||
category_slug='logging',
|
||||
)
|
||||
|
||||
@ -31,6 +31,7 @@ class Command(BaseCommand):
|
||||
for session in sessions:
|
||||
user_id = session.get_decoded().get('_auth_user_id')
|
||||
if (user is None) or (user_id and user.id == int(user_id)):
|
||||
# The Session model instance doesn't have .flush(), we need a SessionStore instance.
|
||||
session = import_module(settings.SESSION_ENGINE).SessionStore(session.session_key)
|
||||
# Log out the session, but without the need for a request object.
|
||||
session.flush()
|
||||
|
||||
@ -7,6 +7,7 @@ import time
|
||||
import urllib.parse
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.auth import logout
|
||||
from django.contrib.auth.models import User
|
||||
from django.db.migrations.executor import MigrationExecutor
|
||||
from django.db import connection
|
||||
@ -71,6 +72,21 @@ class SessionTimeoutMiddleware(MiddlewareMixin):
|
||||
return response
|
||||
|
||||
|
||||
class DisableLocalAuthMiddleware(MiddlewareMixin):
|
||||
"""
|
||||
Respects the presence of the DISABLE_LOCAL_AUTH setting and forces
|
||||
local-only users to logout when they make a request.
|
||||
"""
|
||||
|
||||
def process_request(self, request):
|
||||
if settings.DISABLE_LOCAL_AUTH:
|
||||
user = request.user
|
||||
if not user.pk:
|
||||
return
|
||||
if not (user.profile.ldap_dn or user.social_auth.exists() or user.enterprise_auth.exists()):
|
||||
logout(request)
|
||||
|
||||
|
||||
def _customize_graph():
|
||||
from awx.main.models import Instance, Schedule, UnifiedJobTemplate
|
||||
|
||||
|
||||
@ -364,7 +364,7 @@ AUTHENTICATION_BACKENDS = (
|
||||
'social_core.backends.github_enterprise.GithubEnterpriseTeamOAuth2',
|
||||
'social_core.backends.azuread.AzureADOAuth2',
|
||||
'awx.sso.backends.SAMLAuth',
|
||||
'django.contrib.auth.backends.ModelBackend',
|
||||
'awx.main.backends.AWXModelBackend',
|
||||
)
|
||||
|
||||
|
||||
@ -716,6 +716,7 @@ CALLBACK_QUEUE = "callback_tasks"
|
||||
# Note: This setting may be overridden by database settings.
|
||||
ORG_ADMINS_CAN_SEE_ALL_USERS = True
|
||||
MANAGE_ORGANIZATION_AUTH = True
|
||||
DISABLE_LOCAL_AUTH = False
|
||||
|
||||
# Note: This setting may be overridden by database settings.
|
||||
TOWER_URL_BASE = "https://towerhost"
|
||||
@ -913,6 +914,7 @@ MIDDLEWARE = [
|
||||
'django.middleware.common.CommonMiddleware',
|
||||
'django.middleware.csrf.CsrfViewMiddleware',
|
||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||
'awx.main.middleware.DisableLocalAuthMiddleware',
|
||||
'django.contrib.messages.middleware.MessageMiddleware',
|
||||
'awx.sso.middleware.SocialAuthMiddleware',
|
||||
'crum.CurrentRequestUserMiddleware',
|
||||
|
||||
@ -196,6 +196,7 @@ class AuthenticationBackendsField(fields.StringListField):
|
||||
],
|
||||
),
|
||||
('django.contrib.auth.backends.ModelBackend', []),
|
||||
('awx.main.backends.AWXModelBackend', []),
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
@ -48,6 +48,7 @@ function MiscSystemDetail() {
|
||||
'INSIGHTS_TRACKING_STATE',
|
||||
'LOGIN_REDIRECT_OVERRIDE',
|
||||
'MANAGE_ORGANIZATION_AUTH',
|
||||
'DISABLE_LOCAL_AUTH',
|
||||
'OAUTH2_PROVIDER',
|
||||
'ORG_ADMINS_CAN_SEE_ALL_USERS',
|
||||
'REDHAT_PASSWORD',
|
||||
|
||||
@ -30,6 +30,7 @@ describe('<MiscSystemDetail />', () => {
|
||||
INSIGHTS_TRACKING_STATE: false,
|
||||
LOGIN_REDIRECT_OVERRIDE: 'https://redirect.com',
|
||||
MANAGE_ORGANIZATION_AUTH: true,
|
||||
DISABLE_LOCAL_AUTH: false,
|
||||
OAUTH2_PROVIDER: {
|
||||
ACCESS_TOKEN_EXPIRE_SECONDS: 1,
|
||||
AUTHORIZATION_CODE_EXPIRE_SECONDS: 2,
|
||||
|
||||
@ -48,6 +48,7 @@ function MiscSystemEdit() {
|
||||
'INSIGHTS_TRACKING_STATE',
|
||||
'LOGIN_REDIRECT_OVERRIDE',
|
||||
'MANAGE_ORGANIZATION_AUTH',
|
||||
'DISABLE_LOCAL_AUTH',
|
||||
'OAUTH2_PROVIDER',
|
||||
'ORG_ADMINS_CAN_SEE_ALL_USERS',
|
||||
'REDHAT_PASSWORD',
|
||||
@ -261,6 +262,12 @@ function MiscSystemEdit() {
|
||||
name="MANAGE_ORGANIZATION_AUTH"
|
||||
config={system.MANAGE_ORGANIZATION_AUTH}
|
||||
/>
|
||||
<BooleanField
|
||||
name="DISABLE_LOCAL_AUTH"
|
||||
needsConfirmationModal
|
||||
modalTitle={t`Confirm Disable Local Authorization`}
|
||||
config={system.DISABLE_LOCAL_AUTH}
|
||||
/>
|
||||
<InputField
|
||||
name="SESSION_COOKIE_AGE"
|
||||
config={system.SESSION_COOKIE_AGE}
|
||||
|
||||
@ -31,6 +31,7 @@ const systemData = {
|
||||
INSIGHTS_TRACKING_STATE: false,
|
||||
LOGIN_REDIRECT_OVERRIDE: '',
|
||||
MANAGE_ORGANIZATION_AUTH: true,
|
||||
DISABLE_LOCAL_AUTH: false,
|
||||
OAUTH2_PROVIDER: {
|
||||
ACCESS_TOKEN_EXPIRE_SECONDS: 31536000000,
|
||||
AUTHORIZATION_CODE_EXPIRE_SECONDS: 600,
|
||||
|
||||
@ -3,6 +3,7 @@ import { shape, string } from 'prop-types';
|
||||
import { t } from '@lingui/macro';
|
||||
import { useField } from 'formik';
|
||||
import {
|
||||
Button,
|
||||
FileUpload,
|
||||
FormGroup as PFFormGroup,
|
||||
InputGroup,
|
||||
@ -25,6 +26,7 @@ import {
|
||||
url,
|
||||
} from '../../../util/validators';
|
||||
import RevertButton from './RevertButton';
|
||||
import AlertModal from '../../../components/AlertModal';
|
||||
|
||||
const FormGroup = styled(PFFormGroup)`
|
||||
.pf-c-form__group-label {
|
||||
@ -73,8 +75,56 @@ const SettingGroup = ({
|
||||
</FormGroup>
|
||||
);
|
||||
};
|
||||
const BooleanField = ({ ariaLabel = '', name, config, disabled = false }) => {
|
||||
const BooleanField = ({
|
||||
ariaLabel = '',
|
||||
name,
|
||||
config,
|
||||
disabled = false,
|
||||
needsConfirmationModal,
|
||||
modalTitle,
|
||||
}) => {
|
||||
const [field, meta, helpers] = useField(name);
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
|
||||
if (isModalOpen) {
|
||||
return (
|
||||
<AlertModal
|
||||
isOpen
|
||||
title={modalTitle}
|
||||
variant="danger"
|
||||
aria-label={modalTitle}
|
||||
onClose={() => {
|
||||
helpers.setValue(false);
|
||||
}}
|
||||
actions={[
|
||||
<Button
|
||||
ouiaId="confirm-misc-settings-modal"
|
||||
key="confirm"
|
||||
variant="danger"
|
||||
aria-label={t`Confirm`}
|
||||
onClick={() => {
|
||||
helpers.setValue(true);
|
||||
setIsModalOpen(false);
|
||||
}}
|
||||
>
|
||||
{t`Confirm`}
|
||||
</Button>,
|
||||
<Button
|
||||
ouiaId="cancel-misc-settings-modal"
|
||||
key="cancel"
|
||||
variant="link"
|
||||
aria-label={t`Cancel`}
|
||||
onClick={() => {
|
||||
helpers.setValue(false);
|
||||
setIsModalOpen(false);
|
||||
}}
|
||||
>
|
||||
{t`Cancel`}
|
||||
</Button>,
|
||||
]}
|
||||
>{t`Are you sure you want to disable local authentication? Doing so could impact users' ability to log in and the system administrator's ability to reverse this change.`}</AlertModal>
|
||||
);
|
||||
}
|
||||
|
||||
return config ? (
|
||||
<SettingGroup
|
||||
@ -92,7 +142,11 @@ const BooleanField = ({ ariaLabel = '', name, config, disabled = false }) => {
|
||||
isDisabled={disabled}
|
||||
label={t`On`}
|
||||
labelOff={t`Off`}
|
||||
onChange={() => helpers.setValue(!field.value)}
|
||||
onChange={() =>
|
||||
needsConfirmationModal
|
||||
? setIsModalOpen(true)
|
||||
: helpers.setValue(!field.value)
|
||||
}
|
||||
aria-label={ariaLabel || config.label}
|
||||
/>
|
||||
</SettingGroup>
|
||||
|
||||
@ -265,4 +265,96 @@ describe('Setting form fields', () => {
|
||||
wrapper.update();
|
||||
expect(wrapper.find('input#mock_file-filename').prop('value')).toEqual('');
|
||||
});
|
||||
test('should render confirmation modal when toggle on for disable local auth', async () => {
|
||||
const wrapper = mountWithContexts(
|
||||
<Formik
|
||||
initialValues={{
|
||||
DISABLE_LOCAL_AUTH: false,
|
||||
}}
|
||||
>
|
||||
{() => (
|
||||
<BooleanField
|
||||
name="DISABLE_LOCAL_AUTH"
|
||||
needsConfirmationModal
|
||||
modalTitle="Confirm Disable Local Authorization"
|
||||
config={{
|
||||
category: 'Authentication',
|
||||
category_slug: 'authentication',
|
||||
default: false,
|
||||
help_text:
|
||||
'Controls whether users are prevented from using the built-in authentication system. You probably want to do this if you are using an LDAP or SAML integration.',
|
||||
label: 'Disable the built-in authentication system',
|
||||
required: true,
|
||||
type: 'boolean',
|
||||
value: false,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Formik>
|
||||
);
|
||||
expect(wrapper.find('Switch')).toHaveLength(1);
|
||||
expect(wrapper.find('Switch').prop('isChecked')).toBe(false);
|
||||
expect(wrapper.find('Switch').prop('isDisabled')).toBe(false);
|
||||
await act(async () => {
|
||||
wrapper.find('Switch').invoke('onChange')();
|
||||
});
|
||||
wrapper.update();
|
||||
|
||||
expect(wrapper.find('AlertModal')).toHaveLength(1);
|
||||
await act(async () =>
|
||||
wrapper
|
||||
.find('Button[ouiaId="confirm-misc-settings-modal"]')
|
||||
.prop('onClick')()
|
||||
);
|
||||
wrapper.update();
|
||||
expect(wrapper.find('AlertModal')).toHaveLength(0);
|
||||
expect(wrapper.find('Switch').prop('isChecked')).toBe(true);
|
||||
});
|
||||
|
||||
test('shold not toggle disable local auth', async () => {
|
||||
const wrapper = mountWithContexts(
|
||||
<Formik
|
||||
initialValues={{
|
||||
DISABLE_LOCAL_AUTH: false,
|
||||
}}
|
||||
>
|
||||
{() => (
|
||||
<BooleanField
|
||||
name="DISABLE_LOCAL_AUTH"
|
||||
needsConfirmationModal
|
||||
modalTitle="Confirm Disable Local Authorization"
|
||||
config={{
|
||||
category: 'Authentication',
|
||||
category_slug: 'authentication',
|
||||
default: false,
|
||||
help_text:
|
||||
'Controls whether users are prevented from using the built-in authentication system. You probably want to do this if you are using an LDAP or SAML integration.',
|
||||
label: 'Disable the built-in authentication system',
|
||||
required: true,
|
||||
type: 'boolean',
|
||||
value: false,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Formik>
|
||||
);
|
||||
expect(wrapper.find('Switch')).toHaveLength(1);
|
||||
expect(wrapper.find('Switch').prop('isChecked')).toBe(false);
|
||||
expect(wrapper.find('Switch').prop('isDisabled')).toBe(false);
|
||||
await act(async () => {
|
||||
wrapper.find('Switch').invoke('onChange')();
|
||||
});
|
||||
wrapper.update();
|
||||
|
||||
expect(wrapper.find('AlertModal')).toHaveLength(1);
|
||||
await act(async () =>
|
||||
wrapper
|
||||
.find('Button[ouiaId="cancel-misc-settings-modal"]')
|
||||
.prop('onClick')()
|
||||
);
|
||||
wrapper.update();
|
||||
|
||||
expect(wrapper.find('AlertModal')).toHaveLength(0);
|
||||
expect(wrapper.find('Switch').prop('isChecked')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
@ -34,6 +34,14 @@
|
||||
"category_slug": "system",
|
||||
"defined_in_file": false
|
||||
},
|
||||
"DISABLE_LOCAL_AUTH": {
|
||||
"type": "boolean",
|
||||
"label": "Disable the built-in authentication system",
|
||||
"help_text": "Controls whether users are prevented from using the built-in authentication system. You probably want to do this if you are using an LDAP or SAML integration.",
|
||||
"category": "Authentication",
|
||||
"category_slug": "authentication",
|
||||
"defined_in_file": false
|
||||
},
|
||||
"TOWER_URL_BASE": {
|
||||
"type": "string",
|
||||
"label": "Base URL of the service",
|
||||
@ -2897,6 +2905,15 @@
|
||||
"category_slug": "system",
|
||||
"default": true
|
||||
},
|
||||
"DISABLE_LOCAL_AUTH": {
|
||||
"type": "boolean",
|
||||
"required": true,
|
||||
"label": "Disable the built-in authentication system",
|
||||
"help_text": "Controls whether users are prevented from using the built-in authentication system. You probably want to do this if you are using an LDAP or SAML integration.",
|
||||
"category": "Authentication",
|
||||
"category_slug": "authentication",
|
||||
"default": false
|
||||
},
|
||||
"TOWER_URL_BASE": {
|
||||
"type": "string",
|
||||
"required": true,
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
"ACTIVITY_STREAM_ENABLED_FOR_INVENTORY_SYNC":false,
|
||||
"ORG_ADMINS_CAN_SEE_ALL_USERS":true,
|
||||
"MANAGE_ORGANIZATION_AUTH":true,
|
||||
"DISABLE_LOCAL_AUTH":false,
|
||||
"TOWER_URL_BASE":"https://localhost:3000",
|
||||
"REMOTE_HOST_HEADERS":["REMOTE_ADDR","REMOTE_HOST"],
|
||||
"PROXY_IP_ALLOWED_LIST":[],
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user