diff options
author | Alan Rominger <arominge@redhat.com> | 2024-11-04 22:21:36 +0100 |
---|---|---|
committer | Alan Rominger <arominge@redhat.com> | 2024-11-20 17:18:52 +0100 |
commit | 6599f3f827f4a5eef9a0b2bb965b7087e71d404a (patch) | |
tree | 318336f3bce0be7f6a582d1849c197a8acfc7a67 /awxkit | |
parent | Fix server error from system job detail view (#15640) (diff) | |
download | awx-6599f3f827f4a5eef9a0b2bb965b7087e71d404a.tar.xz awx-6599f3f827f4a5eef9a0b2bb965b7087e71d404a.zip |
Removal of OAuth2 stuff from CLI
also from awxkit generally
Remove login command
Diffstat (limited to 'awxkit')
-rw-r--r-- | awxkit/awxkit/api/client.py | 13 | ||||
-rw-r--r-- | awxkit/awxkit/api/pages/__init__.py | 2 | ||||
-rw-r--r-- | awxkit/awxkit/api/pages/api.py | 1 | ||||
-rw-r--r-- | awxkit/awxkit/api/pages/applications.py | 82 | ||||
-rw-r--r-- | awxkit/awxkit/api/pages/authtoken.py | 10 | ||||
-rw-r--r-- | awxkit/awxkit/api/pages/base.py | 72 | ||||
-rw-r--r-- | awxkit/awxkit/api/resources.py | 12 | ||||
-rw-r--r-- | awxkit/awxkit/awx/utils.py | 13 | ||||
-rwxr-xr-x | awxkit/awxkit/cli/client.py | 16 | ||||
-rw-r--r-- | awxkit/awxkit/cli/docs/source/usage.rst | 3 | ||||
-rw-r--r-- | awxkit/awxkit/cli/format.py | 6 | ||||
-rw-r--r-- | awxkit/awxkit/cli/resource.py | 47 | ||||
-rw-r--r-- | awxkit/awxkit/ws.py | 8 | ||||
-rw-r--r-- | awxkit/test/api/pages/test_base.py | 38 | ||||
-rw-r--r-- | awxkit/test/test_ws.py | 5 |
15 files changed, 15 insertions, 313 deletions
diff --git a/awxkit/awxkit/api/client.py b/awxkit/awxkit/api/client.py index cca1bba51d..ef18055f2a 100644 --- a/awxkit/awxkit/api/client.py +++ b/awxkit/awxkit/api/client.py @@ -13,15 +13,6 @@ class ConnectionException(exc.Common): pass -class Token_Auth(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 log.debug('"{0.request.method} {0.url}" elapsed: {0.elapsed}'.format(r)) @@ -47,7 +38,7 @@ class Connection(object): self.get(config.api_base_path) # this causes a cookie w/ the CSRF token to be set return dict(next=next) - def login(self, username=None, password=None, token=None, **kwargs): + def login(self, username=None, password=None, **kwargs): if username and password: _next = kwargs.get('next') if _next: @@ -62,8 +53,6 @@ class Connection(object): self.uses_session_cookie = True else: self.session.auth = (username, password) - elif token: - self.session.auth = Token_Auth(token) else: self.session.auth = None diff --git a/awxkit/awxkit/api/pages/__init__.py b/awxkit/awxkit/api/pages/__init__.py index 7fbdbcdb0d..0ede3ee2fe 100644 --- a/awxkit/awxkit/api/pages/__init__.py +++ b/awxkit/awxkit/api/pages/__init__.py @@ -4,13 +4,11 @@ from .base import * # NOQA from .bulk import * # NOQA from .access_list import * # NOQA from .api import * # NOQA -from .authtoken import * # NOQA from .roles import * # NOQA from .organizations import * # NOQA from .notifications import * # NOQA from .notification_templates import * # NOQA from .users import * # NOQA -from .applications import * # NOQA from .teams import * # NOQA from .credentials import * # NOQA from .unified_jobs import * # NOQA diff --git a/awxkit/awxkit/api/pages/api.py b/awxkit/awxkit/api/pages/api.py index 2283f10c96..7c91e38e96 100644 --- a/awxkit/awxkit/api/pages/api.py +++ b/awxkit/awxkit/api/pages/api.py @@ -25,7 +25,6 @@ EXPORTABLE_RESOURCES = [ 'job_templates', 'workflow_job_templates', 'execution_environments', - 'applications', 'schedules', ] diff --git a/awxkit/awxkit/api/pages/applications.py b/awxkit/awxkit/api/pages/applications.py deleted file mode 100644 index 3c9e4a8d76..0000000000 --- a/awxkit/awxkit/api/pages/applications.py +++ /dev/null @@ -1,82 +0,0 @@ -from awxkit.utils import random_title, update_payload, filter_by_class, PseudoNamespace -from awxkit.api.resources import resources -from awxkit.api.pages import Organization -from awxkit.api.mixins import HasCreate, DSAdapter - -from . import page -from . import base - - -class OAuth2Application(HasCreate, base.Base): - dependencies = [Organization] - NATURAL_KEY = ('organization', 'name') - - def payload(self, **kwargs): - payload = PseudoNamespace( - name=kwargs.get('name') or 'OAuth2Application - {}'.format(random_title()), - description=kwargs.get('description') or random_title(10), - client_type=kwargs.get('client_type', 'public'), - authorization_grant_type=kwargs.get('authorization_grant_type', 'password'), - ) - if kwargs.get('organization'): - payload.organization = kwargs['organization'].id - - optional_fields = ('redirect_uris', 'skip_authorization') - update_payload(payload, optional_fields, kwargs) - return payload - - def create_payload(self, organization=Organization, **kwargs): - self.create_and_update_dependencies(*filter_by_class((organization, Organization))) - organization = self.ds.organization if organization else None - payload = self.payload(organization=organization, **kwargs) - payload.ds = DSAdapter(self.__class__.__name__, self._dependency_store) - return payload - - def create(self, organization=Organization, **kwargs): - payload = self.create_payload(organization=organization, **kwargs) - return self.update_identity(OAuth2Applications(self.connection).post(payload)) - - -page.register_page((resources.application, (resources.applications, 'post')), OAuth2Application) - - -class OAuth2Applications(page.PageList, OAuth2Application): - pass - - -page.register_page(resources.applications, OAuth2Applications) - - -class OAuth2AccessToken(HasCreate, base.Base): - optional_dependencies = [OAuth2Application] - - def payload(self, **kwargs): - payload = PseudoNamespace(description=kwargs.get('description') or random_title(10), scope=kwargs.get('scope', 'write')) - - if kwargs.get('oauth_2_application'): - payload.application = kwargs['oauth_2_application'].id - - optional_fields = ('expires',) - update_payload(payload, optional_fields, kwargs) - return payload - - def create_payload(self, oauth_2_application=None, **kwargs): - self.create_and_update_dependencies(*filter_by_class((oauth_2_application, OAuth2Application))) - oauth_2_application = self.ds.oauth_2_application if oauth_2_application else None - payload = self.payload(oauth_2_application=oauth_2_application, **kwargs) - payload.ds = DSAdapter(self.__class__.__name__, self._dependency_store) - return payload - - def create(self, oauth_2_application=None, **kwargs): - payload = self.create_payload(oauth_2_application=oauth_2_application, **kwargs) - return self.update_identity(OAuth2AccessTokens(self.connection).post(payload)) - - -page.register_page((resources.token, (resources.tokens, 'post')), OAuth2AccessToken) - - -class OAuth2AccessTokens(page.PageList, OAuth2AccessToken): - pass - - -page.register_page(resources.tokens, OAuth2AccessTokens) diff --git a/awxkit/awxkit/api/pages/authtoken.py b/awxkit/awxkit/api/pages/authtoken.py deleted file mode 100644 index 36ec0b9546..0000000000 --- a/awxkit/awxkit/api/pages/authtoken.py +++ /dev/null @@ -1,10 +0,0 @@ -from awxkit.api.resources import resources -from . import base -from . import page - - -class AuthToken(base.Base): - pass - - -page.register_page(resources.authtoken, AuthToken) diff --git a/awxkit/awxkit/api/pages/base.py b/awxkit/awxkit/api/pages/base.py index 8730b9be48..99fb08f5f7 100644 --- a/awxkit/awxkit/api/pages/base.py +++ b/awxkit/awxkit/api/pages/base.py @@ -1,23 +1,13 @@ -import collections import logging -import typing -from requests.auth import HTTPBasicAuth - -from awxkit.api.pages import Page, get_registered_page, exception_from_status_code +from awxkit.api.pages import Page from awxkit.config import config -from awxkit.api.resources import resources import awxkit.exceptions as exc log = logging.getLogger(__name__) -class AuthUrls(typing.TypedDict): - access_token: str - personal_token: str - - class Base(Page): def silent_delete(self): """Delete the object. If it's already deleted, ignore the error""" @@ -135,66 +125,6 @@ class Base(Page): for obj_role in Roles(self.connection, endpoint=url).get().json.results: yield Role(self.connection, endpoint=obj_role.url).get() - def get_authtoken(self, username='', password=''): - default_cred = config.credentials.default - payload = dict(username=username or default_cred.username, password=password or default_cred.password) - auth_url = resources.authtoken - return get_registered_page(auth_url)(self.connection, endpoint=auth_url).post(payload).token - - def load_authtoken(self, username='', password=''): - self.connection.login(token=self.get_authtoken(username, password)) - return self - - load_default_authtoken = load_authtoken - - def _request_token(self, auth_urls, username, password, client_id, description, client_secret, scope): - req = collections.namedtuple('req', 'headers')({}) - if client_id and client_secret: - HTTPBasicAuth(client_id, client_secret)(req) - req.headers['Content-Type'] = 'application/x-www-form-urlencoded' - resp = self.connection.post( - auth_urls["access_token"], - data={"grant_type": "password", "username": username, "password": password, "scope": scope}, - headers=req.headers, - ) - elif client_id: - req.headers['Content-Type'] = 'application/x-www-form-urlencoded' - resp = self.connection.post( - auth_urls["access_token"], - data={"grant_type": "password", "username": username, "password": password, "client_id": client_id, "scope": scope}, - headers=req.headers, - ) - else: - HTTPBasicAuth(username, password)(req) - resp = self.connection.post( - auth_urls['personal_token'], - json={"description": description, "application": None, "scope": scope}, - headers=req.headers, - ) - if resp.ok: - result = resp.json() - if client_id: - return result.pop('access_token', None) - else: - return result.pop('token', None) - else: - raise exception_from_status_code(resp.status_code) - - def get_oauth2_token(self, username='', password='', client_id=None, description='AWX CLI', client_secret=None, scope='write'): - default_cred = config.credentials.default - username = username or default_cred.username - password = password or default_cred.password - # Try gateway first, fallback to controller - urls: AuthUrls = {"access_token": "/o/token/", "personal_token": f"{config.gateway_base_path}v1/tokens/"} - try: - return self._request_token(urls, username, password, client_id, description, client_secret, scope) - except exc.NotFound: - urls = { - "access_token": f"{config.api_base_path}o/token/", - "personal_token": f"{config.api_base_path}v2/users/{username}/personal_tokens/", - } - return self._request_token(urls, username, password, client_id, description, client_secret, scope) - def load_session(self, username='', password=''): default_cred = config.credentials.default self.connection.login( diff --git a/awxkit/awxkit/api/resources.py b/awxkit/awxkit/api/resources.py index 57bf845f86..7b2d1003d3 100644 --- a/awxkit/awxkit/api/resources.py +++ b/awxkit/awxkit/api/resources.py @@ -12,10 +12,7 @@ class Resources(object): _ad_hoc_related_cancel = r'ad_hoc_commands/\d+/cancel/' _ad_hoc_relaunch = r'ad_hoc_commands/\d+/relaunch/' _ansible_facts = r'hosts/\d+/ansible_facts/' - _application = r'applications/\d+/' - _applications = 'applications/' _auth = 'auth/' - _authtoken = 'authtoken/' _bulk = 'bulk/' _bulk_job_launch = 'bulk/job_launch/' _config = 'config/' @@ -233,8 +230,6 @@ class Resources(object): _team_permissions = r'teams/\d+/permissions/' _team_users = r'teams/\d+/users/' _teams = 'teams/' - _token = r'tokens/\d+/' - _tokens = 'tokens/' _unified_job_template = r'unified_job_templates/\d+/' _unified_job_templates = 'unified_job_templates/' _unified_jobs = 'unified_jobs/' @@ -282,12 +277,7 @@ class Resources(object): def __getattr__(self, resource): if resource[:3] == '___': raise AttributeError('No existing resource: {}'.format(resource)) - # Currently we don't handle anything under: - # /api/o/ - # /api/login/ - # /api/logout/ - # If/when we do we will probably need to modify this __getattr__ method - # Also, if we add another API version, this would be handled here + # If/when we add another API version, this would be handled here prefix = 'v2' resource = '_' + resource return '{0}{1}'.format(getattr(self, prefix), getattr(self, resource)) diff --git a/awxkit/awxkit/awx/utils.py b/awxkit/awxkit/awx/utils.py index c84a58b3b2..0b56fc22cb 100644 --- a/awxkit/awxkit/awx/utils.py +++ b/awxkit/awxkit/awx/utils.py @@ -73,7 +73,6 @@ def check_related(resource): @contextmanager def as_user(v, username, password=None): """Context manager to allow running tests as an alternative login user.""" - access_token = False if not isinstance(v, api.client.Connection): connection = v.connection else: @@ -83,11 +82,6 @@ def as_user(v, username, password=None): password = username.password username = username.username - if isinstance(username, api.OAuth2AccessToken): - access_token = username.token - username = None - password = None - try: if config.use_sessions: session_id = None @@ -101,10 +95,7 @@ def as_user(v, username, password=None): break if session_id: del connection.session.cookies[connection.session_cookie_name] - if access_token: - kwargs = dict(token=access_token) - else: - kwargs = connection.get_session_requirements() + kwargs = connection.get_session_requirements() else: previous_auth = connection.session.auth kwargs = dict() @@ -112,8 +103,6 @@ def as_user(v, username, password=None): yield finally: if config.use_sessions: - if access_token: - connection.session.auth = None del connection.session.cookies[connection.session_cookie_name] if session_id: connection.session.cookies.set(connection.session_cookie_name, session_id, domain=domain) diff --git a/awxkit/awxkit/cli/client.py b/awxkit/awxkit/cli/client.py index de790e184e..c06188c123 100755 --- a/awxkit/awxkit/cli/client.py +++ b/awxkit/awxkit/cli/client.py @@ -82,17 +82,9 @@ class CLI(object): return '--help' in self.argv or '-h' in self.argv def authenticate(self): - """Configure the current session (or OAuth2.0 token)""" - token = self.get_config('token') - if token: - self.root.connection.login( - None, - None, - token=token, - ) - else: - config.use_sessions = True - self.root.load_session().get() + """Configure the current session for basic auth""" + config.use_sessions = True + self.root.load_session().get() def connect(self): """Fetch top-level resources from /api/v2""" @@ -141,7 +133,7 @@ class CLI(object): """Attempt to parse the <resource> (e.g., jobs) specified on the CLI If a valid resource is discovered, the user will be authenticated - (either via an OAuth2.0 token or session-based auth) and the remaining + (via session-based auth) and the remaining CLI arguments will be processed (to determine the requested action e.g., list, create, delete) diff --git a/awxkit/awxkit/cli/docs/source/usage.rst b/awxkit/awxkit/cli/docs/source/usage.rst index c877019bc6..13d36d0c1d 100644 --- a/awxkit/awxkit/cli/docs/source/usage.rst +++ b/awxkit/awxkit/cli/docs/source/usage.rst @@ -88,6 +88,3 @@ A few of the most important ones are: ``--conf.password, CONTROLLER_PASSWORD`` the AWX password to use for authentication - -``--conf.token, CONTROLLER_OAUTH_TOKEN`` - an OAuth2.0 token to use for authentication diff --git a/awxkit/awxkit/cli/format.py b/awxkit/awxkit/cli/format.py index de2de47262..f294a9e045 100644 --- a/awxkit/awxkit/cli/format.py +++ b/awxkit/awxkit/cli/format.py @@ -30,12 +30,6 @@ def add_authentication_arguments(parser, env): default=env.get('CONTROLLER_HOST', env.get('TOWER_HOST', 'https://127.0.0.1:443')), metavar='https://example.awx.org', ) - auth.add_argument( - '--conf.token', - default=env.get('CONTROLLER_OAUTH_TOKEN', env.get('CONTROLLER_TOKEN', env.get('TOWER_OAUTH_TOKEN', env.get('TOWER_TOKEN', '')))), - help='an OAuth2.0 token (get one by using `awx login`)', - metavar='TEXT', - ) config_username, config_password = get_config_credentials() # options configured via cli args take higher precedence than those from the config diff --git a/awxkit/awxkit/cli/resource.py b/awxkit/awxkit/cli/resource.py index 05e8163059..621c76798a 100644 --- a/awxkit/awxkit/cli/resource.py +++ b/awxkit/awxkit/cli/resource.py @@ -1,14 +1,12 @@ import yaml import json -import os -from awxkit import api, config, yaml_file +from awxkit import config, yaml_file from awxkit.exceptions import ImportExportError -from awxkit.utils import to_str from awxkit.api.pages import Page from awxkit.api.pages.api import EXPORTABLE_RESOURCES -from awxkit.cli.format import FORMATTERS, format_response, add_authentication_arguments, add_formatting_import_export -from awxkit.cli.utils import CustomRegistryMeta, cprint +from awxkit.cli.format import format_response, add_formatting_import_export +from awxkit.cli.utils import CustomRegistryMeta CONTROL_RESOURCES = ['ping', 'config', 'me', 'metrics', 'mesh_visualizer'] @@ -66,44 +64,6 @@ class CustomCommand(metaclass=CustomRegistryMeta): raise NotImplementedError() -class Login(CustomCommand): - name = 'login' - help_text = 'authenticate and retrieve an OAuth2 token' - - def print_help(self, parser): - add_authentication_arguments(parser, os.environ) - parser.print_help() - - def handle(self, client, parser): - auth = parser.add_argument_group('OAuth2.0 Options') - auth.add_argument('--description', help='description of the generated OAuth2.0 token', metavar='TEXT') - auth.add_argument('--conf.client_id', metavar='TEXT') - auth.add_argument('--conf.client_secret', metavar='TEXT') - auth.add_argument('--conf.scope', choices=['read', 'write'], default='write') - if client.help: - self.print_help(parser) - raise SystemExit() - parsed = parser.parse_known_args()[0] - kwargs = { - 'client_id': getattr(parsed, 'conf.client_id', None), - 'client_secret': getattr(parsed, 'conf.client_secret', None), - 'scope': getattr(parsed, 'conf.scope', None), - } - if getattr(parsed, 'description', None): - kwargs['description'] = parsed.description - try: - token = api.Api().get_oauth2_token(**kwargs) - except Exception as e: - self.print_help(parser) - cprint('Error retrieving an OAuth2.0 token ({}).'.format(e.__class__), 'red') - else: - fmt = client.get_config('format') - if fmt == 'human': - print('export CONTROLLER_OAUTH_TOKEN={}'.format(token)) - else: - print(to_str(FORMATTERS[fmt]({'token': token}, '.')).strip()) - - class Config(CustomCommand): name = 'config' help_text = 'print current configuration values' @@ -114,7 +74,6 @@ class Config(CustomCommand): raise SystemExit() return { 'base_url': config.base_url, - 'token': client.get_config('token'), 'use_sessions': config.use_sessions, 'credentials': config.credentials, } diff --git a/awxkit/awxkit/ws.py b/awxkit/awxkit/ws.py index ac4b37453a..78c58bf2d2 100644 --- a/awxkit/awxkit/ws.py +++ b/awxkit/awxkit/ws.py @@ -37,7 +37,7 @@ class WSClient(object): 'control': ['limit_reached']} e.x: ``` - ws = WSClient(token, port=8013, secure=False).connect() + ws = WSClient(port=8013, secure=False, session_id='xyz', csrftoken='abc').connect() ws.job_details() ... # launch job job_messages = [msg for msg in ws] @@ -52,7 +52,6 @@ class WSClient(object): def __init__( self, - token=None, hostname='', port=443, secure=True, @@ -80,15 +79,12 @@ class WSClient(object): self.suffix = ws_suffix self._use_ssl = secure self.hostname = hostname - self.token = token self.session_id = session_id self.csrftoken = csrftoken self._recv_queue = Queue() self._ws_closed = False self._ws_connected_flag = threading.Event() - if self.token is not None: - auth_cookie = 'token="{0.token}";'.format(self) - elif self.session_id is not None: + if self.session_id is not None: auth_cookie = '{1}="{0.session_id}"'.format(self, session_cookie_name) if self.csrftoken: auth_cookie += ';csrftoken={0.csrftoken}'.format(self) diff --git a/awxkit/test/api/pages/test_base.py b/awxkit/test/api/pages/test_base.py index 6706d950d4..3126e333d8 100644 --- a/awxkit/test/api/pages/test_base.py +++ b/awxkit/test/api/pages/test_base.py @@ -1,9 +1,6 @@ from http.client import NOT_FOUND import pytest -from pytest_mock import MockerFixture -from requests import Response -from awxkit.api.pages import Base from awxkit.config import config @@ -22,38 +19,3 @@ def response(mocker): "access_token": "my_token", } return r - - -@pytest.mark.parametrize( - ("auth_creds", "url", "token"), - [ - ({"client_id": "foo", "client_secret": "bar"}, "/o/token/", "my_token"), - ({"client_id": "foo"}, "/o/token/", "my_token"), - ({}, "/api/gateway/v1/tokens/", "my_personal_token"), - ], -) -def test_get_oauth2_token_from_gateway(mocker: MockerFixture, response: Response, auth_creds, url, token): - post = mocker.patch("requests.Session.post", return_value=response) - base = Base() - ret = base.get_oauth2_token(**auth_creds) - assert post.call_count == 1 - assert post.call_args.args[0] == url - assert ret == token - - -@pytest.mark.parametrize( - ("auth_creds", "url", "token"), - [ - ({"client_id": "foo", "client_secret": "bar"}, "/api/o/token/", "my_token"), - ({"client_id": "foo"}, "/api/o/token/", "my_token"), - ({}, "/api/v2/users/foo/personal_tokens/", "my_personal_token"), - ], -) -def test_get_oauth2_token_from_controller(mocker: MockerFixture, response: Response, auth_creds, url, token): - type(response).ok = mocker.PropertyMock(side_effect=[False, True]) - post = mocker.patch("requests.Session.post", return_value=response) - base = Base() - ret = base.get_oauth2_token(**auth_creds) - assert post.call_count == 2 - assert post.call_args.args[0] == url - assert ret == token diff --git a/awxkit/test/test_ws.py b/awxkit/test/test_ws.py index c2e69fbc51..f40767b357 100644 --- a/awxkit/test/test_ws.py +++ b/awxkit/test/test_ws.py @@ -10,11 +10,10 @@ ParseResult = namedtuple("ParseResult", ["port", "hostname", "secure"]) def test_explicit_hostname(): - client = WSClient("token", "some-hostname", 556, False) + client = WSClient(hostname="some-hostname", port=556, secure=False) assert client.port == 556 assert client.hostname == "some-hostname" assert client._use_ssl == False - assert client.token == "token" def test_websocket_suffix(): @@ -35,7 +34,7 @@ def test_urlparsing(url, result): with patch("awxkit.ws.config") as mock_config: mock_config.base_url = url - client = WSClient("token") + client = WSClient(hostname=None) assert client.port == result.port assert client.hostname == result.hostname assert client._use_ssl == result.secure |