mirror of
https://github.com/ansible/awx.git
synced 2026-03-11 06:29:31 -02:30
[Devel][AAP-65384]Restoration of Token Authentication for AWX CLI (#16281)
* Added token authentication in logic, arguments, and test
This commit is contained in:
@@ -12,6 +12,15 @@ class ConnectionException(exc.Common):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class TokenAuth(requests.auth.AuthBase):
|
||||||
|
def __init__(self, token):
|
||||||
|
self.token = token
|
||||||
|
|
||||||
|
def __call__(self, request):
|
||||||
|
request.headers['Authorization'] = 'Bearer {0.token}'.format(self)
|
||||||
|
return request
|
||||||
|
|
||||||
|
|
||||||
def log_elapsed(r, *args, **kwargs): # requests hook to display API elapsed time
|
def log_elapsed(r, *args, **kwargs): # requests hook to display API elapsed time
|
||||||
log.debug('"{0.request.method} {0.url}" elapsed: {0.elapsed}'.format(r))
|
log.debug('"{0.request.method} {0.url}" elapsed: {0.elapsed}'.format(r))
|
||||||
|
|
||||||
@@ -37,7 +46,7 @@ class Connection(object):
|
|||||||
self.get(config.api_base_path) # this causes a cookie w/ the CSRF token to be set
|
self.get(config.api_base_path) # this causes a cookie w/ the CSRF token to be set
|
||||||
return dict(next=next)
|
return dict(next=next)
|
||||||
|
|
||||||
def login(self, username=None, password=None, **kwargs):
|
def login(self, username=None, password=None, token=None, **kwargs):
|
||||||
if username and password:
|
if username and password:
|
||||||
_next = kwargs.get('next')
|
_next = kwargs.get('next')
|
||||||
if _next:
|
if _next:
|
||||||
@@ -52,6 +61,8 @@ class Connection(object):
|
|||||||
self.uses_session_cookie = True
|
self.uses_session_cookie = True
|
||||||
else:
|
else:
|
||||||
self.session.auth = (username, password)
|
self.session.auth = (username, password)
|
||||||
|
elif token:
|
||||||
|
self.session.auth = TokenAuth(token)
|
||||||
else:
|
else:
|
||||||
self.session.auth = None
|
self.session.auth = None
|
||||||
|
|
||||||
|
|||||||
@@ -83,12 +83,23 @@ class CLI(object):
|
|||||||
def authenticate(self):
|
def authenticate(self):
|
||||||
"""Configure the current session for authentication.
|
"""Configure the current session for authentication.
|
||||||
|
|
||||||
Uses Basic authentication when AWXKIT_FORCE_BASIC_AUTH environment variable
|
Authentication priority:
|
||||||
is set to true, otherwise defaults to session-based authentication.
|
1. Token authentication (if --conf.token provided)
|
||||||
|
2. Basic authentication (if AWXKIT_FORCE_BASIC_AUTH=true)
|
||||||
|
3. Session-based authentication (default)
|
||||||
|
|
||||||
|
|
||||||
For AAP Gateway environments, set AWXKIT_FORCE_BASIC_AUTH=true to bypass
|
For AAP Gateway environments, set AWXKIT_FORCE_BASIC_AUTH=true to bypass
|
||||||
session login restrictions.
|
session login restrictions when using username/password.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
# Token authentication (if token is provided)
|
||||||
|
token = self.get_config('token')
|
||||||
|
if token:
|
||||||
|
config.use_sessions = False
|
||||||
|
self.root.connection.login(None, None, token=token)
|
||||||
|
return
|
||||||
|
|
||||||
# Check if Basic auth is forced via environment variable
|
# Check if Basic auth is forced via environment variable
|
||||||
if config.get('force_basic_auth', False):
|
if config.get('force_basic_auth', False):
|
||||||
config.use_sessions = False
|
config.use_sessions = False
|
||||||
|
|||||||
@@ -59,6 +59,12 @@ def add_authentication_arguments(parser, env):
|
|||||||
default=env.get('CONTROLLER_PASSWORD', env.get('TOWER_PASSWORD', config_password)),
|
default=env.get('CONTROLLER_PASSWORD', env.get('TOWER_PASSWORD', config_password)),
|
||||||
metavar='TEXT',
|
metavar='TEXT',
|
||||||
)
|
)
|
||||||
|
auth.add_argument(
|
||||||
|
'--conf.token',
|
||||||
|
default=env.get('CONTROLLER_OAUTH_TOKEN', env.get('TOWER_OAUTH_TOKEN', None)),
|
||||||
|
metavar='TEXT',
|
||||||
|
help='OAuth2 token for authentication (takes precedence over username/password)',
|
||||||
|
)
|
||||||
|
|
||||||
auth.add_argument(
|
auth.add_argument(
|
||||||
'-k',
|
'-k',
|
||||||
|
|||||||
@@ -44,6 +44,48 @@ def setup_session_auth(cli_args: Optional[List[str]] = None) -> Tuple[CLI, Mock,
|
|||||||
return cli, mock_root, mock_load_session
|
return cli, mock_root, mock_load_session
|
||||||
|
|
||||||
|
|
||||||
|
def setup_token_auth(cli_args: Optional[List[str]] = None) -> Tuple[CLI, Mock, Mock]:
|
||||||
|
"""Set up CLI with mocked connection for Token auth testing"""
|
||||||
|
cli = CLI()
|
||||||
|
cli.parse_args(cli_args or ['awx', '--conf.token', 'test-token-abc123'])
|
||||||
|
|
||||||
|
mock_root = Mock()
|
||||||
|
mock_connection = Mock()
|
||||||
|
mock_root.connection = mock_connection
|
||||||
|
cli.root = mock_root
|
||||||
|
|
||||||
|
return cli, mock_root, mock_connection
|
||||||
|
|
||||||
|
|
||||||
|
def test_token_auth_preserved(monkeypatch):
|
||||||
|
"""
|
||||||
|
REGRESSION TEST: Token authentication must still work (existed in 4.6.12)
|
||||||
|
|
||||||
|
This test documents the customer's working scenario from 4.6.12:
|
||||||
|
awx login --conf.host URL --conf.username USER --conf.password PASS
|
||||||
|
# Returns: {"token": "E*******J"}
|
||||||
|
|
||||||
|
awx --conf.host URL --conf.token E*******J job_templates launch ...
|
||||||
|
# This WORKED in 4.6.12
|
||||||
|
|
||||||
|
BREAKING CHANGE: Version 4.6.21 removed token authentication entirely,
|
||||||
|
causing customer to report: "neither token no username/password are working"
|
||||||
|
|
||||||
|
This test will FAIL with current code and PASS once fixed.
|
||||||
|
"""
|
||||||
|
cli, mock_root, mock_connection = setup_token_auth(['awx', '--conf.host', 'https://aap-sbx.testbank.com', '--conf.token', 'E1234567890J'])
|
||||||
|
monkeypatch.setattr(config, 'force_basic_auth', False)
|
||||||
|
|
||||||
|
# Execute authentication
|
||||||
|
cli.authenticate()
|
||||||
|
|
||||||
|
# Token auth should call login with token parameter
|
||||||
|
mock_connection.login.assert_called_once_with(None, None, token='E1234567890J')
|
||||||
|
|
||||||
|
# Should NOT use sessions when token is provided
|
||||||
|
assert not config.use_sessions
|
||||||
|
|
||||||
|
|
||||||
def test_basic_auth_enabled(monkeypatch):
|
def test_basic_auth_enabled(monkeypatch):
|
||||||
"""Test that AWXKIT_FORCE_BASIC_AUTH=true enables Basic authentication"""
|
"""Test that AWXKIT_FORCE_BASIC_AUTH=true enables Basic authentication"""
|
||||||
cli, mock_root, mock_connection = setup_basic_auth()
|
cli, mock_root, mock_connection = setup_basic_auth()
|
||||||
|
|||||||
Reference in New Issue
Block a user