Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

COMP-613 Delete tokens on logout #32

Merged
merged 5 commits into from
Dec 22, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@
Changelog
=========

Version 2.2
===========

* Tokens file gets deleted and logout succeeds even if the authentication server is not available `#32 <https://github.com/iqm-finland/cortex-cli/pull/32>`_

Version 2.1
===========

Expand Down
13 changes: 9 additions & 4 deletions src/cortex_cli/cortex_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -547,10 +547,13 @@ def logout(config_file: str, keep_tokens: str, force: bool) -> None:
try:
logout_request(auth_server_url, realm, client_id, refresh_token)
except (Timeout, ConnectionError, ClientAuthenticationError) as error:
raise click.ClickException(f'Error when logging out: {error}') from error
logger.warning(
'Failed to revoke tokens due to error when connecting to authentication server: %s', error
)

Process(pid).terminate()
os.remove(tokens_file)
logger.info('Logged out successfully.')
logger.info('Tokens file deleted. Logged out.')
return

# 4. Delete tokens, perform logout
Expand All @@ -560,10 +563,12 @@ def logout(config_file: str, keep_tokens: str, force: bool) -> None:
try:
logout_request(auth_server_url, realm, client_id, refresh_token)
except (Timeout, ConnectionError, ClientAuthenticationError) as error:
raise click.ClickException(f'Error when logging out: {error}') from error
logger.warning(
'Failed to revoke tokens due to error when connecting to authentication server: %s', error
)

os.remove(tokens_file)
logger.info('Logged out successfully.')
logger.info('Tokens file deleted. Logged out.')
return

logger.info('Logout aborted.')
Expand Down
188 changes: 93 additions & 95 deletions tests/cortex_cli_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -628,7 +628,7 @@ def test_auth_logout_handles_no_keep_tokens_and_pid(config_dict, credentials):
expect_process_terminate()
result = runner.invoke(cortex_cli, ['auth', 'logout', '--config-file', 'config.json', '--force'])
assert result.exit_code == 0
assert 'Logged out successfully' in result.output
assert 'Tokens file deleted. Logged out.' in result.output

# tokens file deleted
with raises(FileNotFoundError):
Expand All @@ -638,95 +638,91 @@ def test_auth_logout_handles_no_keep_tokens_and_pid(config_dict, credentials):
unstub()


# Logout Scenario 4
def test_auth_logout_handles_no_keep_tokens_and_no_pid(config_dict, tokens_dict, credentials):
def test_auth_logout_succeeds_with_auth_server_not_available(credentials, config_dict, tokens_dict):
"""
Tests that ``cortex auth logout`` attempts to logout without daemon running.
Tests that ``cortex auth logout`` deletes tokens file when authentication server fails to process request.
"""
runner = CliRunner()
tokens = prepare_tokens(300, 3600, **credentials)
url = credentials['auth_server_url']
realm = config_dict['realm']
client_id = config_dict['client_id']
refresh_token = tokens['refresh_token']
expect_logout(url, realm, client_id, refresh_token, status_code=401)

runner = CliRunner()
with runner.isolated_filesystem():
with open('config.json', 'w', encoding='UTF-8') as file:
file.write(json.dumps(config_dict))

del tokens_dict['pid']
tokens_dict['access_token'] = tokens['access_token']
tokens_dict['refresh_token'] = tokens['refresh_token']
tokens_dict['pid'] = os.getpid()
with open('tokens.json', 'w', encoding='utf-8') as file:
file.write(json.dumps(tokens_dict))

url = credentials['auth_server_url']
realm = config_dict['realm']
client_id = config_dict['client_id']
refresh_token = tokens_dict['refresh_token']
expect_logout(url, realm, client_id, refresh_token)
runner.invoke(
cortex_cli,
[
'auth',
'login',
'--config-file',
'config.json',
'--username',
credentials['username'],
'--password',
credentials['password'],
'--no-refresh', # do not start token manager
],
)

expect_process_terminate()
result = runner.invoke(cortex_cli, ['auth', 'logout', '--config-file', 'config.json', '--force'])
assert result.exit_code == 0
assert 'No PID found in tokens file' in result.output
assert 'Logged out successfully' in result.output
assert 'Tokens file deleted. Logged out.' in result.output

# tokens file deleted
with raises(FileNotFoundError):
with open('tokens.json', 'r', encoding='utf-8') as file:
json.loads(file.read())
with open('tokens.json', 'r', encoding='UTF-8') as file:
file.read()

unstub()


def test_auth_logout_fails_without_config_file():
# Logout Scenario 4
def test_auth_logout_handles_no_keep_tokens_and_no_pid(config_dict, tokens_dict, credentials):
"""
Tests that ``cortex auth logout`` fails when tokens file doesn't exist.
Tests that ``cortex auth logout`` attempts to logout without daemon running.
"""
runner = CliRunner()
with runner.isolated_filesystem():
result = runner.invoke(cortex_cli, ['auth', 'logout', '--config-file', 'nonexisting_config.json'])
assert result.exit_code != 0
assert 'does not exist' in result.output


def test_auth_logout_fails_without_tokens_file(config_dict):
"""
Tests that ``cortex auth logout`` fails when tokens file doesn't exist.
"""
runner = CliRunner()
with runner.isolated_filesystem():
with open('config.json', 'w', encoding='UTF-8') as file:
file.write(json.dumps(config_dict))

result = runner.invoke(cortex_cli, ['auth', 'logout', '--config-file', 'config.json'])
assert result.exit_code == 0
assert 'Not logged in' in result.output


def test_auth_logout_fails_with_invalid_tokens_file(config_dict, tokens_dict):
"""
Tests that ``cortex auth logout`` reports error when tokens file is invalid.
"""
runner = CliRunner()
with runner.isolated_filesystem():
with open('config.json', 'w', encoding='UTF-8') as file:
file.write(json.dumps(config_dict))
tokens_dict['timestamp'] = 'not a timestamp'
del tokens_dict['pid']
with open('tokens.json', 'w', encoding='utf-8') as file:
file.write(json.dumps(tokens_dict))

result = runner.invoke(
cortex_cli,
[
'auth',
'logout',
'--config-file',
'config.json',
'--keep-tokens',
],
input='n',
)
url = credentials['auth_server_url']
realm = config_dict['realm']
client_id = config_dict['client_id']
refresh_token = tokens_dict['refresh_token']
expect_logout(url, realm, client_id, refresh_token)
result = runner.invoke(cortex_cli, ['auth', 'logout', '--config-file', 'config.json', '--force'])
assert result.exit_code == 0
assert 'Found invalid tokens.json' in result.output
assert 'No PID found in tokens file' in result.output
assert 'Tokens file deleted. Logged out.' in result.output

# tokens file deleted
with raises(FileNotFoundError):
with open('tokens.json', 'r', encoding='utf-8') as file:
json.loads(file.read())

unstub()


# Logout Scenario 3 failure
def test_auth_logout_fails_by_server_response(credentials, config_dict, tokens_dict):
def test_auth_logout_succeeds_with_auth_server_not_available_no_pid(credentials, config_dict, tokens_dict):
"""
Tests that ``cortex auth logout`` reports error when server fails to process request.
Tests that ``cortex auth logout`` deletes tokens file when authentication server fails to process request.
"""
tokens = prepare_tokens(300, 3600, **credentials)
url = credentials['auth_server_url']
Expand All @@ -739,8 +735,7 @@ def test_auth_logout_fails_by_server_response(credentials, config_dict, tokens_d
with runner.isolated_filesystem():
with open('config.json', 'w', encoding='UTF-8') as file:
file.write(json.dumps(config_dict))
tokens_dict['access_token'] = tokens['access_token']
tokens_dict['refresh_token'] = tokens['refresh_token']
del tokens_dict['pid']
with open('tokens.json', 'w', encoding='utf-8') as file:
file.write(json.dumps(tokens_dict))

Expand All @@ -760,64 +755,67 @@ def test_auth_logout_fails_by_server_response(credentials, config_dict, tokens_d
)

result = runner.invoke(cortex_cli, ['auth', 'logout', '--config-file', 'config.json', '--force'])
assert result.exit_code != 0
assert 'Error when logging out' in result.output
assert result.exit_code == 0
assert 'Tokens file deleted. Logged out.' in result.output

# tokens file left unchanged
with open('tokens.json', 'r', encoding='utf-8') as file:
same_tokens = json.loads(file.read())
assert same_tokens['access_token'] == tokens['access_token']
assert same_tokens['refresh_token'] == tokens['refresh_token']
# tokens file deleted
with raises(FileNotFoundError):
with open('tokens.json', 'r', encoding='UTF-8') as file:
file.read()

unstub()


# Logout Scenario 4 failure
def test_auth_logout_fails_by_server_response_no_pid(credentials, config_dict, tokens_dict):
def test_auth_logout_fails_without_config_file():
"""
Tests that ``cortex auth logout`` reports error when server fails to process request.
Tests that ``cortex auth logout`` fails when tokens file doesn't exist.
"""
tokens = prepare_tokens(300, 3600, **credentials)
url = credentials['auth_server_url']
realm = config_dict['realm']
client_id = config_dict['client_id']
refresh_token = tokens['refresh_token']
expect_logout(url, realm, client_id, refresh_token, status_code=401)
runner = CliRunner()
with runner.isolated_filesystem():
result = runner.invoke(cortex_cli, ['auth', 'logout', '--config-file', 'nonexisting_config.json'])
assert result.exit_code != 0
assert 'does not exist' in result.output


def test_auth_logout_fails_without_tokens_file(config_dict):
"""
Tests that ``cortex auth logout`` fails when tokens file doesn't exist.
"""
runner = CliRunner()
with runner.isolated_filesystem():
with open('config.json', 'w', encoding='UTF-8') as file:
file.write(json.dumps(config_dict))
del tokens_dict['pid']

result = runner.invoke(cortex_cli, ['auth', 'logout', '--config-file', 'config.json'])
assert result.exit_code == 0
assert 'Not logged in' in result.output


def test_auth_logout_fails_with_invalid_tokens_file(config_dict, tokens_dict):
"""
Tests that ``cortex auth logout`` reports error when tokens file is invalid.
"""
runner = CliRunner()
with runner.isolated_filesystem():
with open('config.json', 'w', encoding='UTF-8') as file:
file.write(json.dumps(config_dict))
tokens_dict['timestamp'] = 'not a timestamp'
with open('tokens.json', 'w', encoding='utf-8') as file:
file.write(json.dumps(tokens_dict))

runner.invoke(
result = runner.invoke(
cortex_cli,
[
'auth',
'login',
'logout',
'--config-file',
'config.json',
'--username',
credentials['username'],
'--password',
credentials['password'],
'--no-refresh', # do not start token manager
'--keep-tokens',
],
input='n',
)

result = runner.invoke(cortex_cli, ['auth', 'logout', '--config-file', 'config.json', '--force'])
assert result.exit_code != 0
assert 'Error when logging out' in result.output

# tokens file left unchanged
with open('tokens.json', 'r', encoding='utf-8') as file:
same_tokens = json.loads(file.read())
assert same_tokens['access_token'] == tokens['access_token']
assert same_tokens['refresh_token'] == tokens['refresh_token']

unstub()
assert result.exit_code == 0
assert 'Found invalid tokens.json' in result.output


# Tests for utility functions
Expand Down