From b0cbc907ac545148e4a37e34944bf00badbfe5de Mon Sep 17 00:00:00 2001 From: Alexis ANNEIX Date: Thu, 21 Jul 2022 20:12:18 +0200 Subject: [PATCH 1/9] Add IDE to gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 2f59dd2..b49c972 100644 --- a/.gitignore +++ b/.gitignore @@ -130,3 +130,4 @@ dmypy.json # IDEs .vscode +.idea From 08c082c2433c550d7f052af1d97a83576e3dd000 Mon Sep 17 00:00:00 2001 From: Alexis ANNEIX Date: Thu, 21 Jul 2022 20:12:58 +0200 Subject: [PATCH 2/9] Add optional config_path arg to BaseMain --- archfx_cloud/utils/main.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/archfx_cloud/utils/main.py b/archfx_cloud/utils/main.py index f200920..df9430c 100644 --- a/archfx_cloud/utils/main.py +++ b/archfx_cloud/utils/main.py @@ -18,7 +18,7 @@ class BaseMain(object): domain = 'https://arch.archfx.io' logging_level = logging.INFO - def __init__(self): + def __init__(self, config_path='.ini'): """ Initialize Logging configuration Initialize argument parsing @@ -27,7 +27,7 @@ def __init__(self): Additional arguments can be configured by overwriting the add_extra_args() method Logging configuration can be changed by overwritting the config_logging() method """ - CONFIG.read('.ini') + CONFIG.read(config_path) self.parser = argparse.ArgumentParser(description=__doc__) self.parser.add_argument( '-u', '--user', dest='email', type=str, help='Email used for login' From 790e56ccf92c1011ea3e6e9e6ad09141abe736c3 Mon Sep 17 00:00:00 2001 From: Alexis ANNEIX Date: Thu, 21 Jul 2022 20:16:28 +0200 Subject: [PATCH 3/9] Read token and JWT from config file and use them properly --- archfx_cloud/utils/main.py | 34 +++++++++++++++++++++------------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/archfx_cloud/utils/main.py b/archfx_cloud/utils/main.py index df9430c..869ffc5 100644 --- a/archfx_cloud/utils/main.py +++ b/archfx_cloud/utils/main.py @@ -112,24 +112,32 @@ def login(self) -> bool: Check if we can user token from .ini """ - # If there is a token defined, check if legal - ini_token_key = f'c-{self.args.customer}' + customer_section = f'c-{self.args.customer}' try: - ini_cloud = CONFIG[ini_token_key] - token = ini_cloud.get('token') + customer_config = CONFIG[customer_section] + + token = customer_config.get('token') + jwt = { + 'access': customer_config.get('jwt_access'), + 'refresh': customer_config.get('jwt_refresh') + } except (configparser.NoSectionError, KeyError): token = None + jwt = None if token: - self.api.set_token(token) - - try: - user = self.api.account.get() - LOG.info('Using token for {}'.format(user['results'][0]['email'])) - return True - except HttpClientError as err: - LOG.debug(err) - LOG.info('Token is illegal or has expired') + self.api.set_token(token, token_type='token') + elif jwt: + self.api.set_token(jwt, token_type='jwt') + + if self.api.token: + try: + user = self.api.account.get() + LOG.info('Using token for {}'.format(user['results'][0]['email'])) + return True + except HttpClientError as err: + LOG.debug(err) + LOG.info('Token is illegal or has expired') password = getpass.getpass() ok = self.api.login(email=self.args.email, password=password) From ce2a636b63a30dc8067fb75b875d987b1cb3942a Mon Sep 17 00:00:00 2001 From: Alexis ANNEIX Date: Thu, 21 Jul 2022 20:16:50 +0200 Subject: [PATCH 4/9] Allow not to provide email if token login used --- archfx_cloud/utils/main.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/archfx_cloud/utils/main.py b/archfx_cloud/utils/main.py index 869ffc5..ecc49b7 100644 --- a/archfx_cloud/utils/main.py +++ b/archfx_cloud/utils/main.py @@ -46,10 +46,6 @@ def __init__(self, config_path='.ini'): self.args = self.parser.parse_args() self.config_logging() - if not self.args.email: - LOG.error('User email is required: --user') - sys.exit(1) - def _critical_exit(self, msg): LOG.error(msg) sys.exit(1) @@ -139,6 +135,10 @@ def login(self) -> bool: LOG.debug(err) LOG.info('Token is illegal or has expired') + if not self.args.email: + LOG.error('User email is required: --user') + sys.exit(1) + password = getpass.getpass() ok = self.api.login(email=self.args.email, password=password) if ok: From fd24afbce111e9c9a7c079d557b6bf602fcc9f6e Mon Sep 17 00:00:00 2001 From: Alexis ANNEIX Date: Thu, 21 Jul 2022 20:17:04 +0200 Subject: [PATCH 5/9] Code cleaning --- archfx_cloud/api/connection.py | 2 +- archfx_cloud/utils/main.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/archfx_cloud/api/connection.py b/archfx_cloud/api/connection.py index f042bbd..bad836e 100644 --- a/archfx_cloud/api/connection.py +++ b/archfx_cloud/api/connection.py @@ -208,7 +208,7 @@ def __init__(self, domain=None, token_type=None, verify=True, timeout=None, retr self.domain = domain self.base_url = f"{self.domain}/{API_PREFIX}" - self.use_token = True + if token_type: self.token_type = token_type diff --git a/archfx_cloud/utils/main.py b/archfx_cloud/utils/main.py index ecc49b7..f10a6d1 100644 --- a/archfx_cloud/utils/main.py +++ b/archfx_cloud/utils/main.py @@ -25,7 +25,7 @@ def __init__(self, config_path='.ini'): Process any extra arguments Only hard codes one required argument: --user Additional arguments can be configured by overwriting the add_extra_args() method - Logging configuration can be changed by overwritting the config_logging() method + Logging configuration can be changed by overwriting the config_logging() method """ CONFIG.read(config_path) self.parser = argparse.ArgumentParser(description=__doc__) @@ -105,7 +105,7 @@ def get_domain(self) -> str: def login(self) -> bool: """ - Check if we can user token from .ini + Check if we can use token from .ini """ customer_section = f'c-{self.args.customer}' From 6ea3bf8e3a75d00d86e0dcdac374cd611366c1c6 Mon Sep 17 00:00:00 2001 From: Alexis ANNEIX Date: Thu, 21 Jul 2022 20:26:23 +0200 Subject: [PATCH 6/9] Update set_token tests in test_api.py --- tests/test_api.py | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/tests/test_api.py b/tests/test_api.py index 5a604fa..2d73895 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -5,7 +5,7 @@ import unittest from archfx_cloud.api.connection import Api -from archfx_cloud.api.exceptions import HttpClientError, HttpServerError +from archfx_cloud.api.exceptions import HttpClientError, HttpServerError, ImproperlyConfigured class ApiTestCase(unittest.TestCase): @@ -20,9 +20,28 @@ def test_init(self): def test_set_token(self): api = Api() self.assertEqual(api.token, None) + api.set_token('big-token') self.assertEqual(api.token, 'big-token') + api.set_token({ + 'access': 'access-token', + 'refresh': 'refresh-token', + }) + self.assertEqual(api.token, 'access-token') + self.assertEqual(api.refresh_token_data, 'refresh-token') + + api.set_token({ + 'token': 'another-token', + }) + self.assertEqual(api.token, 'another-token') + self.assertEqual(api.refresh_token_data, 'refresh-token') + + with self.assertRaises(ImproperlyConfigured): + api.set_token({ + 'other': 'dummy', + }) + @requests_mock.Mocker() def test_timeout(self, m): m.get('http://archfx.test/api/v1/timeout/', exc=requests.exceptions.ConnectTimeout ) From 68e601da6faeba74e6fd9e721d23fe0a9ed31906 Mon Sep 17 00:00:00 2001 From: Alexis ANNEIX Date: Thu, 21 Jul 2022 20:26:37 +0200 Subject: [PATCH 7/9] Add tests for BaseMain login features --- tests/data/test_main_login_token_file.ini | 2 + tests/test_main.py | 106 ++++++++++++++++++++++ 2 files changed, 108 insertions(+) create mode 100644 tests/data/test_main_login_token_file.ini create mode 100644 tests/test_main.py diff --git a/tests/data/test_main_login_token_file.ini b/tests/data/test_main_login_token_file.ini new file mode 100644 index 0000000..d5d6189 --- /dev/null +++ b/tests/data/test_main_login_token_file.ini @@ -0,0 +1,2 @@ +[c-test] +token=test-token diff --git a/tests/test_main.py b/tests/test_main.py new file mode 100644 index 0000000..4608b75 --- /dev/null +++ b/tests/test_main.py @@ -0,0 +1,106 @@ +import json +import os.path +import unittest +from argparse import Namespace + +import mock +import requests_mock + +from archfx_cloud.utils.main import BaseMain + + +class MainTestCase(unittest.TestCase): + @requests_mock.Mocker() + @mock.patch('archfx_cloud.utils.main.argparse.ArgumentParser.parse_args') + @mock.patch('archfx_cloud.utils.main.getpass.getpass') + def test_main_login_email_password_ok( + self, mock_request, mock_getpass, mock_parse_args + ): + mock_request.post( + 'https://test.archfx.io/api/v1/auth/login/', + text=json.dumps({'jwt': 'big-token', 'username': 'user1'}), + ) + mock_request.post( + 'https://test.archfx.io/api/v1/auth/logout/', status_code=204 + ) + mock_getpass.return_value = 'password' + mock_parse_args.return_value = Namespace( + customer='test', server_type='prod', email='user1@test.com' + ) + + main = BaseMain() + main.main() + + self.assertEqual(len(mock_request.request_history), 2) + self.assertEqual( + mock_request.request_history[0].url, + 'https://test.archfx.io/api/v1/auth/login/', + ) + self.assertTrue( + 'Authorization' not in mock_request.request_history[0].headers + ) + self.assertEqual( + json.loads(mock_request.request_history[0].body), + {'email': 'user1@test.com', 'password': 'password'}, + ) + self.assertEqual( + mock_request.request_history[1].url, + 'https://test.archfx.io/api/v1/auth/logout/', + ) + self.assertEqual( + mock_request.request_history[1].headers['Authorization'], + 'jwt big-token', + ) + + @requests_mock.Mocker() + @mock.patch('archfx_cloud.utils.main.argparse.ArgumentParser.parse_args') + def test_main_login_token_file_ok(self, mock_request, mock_parse_args): + mock_request.get( + 'https://test.archfx.io/api/v1/account/', + text=json.dumps({'results': [{'email': 'user1@test.com'}]}), + ) + mock_request.post( + 'https://test.archfx.io/api/v1/auth/logout/', status_code=204 + ) + mock_parse_args.return_value = Namespace( + customer='test', + server_type='prod', + ) + + main = BaseMain( + config_path=f'{os.path.dirname(__file__)}/data/test_main_login_token_file.ini' + ) + main.main() + + self.assertEqual(len(mock_request.request_history), 2) + self.assertEqual( + mock_request.request_history[0].url, + 'https://test.archfx.io/api/v1/account/', + ) + self.assertEqual( + mock_request.request_history[0].headers['Authorization'], + 'token test-token', + ) + self.assertEqual( + mock_request.request_history[1].url, + 'https://test.archfx.io/api/v1/auth/logout/', + ) + self.assertEqual( + mock_request.request_history[1].headers['Authorization'], + 'token test-token', + ) + + @mock.patch('archfx_cloud.utils.main.argparse.ArgumentParser.parse_args') + def test_main_login_token_file_ko(self, mock_parse_args): + mock_parse_args.return_value = Namespace( + customer='test', + server_type='prod', + email=None, + ) + + main = BaseMain(config_path=f'non_existing_config_file.ini') + + with self.assertRaises(SystemExit) as e: + main.main() + + self.assertEqual(e.exception.code, 1) From 5591cceba97b6bd55e958d21e3b26dc1610d7a0c Mon Sep 17 00:00:00 2001 From: Alexis ANNEIX Date: Thu, 21 Jul 2022 20:32:28 +0200 Subject: [PATCH 8/9] Update RELEASE.md file --- RELEASE.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/RELEASE.md b/RELEASE.md index c4e3618..4efb6ea 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -2,6 +2,13 @@ All major changes in each released version of the archfx-cloud plugin are listed here. +## 0.14.0 + +- Add optional parameter to BaseMain constructor to give the config file path (.ini) +- Config file can configure JWT token (access and refresh) +- Provided user email is not required anymore if logging in via token +- Avoid one API request with empty token if no token provided + ## 0.13.0 - Removed unneed pytz dependency From a5474fccafef1291fd7eb1b1e9a2702b708e7b48 Mon Sep 17 00:00:00 2001 From: Alexis ANNEIX Date: Mon, 1 Aug 2022 13:18:10 +0200 Subject: [PATCH 9/9] Bump version to 0.14.0 --- version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.py b/version.py index af87af8..449f1a3 100644 --- a/version.py +++ b/version.py @@ -1 +1 @@ -version = '0.13.0' +version = '0.14.0'