From 01eb4419bc972030c715d3576fdac3662b4bc0a5 Mon Sep 17 00:00:00 2001 From: theborch Date: Tue, 29 Aug 2023 12:10:32 -0500 Subject: [PATCH 01/27] add PYBRITIVE_BROWSER env var option --- docs/index.md | 13 ++++++++++++- src/pybritive/britive_cli.py | 2 +- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/docs/index.md b/docs/index.md index 6fa7ca0..3778d04 100644 --- a/docs/index.md +++ b/docs/index.md @@ -211,11 +211,22 @@ provided `pybritive` will use an internally generated passphrase unique to the m ## Home Directory By default, files that `pybritive` requires will be persisted to `~/.britive/`. -This can be overwritten by specifying environment variable `PYBRITIVE_HOME_DIR`. This should be a path to where +This can be overwritten by specifying environment variable `PYBRITIVE_HOME_DIR`. This can be either one of the following choices to where the end user wants to persist the `.britive` directory. Note that `.britive` will still be created so do not specify that as part of the path. +## Browser +By default, `pybritive` will use the OS defined default for any actions that have browser interaction(s). + +This can be overwritten by specifying environment variable `PYBRITIVE_BROWSER`. This can either be a one of the choices listed for commands +that have the `--browser` option/flag, or can be set to an open command for browsers not provided by the Python3 `webbrowser` module. + +Example: +~~~bash +export PYBRITIVE_BROWSER="open -a /Applications/Firefox\ Developer\ Edition.app %s" +~~~ + ## Escaping If the name of an application, environment, or profile contains a `/` then that character must be properly escaped with a `\`. diff --git a/src/pybritive/britive_cli.py b/src/pybritive/britive_cli.py index 96ee9b4..514c09e 100644 --- a/src/pybritive/britive_cli.py +++ b/src/pybritive/britive_cli.py @@ -1053,7 +1053,7 @@ def aws_console(profile, duration, browser): # but use the url to pop open a browser console_url = requests.Request('GET', url, params=params).prepare().url - browser = webbrowser.get(using=browser) + browser = webbrowser.get(using=os.getenv("PYBRITIVE_BROWSER", browser)) browser.open(console_url) def request_disposition(self, request_id, decision): From 621d48cb288760eea94ed51ee7e71b62ed350690 Mon Sep 17 00:00:00 2001 From: theborch Date: Tue, 29 Aug 2023 12:10:51 -0500 Subject: [PATCH 02/27] removing unnecessary string --- src/pybritive/choices/browser.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pybritive/choices/browser.py b/src/pybritive/choices/browser.py index 1bae818..2836c6e 100644 --- a/src/pybritive/choices/browser.py +++ b/src/pybritive/choices/browser.py @@ -4,7 +4,6 @@ browser_choices = click.Choice( [ - 'default' 'mozilla', 'firefox', 'windows-default', From 63eb97b2bc7ff45b20978b36d175ec71e98f7931 Mon Sep 17 00:00:00 2001 From: theborch Date: Tue, 29 Aug 2023 12:14:57 -0500 Subject: [PATCH 03/27] respect browser choice on interactive login --- src/pybritive/commands/login.py | 4 ++-- src/pybritive/helpers/credentials.py | 7 ++++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/pybritive/commands/login.py b/src/pybritive/commands/login.py index bcfa56f..c968db1 100644 --- a/src/pybritive/commands/login.py +++ b/src/pybritive/commands/login.py @@ -5,8 +5,8 @@ @click.command() @build_britive -@britive_options(names='tenant,token,silent,passphrase,federation_provider') -def login(ctx, tenant, token, silent, passphrase, federation_provider): +@britive_options(names='tenant,token,silent,passphrase,federation_provider,browser') +def login(ctx, tenant, token, silent, passphrase, federation_provider, browser): """Perform an interactive login to obtain temporary credentials. This only applies when an API token has not been specified via `--token,-T` or via environment variable diff --git a/src/pybritive/helpers/credentials.py b/src/pybritive/helpers/credentials.py index 0aac1b8..02d93ec 100644 --- a/src/pybritive/helpers/credentials.py +++ b/src/pybritive/helpers/credentials.py @@ -46,12 +46,13 @@ def b64_encode_url_safe(value: bytes): # this base class expects self.credentials to be a dict - so sub classes need to convert to dict class CredentialManager: - def __init__(self, tenant_name: str, tenant_alias: str, cli: any, federation_provider: str = None): + def __init__(self, tenant_name: str, tenant_alias: str, cli: any, federation_provider: str = None, browser: str = os.getenv("PYBRITIVE_BROWSER")): self.cli = cli self.tenant = tenant_name self.alias = tenant_alias self.base_url = f'https://{Britive.parse_tenant(tenant_name)}' self.federation_provider = federation_provider + self.browser = browser self.session = None # not sure if we really need 32 random bytes or if any random string would work @@ -90,8 +91,8 @@ def perform_interactive_login(self): self._setup_requests_session() try: - webbrowser.get() - webbrowser.open(url) + browser = webbrowser.get(using=self.browser) + browser.open(url) except webbrowser.Error: self.cli.print( 'No web browser found. Please manually navigate to the link below and authenticate.' From 58b10b1c2f7e7182e9f554ccebcefba667a0fca1 Mon Sep 17 00:00:00 2001 From: theborch Date: Tue, 29 Aug 2023 12:39:45 -0500 Subject: [PATCH 04/27] version bump, 1.5.0 --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 76e0f65..e2faa16 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = pybritive -version = 1.4.0 +version = 1.5.0 author = Britive Inc. author_email = support@britive.com description = A pure Python CLI for Britive From a5391e2f8b6cb1acb6c4611c054750db5577ac73 Mon Sep 17 00:00:00 2001 From: theborch Date: Wed, 30 Aug 2023 16:00:26 -0500 Subject: [PATCH 05/27] respect browser choice on checkout --- src/pybritive/britive_cli.py | 13 ++++++++----- src/pybritive/helpers/cloud_credential_printer.py | 4 +++- src/pybritive/helpers/credentials.py | 6 +++--- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/src/pybritive/britive_cli.py b/src/pybritive/britive_cli.py index 514c09e..d3a1a1d 100644 --- a/src/pybritive/britive_cli.py +++ b/src/pybritive/britive_cli.py @@ -28,7 +28,7 @@ class BritiveCli: def __init__(self, tenant_name: str = None, token: str = None, silent: bool = False, - passphrase: str = None, federation_provider: str = None): + passphrase: str = None, federation_provider: str = None, browser: str = os.getenv('PYBRITIVE_BROWSER')): self.silent = silent self.output_format = None self.tenant_name = None @@ -43,6 +43,7 @@ def __init__(self, tenant_name: str = None, token: str = None, silent: bool = Fa self.credential_manager = None self.verbose_checkout = False self.checkout_progress_previous_message = None + self.browser = browser def set_output_format(self, output_format: str): self.output_format = self.config.get_output_format(output_format) @@ -56,7 +57,8 @@ def set_credential_manager(self): tenant_alias=self.tenant_alias, tenant_name=self.tenant_name, cli=self, - federation_provider=self.federation_provider + federation_provider=self.federation_provider, + browser=self.browser ) elif backend == 'encrypted-file': self.credential_manager = EncryptedFileCredentialManager( @@ -64,7 +66,8 @@ def set_credential_manager(self): tenant_name=self.tenant_name, cli=self, passphrase=self.passphrase, - federation_provider=self.federation_provider + federation_provider=self.federation_provider, + browser=self.browser ) else: raise click.ClickException(f'invalid credential backend {backend}.') @@ -474,8 +477,8 @@ def checkout(self, alias, blocktime, console, justification, mode, maxpolltime, # these 2 modes implicitly say that console access should be checked out without having to provide # the --console flag - if mode and (mode == 'console' or mode.startswith('browser')): - console = True + if console := (mode and (mode == 'console' or mode.startswith('browser'))): + self.browser = mode.replace("browser-","") self._validate_justification(justification) diff --git a/src/pybritive/helpers/cloud_credential_printer.py b/src/pybritive/helpers/cloud_credential_printer.py index 681bbc2..5e07b86 100644 --- a/src/pybritive/helpers/cloud_credential_printer.py +++ b/src/pybritive/helpers/cloud_credential_printer.py @@ -1,5 +1,6 @@ import json import uuid +import os import click import platform import configparser @@ -65,7 +66,8 @@ def print(self): def print_console(self): url = self.credentials.get('url', self.credentials) if self.mode == 'browser': - webbrowser.get(self.mode_modifier).open(url) + browser = self.mode_modifier or os.getenv('PYBRITIVE_BROWSER') + webbrowser.get(using=browser).open(url) else: self.cli.print(url, ignore_silent=True) diff --git a/src/pybritive/helpers/credentials.py b/src/pybritive/helpers/credentials.py index 02d93ec..c77383c 100644 --- a/src/pybritive/helpers/credentials.py +++ b/src/pybritive/helpers/credentials.py @@ -236,10 +236,10 @@ def has_valid_credentials(self): class FileCredentialManager(CredentialManager): - def __init__(self, tenant_name: str, tenant_alias: str, cli: any, federation_provider: str = None): + def __init__(self, tenant_name: str, tenant_alias: str, cli: any, federation_provider: str = None, browser: str = os.getenv('PYBRITIVE_BROWSER')): home = os.getenv('PYBRITIVE_HOME_DIR', str(Path.home())) self.path = str(Path(home) / '.britive' / 'pybritive.credentials') - super().__init__(tenant_name, tenant_alias, cli, federation_provider) + super().__init__(tenant_name, tenant_alias, cli, federation_provider, browser) def load(self, full=False): path = Path(self.path) @@ -278,7 +278,7 @@ def delete(self): class EncryptedFileCredentialManager(CredentialManager): def __init__(self, tenant_name: str, tenant_alias: str, cli: any, passphrase: str = None, - federation_provider: str = None): + federation_provider: str = None, browser: str = os.getenv('PYBRITIVE_BROWSER')): home = os.getenv('PYBRITIVE_HOME_DIR', str(Path.home())) self.path = str(Path(home) / '.britive' / 'pybritive.credentials.encrypted') self.passphrase = passphrase From 4421149bfa38d6559f4c629327d1e01e3a4e6c88 Mon Sep 17 00:00:00 2001 From: theborch Date: Wed, 30 Aug 2023 16:07:37 -0500 Subject: [PATCH 06/27] single quote preference --- src/pybritive/britive_cli.py | 2 +- src/pybritive/helpers/credentials.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pybritive/britive_cli.py b/src/pybritive/britive_cli.py index d3a1a1d..a2003f0 100644 --- a/src/pybritive/britive_cli.py +++ b/src/pybritive/britive_cli.py @@ -1056,7 +1056,7 @@ def aws_console(profile, duration, browser): # but use the url to pop open a browser console_url = requests.Request('GET', url, params=params).prepare().url - browser = webbrowser.get(using=os.getenv("PYBRITIVE_BROWSER", browser)) + browser = webbrowser.get(using=os.getenv('PYBRITIVE_BROWSER', browser)) browser.open(console_url) def request_disposition(self, request_id, decision): diff --git a/src/pybritive/helpers/credentials.py b/src/pybritive/helpers/credentials.py index c77383c..c5063f2 100644 --- a/src/pybritive/helpers/credentials.py +++ b/src/pybritive/helpers/credentials.py @@ -46,7 +46,7 @@ def b64_encode_url_safe(value: bytes): # this base class expects self.credentials to be a dict - so sub classes need to convert to dict class CredentialManager: - def __init__(self, tenant_name: str, tenant_alias: str, cli: any, federation_provider: str = None, browser: str = os.getenv("PYBRITIVE_BROWSER")): + def __init__(self, tenant_name: str, tenant_alias: str, cli: any, federation_provider: str = None, browser: str = os.getenv('PYBRITIVE_BROWSER')): self.cli = cli self.tenant = tenant_name self.alias = tenant_alias From c5a459786e6c76471223cc132ef2c9c8ad0683fa Mon Sep 17 00:00:00 2001 From: theborch Date: Wed, 30 Aug 2023 16:08:34 -0500 Subject: [PATCH 07/27] grammar ocpd --- src/pybritive/commands/api.py | 2 +- src/pybritive/commands/checkin.py | 2 +- src/pybritive/commands/checkout.py | 2 +- src/pybritive/commands/clear.py | 2 +- src/pybritive/commands/request.py | 2 +- ..._argument_dectorator.py => api_method_argument_decorator.py} | 2 -- ...ile_argument_dectorator.py => profile_argument_decorator.py} | 2 -- 7 files changed, 5 insertions(+), 9 deletions(-) rename src/pybritive/helpers/{api_method_argument_dectorator.py => api_method_argument_decorator.py} (99%) rename src/pybritive/helpers/{profile_argument_dectorator.py => profile_argument_decorator.py} (99%) diff --git a/src/pybritive/commands/api.py b/src/pybritive/commands/api.py index fd41992..a39ff26 100644 --- a/src/pybritive/commands/api.py +++ b/src/pybritive/commands/api.py @@ -3,7 +3,7 @@ from ..helpers.build_britive import build_britive from ..options.britive_options import britive_options from ..completers.api_command import command_api_patch_shell_complete -from ..helpers.api_method_argument_dectorator import click_smart_api_method_argument +from ..helpers.api_method_argument_decorator import click_smart_api_method_argument # this holds all the click version logic to gracefully degrade functionality diff --git a/src/pybritive/commands/checkin.py b/src/pybritive/commands/checkin.py index e693764..95c6dbf 100644 --- a/src/pybritive/commands/checkin.py +++ b/src/pybritive/commands/checkin.py @@ -1,7 +1,7 @@ import click from ..helpers.build_britive import build_britive from ..options.britive_options import britive_options -from ..helpers.profile_argument_dectorator import click_smart_profile_argument +from ..helpers.profile_argument_decorator import click_smart_profile_argument @click.command() diff --git a/src/pybritive/commands/checkout.py b/src/pybritive/commands/checkout.py index ae2a864..e4cbbe5 100644 --- a/src/pybritive/commands/checkout.py +++ b/src/pybritive/commands/checkout.py @@ -1,7 +1,7 @@ import click from ..helpers.build_britive import build_britive from ..options.britive_options import britive_options -from ..helpers.profile_argument_dectorator import click_smart_profile_argument +from ..helpers.profile_argument_decorator import click_smart_profile_argument @click.command() diff --git a/src/pybritive/commands/clear.py b/src/pybritive/commands/clear.py index b845231..62674d4 100644 --- a/src/pybritive/commands/clear.py +++ b/src/pybritive/commands/clear.py @@ -1,6 +1,6 @@ import click from ..helpers.build_britive import build_britive -from ..helpers.profile_argument_dectorator import click_smart_profile_argument +from ..helpers.profile_argument_decorator import click_smart_profile_argument @click.group() def clear(): diff --git a/src/pybritive/commands/request.py b/src/pybritive/commands/request.py index d7574f1..b18fd8e 100644 --- a/src/pybritive/commands/request.py +++ b/src/pybritive/commands/request.py @@ -1,7 +1,7 @@ import click from ..helpers.build_britive import build_britive from ..options.britive_options import britive_options -from ..helpers.profile_argument_dectorator import click_smart_profile_argument +from ..helpers.profile_argument_decorator import click_smart_profile_argument @click.group() diff --git a/src/pybritive/helpers/api_method_argument_dectorator.py b/src/pybritive/helpers/api_method_argument_decorator.py similarity index 99% rename from src/pybritive/helpers/api_method_argument_dectorator.py rename to src/pybritive/helpers/api_method_argument_decorator.py index 33173b9..edead14 100644 --- a/src/pybritive/helpers/api_method_argument_dectorator.py +++ b/src/pybritive/helpers/api_method_argument_decorator.py @@ -11,5 +11,3 @@ def click_smart_api_method_argument(func): else: dec = click.argument('method') return dec(func) - - diff --git a/src/pybritive/helpers/profile_argument_dectorator.py b/src/pybritive/helpers/profile_argument_decorator.py similarity index 99% rename from src/pybritive/helpers/profile_argument_dectorator.py rename to src/pybritive/helpers/profile_argument_decorator.py index 3500dca..ed5fa5d 100644 --- a/src/pybritive/helpers/profile_argument_dectorator.py +++ b/src/pybritive/helpers/profile_argument_decorator.py @@ -11,5 +11,3 @@ def click_smart_profile_argument(func): else: dec = click.argument('profile') return dec(func) - - From e47d258559f1d65cca58dbe6ca92e4e94dd73c02 Mon Sep 17 00:00:00 2001 From: theborch Date: Wed, 30 Aug 2023 18:49:26 -0500 Subject: [PATCH 08/27] lint tests/ --- tests/conftest.py | 4 ---- tests/test_0100_version.py | 1 - tests/test_0300_user.py | 4 ---- tests/test_0450_api.py | 7 ------- tests/test_0500_secret.py | 3 --- tests/test_0600_cache.py | 5 ----- tests/test_0800_checkin.py | 4 ---- tests/test_0850_clear.py | 6 ------ tests/test_0900_login.py | 4 ---- tests/test_1000_logout.py | 5 ----- 10 files changed, 43 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index c227a52..1164d7b 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -74,7 +74,3 @@ def unset_api_token_env_var(): name = 'BRITIVE_API_TOKEN' if name in os.environ.keys(): del os.environ[name] - - - - diff --git a/tests/test_0100_version.py b/tests/test_0100_version.py index 152b77d..3529f32 100644 --- a/tests/test_0100_version.py +++ b/tests/test_0100_version.py @@ -17,4 +17,3 @@ def test_version_short_flag(runner, cli): with runner.isolated_filesystem(): result = runner.invoke(cli, ['-v']) common_asserts(result) - diff --git a/tests/test_0300_user.py b/tests/test_0300_user.py index f7bb297..65af112 100644 --- a/tests/test_0300_user.py +++ b/tests/test_0300_user.py @@ -5,7 +5,3 @@ def test_user(runner, cli): result = runner.invoke(cli, ['user']) tenant = os.getenv('PYBRITIVE_TEST_TENANT') assert f'@ {tenant}' in result.output - - - - diff --git a/tests/test_0450_api.py b/tests/test_0450_api.py index ba3e06b..cb747cc 100644 --- a/tests/test_0450_api.py +++ b/tests/test_0450_api.py @@ -14,10 +14,3 @@ def common_asserts(result, substring=None, exit_code=0): def test_api(runner, cli): result = runner.invoke(cli, 'api users.list'.split(' ')) common_asserts(result, ['userId', 'status', 'email', 'identityProvider']) - - - - - - - diff --git a/tests/test_0500_secret.py b/tests/test_0500_secret.py index c542b67..46cf152 100644 --- a/tests/test_0500_secret.py +++ b/tests/test_0500_secret.py @@ -35,6 +35,3 @@ def test_download_filename_provided(runner, cli): assert 'test' in f.read() path = Path(filename) path.unlink(missing_ok=True) - - - diff --git a/tests/test_0600_cache.py b/tests/test_0600_cache.py index 6c4c4af..ed72bc1 100644 --- a/tests/test_0600_cache.py +++ b/tests/test_0600_cache.py @@ -13,8 +13,3 @@ def test_cache_profiles(runner, cli): assert 'profiles' in data.keys() assert len(data['profiles']) > 0 assert len(data['profiles'][0].split('/')) in [2, 3] - - - - - diff --git a/tests/test_0800_checkin.py b/tests/test_0800_checkin.py index ee77630..102823b 100644 --- a/tests/test_0800_checkin.py +++ b/tests/test_0800_checkin.py @@ -3,7 +3,3 @@ def test_checkin(runner, cli, profile): result = runner.invoke(cli, ['checkin', profile]) assert result.exit_code == 0 assert result.output == '' - - - - diff --git a/tests/test_0850_clear.py b/tests/test_0850_clear.py index 4be00cf..f6d4658 100644 --- a/tests/test_0850_clear.py +++ b/tests/test_0850_clear.py @@ -33,9 +33,3 @@ def test_clear_gcloud_key_files(runner, cli): assert not file1.is_file() assert not file2.is_file() assert not path.is_dir() - - - - - - diff --git a/tests/test_0900_login.py b/tests/test_0900_login.py index 22ca587..70a46e5 100644 --- a/tests/test_0900_login.py +++ b/tests/test_0900_login.py @@ -17,7 +17,3 @@ def test_login_interactive(runner, cli, unset_api_token_env_var): data = f.read() assert 'accessToken=' in data assert os.getenv('PYBRITIVE_TEST_TENANT') in data - - - - diff --git a/tests/test_1000_logout.py b/tests/test_1000_logout.py index b70a945..c7ea213 100644 --- a/tests/test_1000_logout.py +++ b/tests/test_1000_logout.py @@ -10,8 +10,3 @@ def test_logout(runner, cli): with open(str(path), 'r') as f: data = f.read() assert len(data) == 0 - - - - - From 6ff4061824e5c672de2753d57f0f0b47e02a43df Mon Sep 17 00:00:00 2001 From: theborch Date: Wed, 30 Aug 2023 18:49:43 -0500 Subject: [PATCH 09/27] sort lexicographically --- requirements.txt | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/requirements.txt b/requirements.txt index 403d565..89770ed 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,22 +1,22 @@ +boto3 britive>=2.20.1 certifi>=2022.12.7 charset-normalizer==2.1.0 click==8.1.3 +cryptography>=41.0.0 +google-cloud-compute idna==3.3 +jmespath~=1.0.1 merge-args==0.1.5 +mkdocs-click==0.8.0 +mkdocs==1.3.1 +pyjwt~=2.6.0 +pytest~=7.1.2 +python-dateutil~=2.8.2 PyYAML==6.0 requests>=2.31.0 six==1.16.0 tabulate==0.8.10 toml==0.10.2 -urllib3==1.26.9 -cryptography>=41.0.0 -pytest~=7.1.2 -mkdocs==1.3.1 -mkdocs-click==0.8.0 twine~=4.0.1 -python-dateutil~=2.8.2 -boto3 -jmespath~=1.0.1 -pyjwt~=2.6.0 -google-cloud-compute +urllib3==1.26.9 From df22b9bde6fe6993dc66b0f77a75551d85acdce4 Mon Sep 17 00:00:00 2001 From: theborch Date: Wed, 30 Aug 2023 18:50:46 -0500 Subject: [PATCH 10/27] lint helpers --- .../helpers/aws_credential_process.py | 14 ++--- src/pybritive/helpers/build_britive.py | 8 +-- src/pybritive/helpers/cache.py | 9 ++-- .../helpers/cloud_credential_printer.py | 16 +++--- src/pybritive/helpers/config.py | 54 +++++++++---------- src/pybritive/helpers/credentials.py | 27 +++++----- src/pybritive/helpers/encryption.py | 10 ++-- 7 files changed, 69 insertions(+), 69 deletions(-) diff --git a/src/pybritive/helpers/aws_credential_process.py b/src/pybritive/helpers/aws_credential_process.py index 1592c97..a150c61 100644 --- a/src/pybritive/helpers/aws_credential_process.py +++ b/src/pybritive/helpers/aws_credential_process.py @@ -1,7 +1,7 @@ def get_args(): from getopt import getopt # lazy load from sys import argv # lazy load - options, non_options = getopt(argv[1:], 't:T:p:f:P:hv', [ + options = getopt(argv[1:], 't:T:p:f:P:hv', [ 'tenant=', 'token=', 'passphrase=', @@ -9,7 +9,7 @@ def get_args(): 'profile=', 'help', 'version' - ]) + ])[0] args = { 'tenant': None, @@ -39,15 +39,15 @@ def get_args(): print( f'pybritive: {cli_version} / platform: {platform()} / python: {python_version()}' ) - exit() + raise SystemExit() return args def usage(): from sys import argv # lazy load - print("Usage : %s --profile [-t/--tenant, -T/--token, -t/--passphrase, -f/--force-renew]" % (argv[0])) - exit() + print(f'Usage : {argv[0]} --profile [-t/--tenant, -T/--token, -t/--passphrase, -f/--force-renew]') + raise SystemExit() def main(): @@ -74,7 +74,7 @@ def main(): json += f'"Expiration": "{creds["expirationTime"]}",' json += '"Version": 1}' print(json) - exit() + raise SystemExit() if not creds: from ..britive_cli import BritiveCli # lazy load for performance purposes @@ -94,7 +94,7 @@ def main(): gcloud_key_file=None, verbose=None ) - exit() + raise SystemExit() if __name__ == '__main__': diff --git a/src/pybritive/helpers/build_britive.py b/src/pybritive/helpers/build_britive.py index c5f1129..ed078ab 100644 --- a/src/pybritive/helpers/build_britive.py +++ b/src/pybritive/helpers/build_britive.py @@ -1,8 +1,9 @@ -from ..britive_cli import BritiveCli -import click +from dataclasses import dataclass from functools import wraps + +import click from merge_args import merge_args -from dataclasses import dataclass +from ..britive_cli import BritiveCli @dataclass @@ -26,7 +27,6 @@ def wrapper( passphrase=kwargs.get('passphrase'), federation_provider=kwargs.get('federation_provider') )) - parent_command = ctx.parent.command.name if parent_command != 'configure': ctx.obj.britive.set_output_format(kwargs.get('output_format')) diff --git a/src/pybritive/helpers/cache.py b/src/pybritive/helpers/cache.py index ec5fa19..244ceda 100644 --- a/src/pybritive/helpers/cache.py +++ b/src/pybritive/helpers/cache.py @@ -1,7 +1,6 @@ -from pathlib import Path import json import os - +from pathlib import Path from .encryption import StringEncryption, InvalidPassphraseException @@ -22,9 +21,9 @@ def load(self): cache = { 'profiles': [] } - path.write_text(json.dumps(cache, indent=2, default=str)) + path.write_text(json.dumps(cache, indent=2, default=str), encoding='utf-8') - with open(str(self.path), 'r') as f: + with open(str(self.path), 'r', encoding='utf-8') as f: try: self.cache = json.loads(f.read()) except json.decoder.JSONDecodeError: @@ -37,7 +36,7 @@ def load(self): def write(self): # write the new cache file - with open(str(self.path), 'w') as f: + with open(str(self.path), 'w', encoding='utf-8') as f: f.write(json.dumps(self.cache, indent=2, default=str)) def get_profiles(self): diff --git a/src/pybritive/helpers/cloud_credential_printer.py b/src/pybritive/helpers/cloud_credential_printer.py index 5e07b86..372d839 100644 --- a/src/pybritive/helpers/cloud_credential_printer.py +++ b/src/pybritive/helpers/cloud_credential_printer.py @@ -1,11 +1,11 @@ +import configparser import json -import uuid import os -import click +from pathlib import Path import platform -import configparser +import uuid import webbrowser -from pathlib import Path +import click # trailing spaces matter as some options do not have the trailing space @@ -39,7 +39,7 @@ def __init__(self, app_type, console, mode, profile, silent, credentials, cli): if self.mode_modifier: self.env_command = env_options[self.mode_modifier] else: - self.on_windows = True if platform.system().lower() == 'windows' else False + self.on_windows = platform.system().lower() == 'windows' self.env_command = env_options['wincmd'] if self.on_windows else env_options['nix'] def print(self): @@ -161,7 +161,7 @@ def print_integrate(self): # if credentials file does not yet exist, create it as an empty file if not path.is_file(): path.parent.mkdir(exist_ok=True, parents=True) - path.write_text('') + path.write_text('', encoding='utf-8') # open the file with configparser config = configparser.ConfigParser() @@ -176,7 +176,7 @@ def print_integrate(self): } # write the new credentials file - with open(str(path), 'w') as f: + with open(str(path), 'w', encoding='utf-8') as f: config.write(f, space_around_delimiters=False) def print_awscredentialprocess(self): @@ -247,7 +247,7 @@ def print_gcloudauth(self): # key file does not yet exist so write to it path.parent.mkdir(exist_ok=True, parents=True) - path.write_text(json.dumps(self.credentials, indent=2)) + path.write_text(json.dumps(self.credentials, indent=2), encoding='utf-8') self.cli.print( f"gcloud auth activate-service-account {self.credentials['client_email']} --key-file {str(path)}", diff --git a/src/pybritive/helpers/config.py b/src/pybritive/helpers/config.py index 803b0ed..6f97df4 100644 --- a/src/pybritive/helpers/config.py +++ b/src/pybritive/helpers/config.py @@ -1,14 +1,15 @@ -import os -import shutil -from pathlib import Path -import click import configparser import json +import os +from pathlib import Path +import shutil import toml -from ..choices.output_format import output_format_choices + +from britive.britive import Britive +import click from ..choices.backend import backend_choices from ..choices.mode import mode_choices -from britive.britive import Britive +from ..choices.output_format import output_format_choices from ..helpers.split import profile_split @@ -20,13 +21,12 @@ def lowercase(obj): """ Make dictionary lowercase """ if isinstance(obj, dict): return {k.lower(): lowercase(v) for k, v in obj.items()} - elif isinstance(obj, (list, set, tuple)): + if isinstance(obj, (list, set, tuple)): t = type(obj) return t(lowercase(o) for o in obj) - elif isinstance(obj, str): + if isinstance(obj, str): return obj.lower() - else: - return obj + return obj def coalesce(*arg): @@ -93,7 +93,7 @@ def load(self, force=False): if not path.is_file(): # config file does not yet exist, create it as an empty file path.parent.mkdir(exist_ok=True, parents=True) - path.write_text('') + path.write_text('', encoding='utf-8') config = configparser.ConfigParser() config.optionxform = str # maintain key case @@ -104,7 +104,7 @@ def load(self, force=False): self.alias = None # will be set in self.get_tenant() self.default_tenant = self.config.get('global', {}).get('default_tenant') self.tenants = {} - for key in list(self.config.keys()): + for key in self.config: if key.startswith('tenant-'): alias = extract_tenant(key) self.tenants[alias] = self.config[key] @@ -120,22 +120,22 @@ def get_tenant(self): name = self.tenant_name.lower() if self.tenant_name else None # do some error checking to ensure we can actually grab a tenant - if len(self.tenants.keys()) == 0 and not name: + if len(self.tenants) == 0 and not name: raise click.ClickException(f'No tenants found in {self.path}. Cannot continue.') # attempt to determine the name of the tenant based on what the user passed in (or didn't pass in) provided_tenant_name = name if name else self.default_tenant if not provided_tenant_name: # name not provided and no default has been set - if len(self.tenants.keys()) != 1: - raise click.ClickException('Tenant not provided, no default tenant set, and more than one ' - 'tenant exists.') - else: - # nothing given but only 1 tenant so assume that is what should be used - provided_tenant_name = list(self.tenants.keys())[0] + if len(self.tenants) != 1: + raise click.ClickException( + 'Tenant not provided, no default tenant set, and more than one tenant exists.' + ) + # nothing given but only 1 tenant so assume that is what should be used + provided_tenant_name = list(self.tenants)[0] # if we get here then we now have a tenant name we can check to ensure exists - if provided_tenant_name not in self.tenants.keys() and not name: + if provided_tenant_name not in self.tenants and not name: raise click.ClickException(f'Tenant name "{provided_tenant_name}" not found in {self.path}') self.alias = provided_tenant_name or name @@ -149,14 +149,14 @@ def save(self): config.read_dict(self.config) # write the new credentials file - with open(str(self.path), 'w') as f: + with open(str(self.path), 'w', encoding='utf-8') as f: config.write(f, space_around_delimiters=False) def save_tenant(self, tenant: str, alias: str = None, output_format: str = None): self.load() if not alias: alias = tenant - if f'tenant-{alias}' not in self.config.keys(): + if f'tenant-{alias}' not in self.config: self.config[f'tenant-{alias}'] = {} self.config[f'tenant-{alias}']['name'] = tenant if output_format: @@ -167,7 +167,7 @@ def save_global(self, default_tenant_name: str = None, output_format: str = None self.load() if not default_tenant_name and not output_format and not backend: return - if 'global' not in self.config.keys(): + if 'global' not in self.config: self.config['global'] = {} if default_tenant_name: self.config['global']['default_tenant'] = default_tenant_name @@ -186,7 +186,7 @@ def save_profile_alias(self, alias, profile): def import_global_npm_config(self): self.load() path = str(Path(self.home) / '.britive' / 'config') # handle os specific separators properly - with open(path, 'r') as f: + with open(path, 'r', encoding='utf-8') as f: npm_config = toml.load(f) tenant = npm_config.get('tenantURL', '').replace('https://', '').replace('.britive-app.com', '').lower() output_format = npm_config.get('output_format', '').lower() @@ -227,9 +227,9 @@ def aws_default_checkout_mode(self): def update(self, section, field, value): self.load() - if section not in self.config.keys(): + if section not in self.config: self.config[section] = {} - if field not in self.config[section].keys(): + if field not in self.config[section]: self.config[section][field] = '' self.config[section][field] = value self.save() @@ -269,7 +269,7 @@ def validate_global(self, section, fields): self.validation_error_messages.append(error) if field == 'default_tenant': tenant_aliases_from_sections = [ - extract_tenant(t) for t in self.config.keys() if t.startswith('tenant-') + extract_tenant(t) for t in self.config if t.startswith('tenant-') ] if value not in tenant_aliases_from_sections: error = f'Invalid {section} field {field} value {value} provided. Tenant not found.' diff --git a/src/pybritive/helpers/credentials.py b/src/pybritive/helpers/credentials.py index c5063f2..e30ee07 100644 --- a/src/pybritive/helpers/credentials.py +++ b/src/pybritive/helpers/credentials.py @@ -1,19 +1,20 @@ -import random import base64 +import configparser import hashlib +import json +import os +from pathlib import Path +import random import time import webbrowser import requests -from requests.adapters import HTTPAdapter, Retry -from pathlib import Path -import click -import configparser -import json -import os -from .encryption import StringEncryption, InvalidPassphraseException + from britive.britive import Britive +import click from dateutil import parser import jwt +from requests.adapters import HTTPAdapter, Retry +from .encryption import StringEncryption, InvalidPassphraseException interactive_login_fields_to_pop = [ @@ -172,7 +173,7 @@ def perform_federation_provider_authentication(self): 'verify_aud': False } )['exp'] * 1000 - except Exception as e: + except Exception: self.cli.print(f'Cannot obtain token expiration time for {self.federation_provider}. Defaulting to ' f'{federation_provider_default_expiration_seconds} seconds.') @@ -245,7 +246,7 @@ def load(self, full=False): path = Path(self.path) if not path.is_file(): # credentials file does not yet exist, create it as an empty file path.parent.mkdir(exist_ok=True, parents=True) - path.write_text('') + path.write_text('', encoding='utf-8') # open the file with configparser credentials = configparser.ConfigParser() @@ -268,7 +269,7 @@ def save(self, credentials: dict): config.read_dict(full_credentials) # write the new credentials file - with open(str(self.path), 'w') as f: + with open(str(self.path), 'w', encoding='utf-8') as f: config.write(f, space_around_delimiters=False) self.credentials = credentials @@ -301,7 +302,7 @@ def load(self, full=False): path = Path(self.path) if not path.is_file(): # credentials file does not yet exist, create it as an empty file path.parent.mkdir(exist_ok=True, parents=True) - path.write_text('') + path.write_text('', encoding='utf-8') # open the file with configparser credentials = configparser.ConfigParser() @@ -332,7 +333,7 @@ def save(self, credentials: dict): config.read_dict(full_credentials) # write the new credentials file - with open(str(self.path), 'w') as f: + with open(str(self.path), 'w', encoding='utf-8') as f: config.write(f, space_around_delimiters=False) def delete(self): diff --git a/src/pybritive/helpers/encryption.py b/src/pybritive/helpers/encryption.py index e0c7ff3..8bec2ca 100644 --- a/src/pybritive/helpers/encryption.py +++ b/src/pybritive/helpers/encryption.py @@ -1,11 +1,11 @@ +import base64 +import os import uuid + from cryptography.fernet import Fernet, InvalidToken from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC -import os -import base64 -import click class InvalidPassphraseException(Exception): @@ -41,5 +41,5 @@ def decrypt(self, ciphertext: str): ciphertext, b64salt = ciphertext.split(':') key = self._key(b64salt) return Fernet(key).decrypt(base64.b64decode(ciphertext.encode())).decode('utf-8') - except InvalidToken: - raise InvalidPassphraseException() + except InvalidToken as e: + raise InvalidPassphraseException() from e From bd26de1e42352a12b5bc203fe438f4c508bf0707 Mon Sep 17 00:00:00 2001 From: theborch Date: Wed, 30 Aug 2023 18:50:56 -0500 Subject: [PATCH 11/27] lint options --- src/pybritive/options/blocktime.py | 1 - src/pybritive/options/britive_options.py | 1 - src/pybritive/options/checked_out.py | 1 - src/pybritive/options/configure_alias.py | 1 - src/pybritive/options/console.py | 1 - src/pybritive/options/federation_provider.py | 1 - src/pybritive/options/file.py | 1 - src/pybritive/options/force_renew.py | 1 - src/pybritive/options/justification.py | 1 - src/pybritive/options/maxpolltime.py | 1 - src/pybritive/options/mode.py | 1 - src/pybritive/options/output_format.py | 1 - src/pybritive/options/passphrase.py | 1 - src/pybritive/options/silent.py | 1 - src/pybritive/options/ssh_hostname.py | 1 - src/pybritive/options/ssh_key_source.py | 2 -- src/pybritive/options/ssh_port.py | 1 - src/pybritive/options/ssh_username.py | 1 - src/pybritive/options/verbose.py | 1 - src/pybritive/options/version.py | 3 ++- 20 files changed, 2 insertions(+), 21 deletions(-) diff --git a/src/pybritive/options/blocktime.py b/src/pybritive/options/blocktime.py index 4f61169..9751732 100644 --- a/src/pybritive/options/blocktime.py +++ b/src/pybritive/options/blocktime.py @@ -7,4 +7,3 @@ help='Seconds to wait before starting to poll for credentials. If not provided will default to 60 for profiles ' 'that require approval and 3 for profiles without approval.' ) - diff --git a/src/pybritive/options/britive_options.py b/src/pybritive/options/britive_options.py index f0dc12a..6d06186 100644 --- a/src/pybritive/options/britive_options.py +++ b/src/pybritive/options/britive_options.py @@ -80,4 +80,3 @@ def inner(f): f = option(f) return f return inner - diff --git a/src/pybritive/options/checked_out.py b/src/pybritive/options/checked_out.py index 6eb4dec..8818c80 100644 --- a/src/pybritive/options/checked_out.py +++ b/src/pybritive/options/checked_out.py @@ -8,4 +8,3 @@ show_default=True, help='Filter profile list to currently checked out profiles.' ) - diff --git a/src/pybritive/options/configure_alias.py b/src/pybritive/options/configure_alias.py index 843e1bb..da46cdb 100644 --- a/src/pybritive/options/configure_alias.py +++ b/src/pybritive/options/configure_alias.py @@ -6,4 +6,3 @@ default=None, help='Optional alias for the above tenant. This alias would be used with the `--tenant` flag.' ) - diff --git a/src/pybritive/options/console.py b/src/pybritive/options/console.py index 5c081a8..0e36a7e 100644 --- a/src/pybritive/options/console.py +++ b/src/pybritive/options/console.py @@ -8,4 +8,3 @@ show_default=True, help='Checkout the console access for the profile instead of programmatic access.' ) - diff --git a/src/pybritive/options/federation_provider.py b/src/pybritive/options/federation_provider.py index d522d6d..7bbcf91 100644 --- a/src/pybritive/options/federation_provider.py +++ b/src/pybritive/options/federation_provider.py @@ -9,4 +9,3 @@ default=None, show_default=True ) - diff --git a/src/pybritive/options/file.py b/src/pybritive/options/file.py index 36c8b61..4add04f 100644 --- a/src/pybritive/options/file.py +++ b/src/pybritive/options/file.py @@ -7,4 +7,3 @@ 'result in the file being saved to the current directory with the name provided when the secret ' 'file was initially uploaded. Providing `-` will print the contents of the secret file to stdout.' ) - diff --git a/src/pybritive/options/force_renew.py b/src/pybritive/options/force_renew.py index ec50314..57668cf 100644 --- a/src/pybritive/options/force_renew.py +++ b/src/pybritive/options/force_renew.py @@ -8,4 +8,3 @@ help='AWS Programmatic Only - If the credentials are to expire within the specified number of minutes, check in ' 'the profile first and check it out again to get a new set of credentials.' ) - diff --git a/src/pybritive/options/justification.py b/src/pybritive/options/justification.py index c9d0905..48613f4 100644 --- a/src/pybritive/options/justification.py +++ b/src/pybritive/options/justification.py @@ -7,4 +7,3 @@ show_default=True, help='Justification for the checkout approval process, if the profile checkout requires approval.', ) - diff --git a/src/pybritive/options/maxpolltime.py b/src/pybritive/options/maxpolltime.py index c9996fa..a973196 100644 --- a/src/pybritive/options/maxpolltime.py +++ b/src/pybritive/options/maxpolltime.py @@ -7,4 +7,3 @@ show_default=True, help='Maximum seconds to poll before exiting.' ) - diff --git a/src/pybritive/options/mode.py b/src/pybritive/options/mode.py index a5911cd..2c53bf2 100644 --- a/src/pybritive/options/mode.py +++ b/src/pybritive/options/mode.py @@ -16,4 +16,3 @@ '`gcloudauth` will save the generated key file/credentials to the pybritive config directory and generate a ' 'gcloud auth command which can be directly evaluated. Will default to `json` if not provided.' ) - diff --git a/src/pybritive/options/output_format.py b/src/pybritive/options/output_format.py index fd483ef..bbc94d1 100644 --- a/src/pybritive/options/output_format.py +++ b/src/pybritive/options/output_format.py @@ -16,4 +16,3 @@ show_default=True ) - diff --git a/src/pybritive/options/passphrase.py b/src/pybritive/options/passphrase.py index cc0d238..79760fd 100644 --- a/src/pybritive/options/passphrase.py +++ b/src/pybritive/options/passphrase.py @@ -8,4 +8,3 @@ show_envvar=True, show_default=True ) - diff --git a/src/pybritive/options/silent.py b/src/pybritive/options/silent.py index 5f7a98a..d30680c 100644 --- a/src/pybritive/options/silent.py +++ b/src/pybritive/options/silent.py @@ -8,4 +8,3 @@ show_default=True, help='Enable silent mode.' ) - diff --git a/src/pybritive/options/ssh_hostname.py b/src/pybritive/options/ssh_hostname.py index 38be4e0..a94cbc9 100644 --- a/src/pybritive/options/ssh_hostname.py +++ b/src/pybritive/options/ssh_hostname.py @@ -6,4 +6,3 @@ required=True, help='The SSH hostname from the SSH config file to which the ephemeral SSH public key will be pushed.' ) - diff --git a/src/pybritive/options/ssh_key_source.py b/src/pybritive/options/ssh_key_source.py index 71a3cc8..8d6a089 100644 --- a/src/pybritive/options/ssh_key_source.py +++ b/src/pybritive/options/ssh_key_source.py @@ -11,5 +11,3 @@ show_default=True, help='The source of the SSH key used to authenticate to the remove server.' ) - - diff --git a/src/pybritive/options/ssh_port.py b/src/pybritive/options/ssh_port.py index 4afa20f..f29b973 100644 --- a/src/pybritive/options/ssh_port.py +++ b/src/pybritive/options/ssh_port.py @@ -6,4 +6,3 @@ required=True, help='The SSH port number from the SSH config file for the given host.' ) - diff --git a/src/pybritive/options/ssh_username.py b/src/pybritive/options/ssh_username.py index 58efa35..3051d72 100644 --- a/src/pybritive/options/ssh_username.py +++ b/src/pybritive/options/ssh_username.py @@ -6,4 +6,3 @@ required=True, help='The EC2 OS username for whom the ephemeral SSH public key will be pushed.' ) - diff --git a/src/pybritive/options/verbose.py b/src/pybritive/options/verbose.py index b8c7259..0f51bce 100644 --- a/src/pybritive/options/verbose.py +++ b/src/pybritive/options/verbose.py @@ -8,4 +8,3 @@ show_default=True, help='Enable verbose checkout mode.' ) - diff --git a/src/pybritive/options/version.py b/src/pybritive/options/version.py index a49f84a..f0d0a09 100644 --- a/src/pybritive/options/version.py +++ b/src/pybritive/options/version.py @@ -1,5 +1,6 @@ -import click import platform + +import click import pkg_resources From 27bacc5cf22fb028be8d17e78785640749cd887f Mon Sep 17 00:00:00 2001 From: theborch Date: Wed, 30 Aug 2023 18:51:04 -0500 Subject: [PATCH 12/27] lint choices --- src/pybritive/choices/backend.py | 1 - src/pybritive/choices/browser.py | 1 - src/pybritive/choices/mode.py | 1 - src/pybritive/choices/output_format.py | 1 - src/pybritive/choices/ssh_key_source.py | 1 - src/pybritive/choices/ssh_push_public_key.py | 1 - 6 files changed, 6 deletions(-) diff --git a/src/pybritive/choices/backend.py b/src/pybritive/choices/backend.py index 8dba7a5..841d70c 100644 --- a/src/pybritive/choices/backend.py +++ b/src/pybritive/choices/backend.py @@ -8,4 +8,3 @@ ], case_sensitive=False ) - diff --git a/src/pybritive/choices/browser.py b/src/pybritive/choices/browser.py index 2836c6e..78e36a0 100644 --- a/src/pybritive/choices/browser.py +++ b/src/pybritive/choices/browser.py @@ -14,4 +14,3 @@ ], case_sensitive=False ) - diff --git a/src/pybritive/choices/mode.py b/src/pybritive/choices/mode.py index 06b8d18..30befda 100644 --- a/src/pybritive/choices/mode.py +++ b/src/pybritive/choices/mode.py @@ -27,4 +27,3 @@ ], case_sensitive=False ) - diff --git a/src/pybritive/choices/output_format.py b/src/pybritive/choices/output_format.py index f23d569..fbf3ea7 100644 --- a/src/pybritive/choices/output_format.py +++ b/src/pybritive/choices/output_format.py @@ -41,4 +41,3 @@ ], case_sensitive=False ) - diff --git a/src/pybritive/choices/ssh_key_source.py b/src/pybritive/choices/ssh_key_source.py index 3eb0839..8b3644f 100644 --- a/src/pybritive/choices/ssh_key_source.py +++ b/src/pybritive/choices/ssh_key_source.py @@ -7,4 +7,3 @@ ], case_sensitive=False ) - diff --git a/src/pybritive/choices/ssh_push_public_key.py b/src/pybritive/choices/ssh_push_public_key.py index a520b0c..7bca514 100644 --- a/src/pybritive/choices/ssh_push_public_key.py +++ b/src/pybritive/choices/ssh_push_public_key.py @@ -11,4 +11,3 @@ ], case_sensitive=False ) - From 43a64051938da7fa31f0366183451dfe7e8c2b15 Mon Sep 17 00:00:00 2001 From: theborch Date: Wed, 30 Aug 2023 18:51:13 -0500 Subject: [PATCH 13/27] lint commands --- src/pybritive/commands/__init__.py | 1 - src/pybritive/commands/cache.py | 3 --- src/pybritive/commands/checkin.py | 2 -- src/pybritive/commands/clear.py | 1 - src/pybritive/commands/configure.py | 5 ----- src/pybritive/commands/request.py | 2 +- src/pybritive/commands/user.py | 1 - 7 files changed, 1 insertion(+), 14 deletions(-) diff --git a/src/pybritive/commands/__init__.py b/src/pybritive/commands/__init__.py index 8b13789..e69de29 100644 --- a/src/pybritive/commands/__init__.py +++ b/src/pybritive/commands/__init__.py @@ -1 +0,0 @@ - diff --git a/src/pybritive/commands/cache.py b/src/pybritive/commands/cache.py index 94cd7c9..31fda3d 100644 --- a/src/pybritive/commands/cache.py +++ b/src/pybritive/commands/cache.py @@ -22,6 +22,3 @@ def profiles(ctx, tenant, token, silent, passphrase, federation_provider): def clear(ctx): """Clears the local cache.""" ctx.obj.britive.cache_clear() - - - diff --git a/src/pybritive/commands/checkin.py b/src/pybritive/commands/checkin.py index 95c6dbf..51cb855 100644 --- a/src/pybritive/commands/checkin.py +++ b/src/pybritive/commands/checkin.py @@ -17,5 +17,3 @@ def checkin(ctx, tenant, token, silent, passphrase, federation_provider, profile ctx.obj.britive.checkin( profile=profile ) - - diff --git a/src/pybritive/commands/clear.py b/src/pybritive/commands/clear.py index 62674d4..a043a1a 100644 --- a/src/pybritive/commands/clear.py +++ b/src/pybritive/commands/clear.py @@ -28,4 +28,3 @@ def gcloud_auth_key_files(ctx): def cached_aws_credentials(ctx, profile): """Clears cached AWS credentials used as part of the AWS CLI credential process.""" ctx.obj.britive.clear_cached_aws_credentials(profile=profile) - diff --git a/src/pybritive/commands/configure.py b/src/pybritive/commands/configure.py index b176574..a544ca3 100644 --- a/src/pybritive/commands/configure.py +++ b/src/pybritive/commands/configure.py @@ -21,7 +21,6 @@ def tenant(ctx, configure_tenant, configure_alias, output_format, configure_prom tenant_name = configure_tenant no_prompt = configure_prompt alias = configure_alias - output_format = output_format if not no_prompt: if not tenant_name: @@ -67,7 +66,6 @@ def global_command(ctx, configure_tenant, output_format, configure_prompt, confi """ default_tenant_name = configure_tenant no_prompt = configure_prompt - output_format = output_format backend = configure_backend if not no_prompt: @@ -130,6 +128,3 @@ def update(ctx, silent, section, field, value): # silent is handled by @build_b field = field.lower().strip() value = value.lower().strip() ctx.obj.britive.configure_update(section=section, field=field, value=value) - - - diff --git a/src/pybritive/commands/request.py b/src/pybritive/commands/request.py index b18fd8e..70f2464 100644 --- a/src/pybritive/commands/request.py +++ b/src/pybritive/commands/request.py @@ -77,4 +77,4 @@ def reject(ctx, tenant, token, silent, passphrase, federation_provider, request_ ctx.obj.britive.request_disposition( request_id=request_id, decision='reject' - ) \ No newline at end of file + ) diff --git a/src/pybritive/commands/user.py b/src/pybritive/commands/user.py index 36445fe..5ac05a7 100644 --- a/src/pybritive/commands/user.py +++ b/src/pybritive/commands/user.py @@ -9,4 +9,3 @@ def user(ctx, tenant, token, silent, passphrase, federation_provider): """Print details about the authenticated identity.""" ctx.obj.britive.user() - From ccd35968e13d4f98de1d4d53e87b5a65f4f51f27 Mon Sep 17 00:00:00 2001 From: theborch Date: Wed, 30 Aug 2023 18:51:21 -0500 Subject: [PATCH 14/27] lint completers --- src/pybritive/completers/__init__.py | 1 - src/pybritive/completers/api.py | 1 - src/pybritive/completers/api_command.py | 11 +++++------ src/pybritive/completers/bash_gte_42.py | 5 +++-- src/pybritive/completers/powershell_completion.py | 13 +++++++------ 5 files changed, 15 insertions(+), 16 deletions(-) diff --git a/src/pybritive/completers/__init__.py b/src/pybritive/completers/__init__.py index 3641161..7ac37d9 100644 --- a/src/pybritive/completers/__init__.py +++ b/src/pybritive/completers/__init__.py @@ -9,4 +9,3 @@ if click_major_version >= 8: from . import powershell_completion from . import bash_gte_42 - diff --git a/src/pybritive/completers/api.py b/src/pybritive/completers/api.py index dd308ab..928716e 100644 --- a/src/pybritive/completers/api.py +++ b/src/pybritive/completers/api.py @@ -1,5 +1,4 @@ from britive.britive import Britive -import json def api_completer(ctx, param, incomplete): diff --git a/src/pybritive/completers/api_command.py b/src/pybritive/completers/api_command.py index 46a17d9..6520311 100644 --- a/src/pybritive/completers/api_command.py +++ b/src/pybritive/completers/api_command.py @@ -1,5 +1,6 @@ -import typing as t import inspect +import typing as t + from britive.britive import Britive import pkg_resources @@ -41,7 +42,7 @@ def get_dynamic_method_parameters(method): doc_lines = inspect.getdoc(b) doc_lines = doc_lines.replace(':returns:', 'RETURNSPLIT') doc_lines = doc_lines.replace(':return:', 'RETURNSPLIT') - doc_lines = doc_lines.split('RETURNSPLIT')[0].split(':param ')[1:] + doc_lines = doc_lines.split('RETURNSPLIT', maxsplit=1)[0].split(':param ')[1:] for line in doc_lines: helper = line.split(':') @@ -71,7 +72,7 @@ def get_dynamic_method_parameters(method): param_list.append(param) return param_list - except Exception as e: + except Exception: return [] @@ -79,7 +80,7 @@ def command_api_patch_shell_complete(cls): # click < 8.0.0 does shell completion different... # not all the classes/decorators are available, so we cannot # create custom shell completions like we can with click > 8.0.0 - major, minor, patch = pkg_resources.get_distribution('click').version.split('.')[0:3] + major, minor = pkg_resources.get_distribution('click').version.split('.')[:2] # we cannot patch the shell_complete method because it does not exist (click 7.x doesn't have it) # future proofing this as well in case click 9.x changes things up a lot @@ -100,8 +101,6 @@ def command_api_patch_shell_complete(cls): __class__ = cls # provide closure cell for super() def shell_complete(self, ctx: Context, incomplete: str) -> t.List["CompletionItem"]: - from click.shell_completion import CompletionItem - results: t.List["CompletionItem"] = [] if incomplete and not incomplete[0].isalnum(): diff --git a/src/pybritive/completers/bash_gte_42.py b/src/pybritive/completers/bash_gte_42.py index 4a642bd..a1bc661 100644 --- a/src/pybritive/completers/bash_gte_42.py +++ b/src/pybritive/completers/bash_gte_42.py @@ -1,6 +1,7 @@ +from gettext import gettext as _ import re + from click.shell_completion import add_completion_class, BashComplete -from gettext import gettext as _ # inspired by https://github.com/pallets-eco/click-bash4.2-completion @@ -49,7 +50,7 @@ def _check_version(self) -> None: import subprocess output = subprocess.run( - ["bash", "-c", "echo ${BASH_VERSION}"], stdout=subprocess.PIPE + ["bash", "-c", "echo ${BASH_VERSION}"], stdout=subprocess.PIPE, check=False ) match = re.search(r"^(\d+)\.(\d+)\.\d+", output.stdout.decode()) diff --git a/src/pybritive/completers/powershell_completion.py b/src/pybritive/completers/powershell_completion.py index eb6b9d9..88312d4 100644 --- a/src/pybritive/completers/powershell_completion.py +++ b/src/pybritive/completers/powershell_completion.py @@ -1,9 +1,10 @@ -from click.shell_completion import add_completion_class -from click.shell_completion import ShellComplete, CompletionItem import os -from click.parser import split_arg_string import typing as t +from click.parser import split_arg_string +from click.shell_completion import add_completion_class +from click.shell_completion import ShellComplete, CompletionItem + # inspired by https://raw.githubusercontent.com/tibortakacs/powershell-argcomplete/master/mat.complete.ps1 _powershell_source = """\ @@ -30,8 +31,8 @@ Invoke-Expression %(prog_name)s -ErrorAction SilentlyContinue | Tee-Object -Var completionResult | Out-Null # cleanup environment variables - Remove-Item Env:\COMP_LINE | Out-Null - Remove-Item Env:\%(complete_var)s | Out-Null + Remove-Item Env:COMP_LINE | Out-Null + Remove-Item Env:%(complete_var)s | Out-Null # get list of completion items $items = $completionResult -split '\\r?\\n' @@ -79,4 +80,4 @@ def source_vars(self) -> t.Dict[str, t.Any]: "complete_func": self.func_name[1:], # remove leading _ "complete_var": self.complete_var, "prog_name": self.prog_name - } \ No newline at end of file + } From f1c982077e62912c89cd16a78dc34031bfcb1ab2 Mon Sep 17 00:00:00 2001 From: theborch Date: Wed, 30 Aug 2023 18:52:07 -0500 Subject: [PATCH 15/27] lint pybritive --- src/pybritive/__init__.py | 1 - src/pybritive/britive_cli.py | 166 ++++++++++++++++----------------- src/pybritive/cli_interface.py | 7 +- 3 files changed, 86 insertions(+), 88 deletions(-) diff --git a/src/pybritive/__init__.py b/src/pybritive/__init__.py index 8b13789..e69de29 100644 --- a/src/pybritive/__init__.py +++ b/src/pybritive/__init__.py @@ -1 +0,0 @@ - diff --git a/src/pybritive/britive_cli.py b/src/pybritive/britive_cli.py index a2003f0..0b48b21 100644 --- a/src/pybritive/britive_cli.py +++ b/src/pybritive/britive_cli.py @@ -1,25 +1,26 @@ +import csv +from datetime import datetime +from datetime import timezone +from datetime import timedelta import io +import json +import os import pathlib +from pathlib import Path +import sys import uuid +import yaml + +import click +import jmespath from britive.britive import Britive +from britive import exceptions +from tabulate import tabulate from .helpers.config import ConfigManager from .helpers.credentials import FileCredentialManager, EncryptedFileCredentialManager from .helpers.split import profile_split -import json -import click -import csv -from tabulate import tabulate -import yaml from .helpers import cloud_credential_printer as printer from .helpers.cache import Cache -from britive import exceptions -from pathlib import Path -from datetime import datetime -from datetime import timezone -from datetime import timedelta -import os -import sys -import jmespath default_table_format = 'fancy_grid' @@ -85,8 +86,8 @@ def login(self, explicit: bool = False): token=self.token, query_features=False ) - except exceptions.UnauthorizedRequest: - raise click.ClickException('Invalid API token provided.') + except exceptions.UnauthorizedRequest as e: + raise click.ClickException('Invalid API token provided.') from e else: while True: # will break after we successfully get logged in try: @@ -97,7 +98,7 @@ def login(self, explicit: bool = False): query_features=False ) break - except exceptions.UnauthorizedRequest as e: + except exceptions.UnauthorizedRequest: self._cleanup_credentials() # if user called `pybritive login` and we should refresh the profile cache...do so @@ -177,7 +178,7 @@ def print(self, data: object, ignore_silent: bool = False): else: click.echo(row) elif self.output_format == 'csv': - fields = list(data[0].keys()) + fields = list(data[0]) output = io.StringIO() writer = csv.DictWriter(output, fieldnames=fields, delimiter=',') writer.writeheader() @@ -351,7 +352,7 @@ def __get_cloud_credential_printer(self, app_type, console, mode, profile, silen cli=self, aws_credentials_file=aws_credentials_file ) - elif app_type in ['Azure']: + if app_type in ['Azure']: return printer.AzureCloudCredentialPrinter( console=console, mode=mode, @@ -360,7 +361,7 @@ def __get_cloud_credential_printer(self, app_type, console, mode, profile, silen silent=silent, cli=self ) - elif app_type in ['GCP']: + if app_type in ['GCP']: return printer.GcpCloudCredentialPrinter( console=console, mode=mode, @@ -370,15 +371,14 @@ def __get_cloud_credential_printer(self, app_type, console, mode, profile, silen cli=self, gcloud_key_file=gcloud_key_file ) - else: - return printer.GenericCloudCredentialPrinter( - console=console, - mode=mode, - profile=profile, - credentials=credentials, - silent=silent, - cli=self - ) + return printer.GenericCloudCredentialPrinter( + console=console, + mode=mode, + profile=profile, + credentials=credentials, + silent=silent, + cli=self + ) def checkin(self, profile): self.login() @@ -407,7 +407,7 @@ def checkin(self, profile): break break if not transaction_id: - raise ValueError(f'no checked out profile found for the given profile') + raise ValueError('no checked out profile found for the given profile') self.b.my_access.checkin( transaction_id=transaction_id @@ -436,8 +436,8 @@ def _checkout(self, profile_name, env_name, app_name, programmatic, blocktime, m justification=justification, progress_func=self.checkout_callback_printer # callback will handle silent, isatty, etc. ) - except exceptions.ApprovalRequiredButNoJustificationProvided: - raise click.ClickException('approval required and no justification provided.') + except exceptions.ApprovalRequiredButNoJustificationProvided as e: + raise click.ClickException('approval required and no justification provided.') from e except ValueError as e: raise click.BadParameter(str(e)) except Exception as e: @@ -446,8 +446,7 @@ def _checkout(self, profile_name, env_name, app_name, programmatic, blocktime, m # this is a cli only feature - not available in the sdk self.print('no programmatic access available - checking out console access instead') return self._checkout(profile_name, env_name, app_name, False, blocktime, maxpolltime, justification) - else: - raise e + raise e @staticmethod def _should_check_force_renew(app, force_renew, console): @@ -506,7 +505,7 @@ def checkout(self, alias, blocktime, console, justification, mode, maxpolltime, 'profile_name': parts['profile'], 'env_name': parts['env'], 'app_name': parts['app'], - 'programmatic': False if console else True, + 'programmatic': not console, 'blocktime': blocktime, 'maxpolltime': maxpolltime, 'justification': justification @@ -552,7 +551,7 @@ def checkout(self, alias, blocktime, console, justification, mode, maxpolltime, def import_existing_npm_config(self): profile_aliases = self.config.import_global_npm_config() - if len(profile_aliases.keys()) == 0: + if len(profile_aliases) == 0: return self.print('') self.print('Profile aliases exist...will retrieve profile details from the tenant.') @@ -565,7 +564,7 @@ def import_existing_npm_config(self): for alias, ids in profile_aliases.items(): if '/' in alias: # no need to import the aliases that aren't really aliases continue - app, env, profile, cloud = ids.split('/') + app, env, profile = ids.split('/')[:3] for p in self.available_profiles: if p['app_id'] == app and p['env_id'] == env and p['profile_id'] == profile: profile_str = f"{p['app_name']}/{p['env_name']}/{p['profile_name']}" @@ -597,13 +596,13 @@ def viewsecret(self, path, blocktime, justification, maxpolltime): wait_time=blocktime, max_wait_time=maxpolltime ) - except exceptions.AccessDenied: - raise click.ClickException('user does not have access to the secret.') - except exceptions.ApprovalRequiredButNoJustificationProvided: - raise click.ClickException('approval required and no justification provided.') + except exceptions.AccessDenied as e: + raise click.ClickException('user does not have access to the secret.') from e + except exceptions.ApprovalRequiredButNoJustificationProvided as e: + raise click.ClickException('approval required and no justification provided.') from e # handle the generic note template type for a better UX - if len(value.keys()) == 1 and 'Note' in value.keys(): + if len(value) == 1 and 'Note' in value: value = value['Note'] # if the value can be converted from JSON to python dict, do it @@ -628,10 +627,10 @@ def downloadsecret(self, path, blocktime, justification, maxpolltime, file): wait_time=blocktime, max_wait_time=maxpolltime ) - except exceptions.AccessDenied: - raise click.ClickException('user does not have access to the secret.') - except exceptions.ApprovalRequiredButNoJustificationProvided: - raise click.ClickException('approval required and no justification provided.') + except exceptions.AccessDenied as e: + raise click.ClickException('user does not have access to the secret.') from e + except exceptions.ApprovalRequiredButNoJustificationProvided as e: + raise click.ClickException('approval required and no justification provided.') from e filename_from_secret = response['filename'] content = response['content_bytes'] @@ -643,7 +642,7 @@ def downloadsecret(self, path, blocktime, justification, maxpolltime, file): raise click.ClickException( 'Secret file contents cannot be decoded to utf-8. ' 'Save the contents of the file to disk instead.' - ) + ) from e return filename = file or filename_from_secret @@ -716,37 +715,40 @@ def request_withdraw(self, profile): def clear_gcloud_auth_key_files(self): self.config.clear_gcloud_auth_key_files() - def api(self, method, parameters={}, query=None): + def api(self, method, parameters: dict, query=None): self.login() # clean up parameters - need to load json as dict if json string is provided and handle file inputs computed_parameters = {} open_file_keys = [] - for key, value in parameters.items(): - computed_key = key.replace('-', '_') - computed_value = value + try: + for key, value in parameters.items(): + computed_key = key.replace('-', '_') + computed_value = value - if value.lower() == 'none': - computed_value = None + if value.lower() == 'none': + computed_value = None - if value.startswith('file://'): - filepath = value.replace('file://', '') - path = pathlib.Path(filepath) - with open(str(path), 'r') as f: - computed_value = f.read().strip() + if value.startswith('file://'): + filepath = value.replace('file://', '') + path = pathlib.Path(filepath) + with open(str(path), 'r', encoding='utf-8') as f: + computed_value = f.read().strip() - if value.startswith('fileb://'): - filepath = value.replace('fileb://', '') - path = pathlib.Path(filepath) - computed_value = open(str(path), 'rb') - open_file_keys.append(computed_key) + if value.startswith('fileb://'): + filepath = value.replace('fileb://', '') + path = pathlib.Path(filepath) + computed_value = open(str(path), 'rb') + open_file_keys.append(computed_key) - try: - computed_parameters[computed_key] = json.loads(computed_value) - except json.JSONDecodeError: - computed_parameters[computed_key] = computed_value - except Exception: # not sure what else we would do so just default to the value provided - computed_parameters[computed_key] = computed_value + try: + computed_parameters[computed_key] = json.loads(computed_value) + except json.JSONDecodeError: + computed_parameters[computed_key] = computed_value + except Exception: # not sure what else we would do so just default to the value provided + computed_parameters[computed_key] = computed_value + except AttributeError as e: + raise click.ClickException(f'invalid parameters {parameters} provided.') from e # determine the sdk method we need to execute, starting at the base Britive class func = self.b @@ -754,7 +756,7 @@ def api(self, method, parameters={}, query=None): for m in method.split('.'): func = getattr(func, m) except Exception as e: - raise click.ClickException(f'invalid method {method} provided.') + raise click.ClickException(f'invalid method {method} provided.') from e # execute the method with the computed parameters response = func(**computed_parameters) @@ -795,7 +797,7 @@ def _convert_names_to_ids(self, profile_name: str, environment_name: str, applic found_profile_id = profile['profile_id'] - if found_profile_id not in found_profiles.keys(): + if found_profile_id not in found_profiles: found_profiles[found_profile_id] = [] # load up multiple options @@ -809,13 +811,13 @@ def _convert_names_to_ids(self, profile_name: str, environment_name: str, applic found_profiles[found_profile_id].append(profile['env_id']) # let's first check to ensure we have only 1 profile - if len(found_profiles.keys()) == 0: + if len(found_profiles) == 0: raise click.ClickException('no profile found with the provided application, environment, and profile names') - if len(found_profiles.keys()) > 1: + if len(found_profiles) > 1: raise click.ClickException('multiple matching profiles found - cannot determine which profile to use') # and now we can check to ensure we have only 1 environment - found_profile_id = list(found_profiles.keys())[0] + found_profile_id = list(found_profiles)[0] possible_environments = found_profiles[found_profile_id] if len(possible_environments) == 0: raise click.ClickException('no profile found with the provided application, environment, and profile names') @@ -934,13 +936,13 @@ def _ssh_generate_key(self, username, hostname, key_source): # as the public key is just pushed to the ec2 instance # as a string in the ec2 instance connect api call (no file # reference) - with open(str(pem_file), 'w') as f: + with open(str(pem_file), 'w', encoding='utf-8') as f: f.write(key_pair['private'].decode()) os.chmod(pem_file, 0o400) # and if we are using ssh-agent we need to add the private key via ssh-add if key_source == 'ssh-agent': - subprocess.run(['ssh-add', str(pem_file), '-t', '60', '-q']) + subprocess.run(['ssh-add', str(pem_file), '-t', '60', '-q'], check=False) return { 'private_key_filename': pem_file, @@ -951,9 +953,9 @@ def _ssh_generate_key(self, username, hostname, key_source): def _ssh_aws_push_key(aws_profile, aws_region, instance_id, username, key_pair): try: import boto3 - except ImportError: + except ImportError as e: message = 'boto3 package is required. Please ensure the package is installed.' - raise click.ClickException(message) + raise click.ClickException(message) from e # we know we will be pushing the key to the instance so establish the # boto3 clients which are required to perform those actions @@ -1010,9 +1012,9 @@ def aws_console(profile, duration, browser): # this is the one that may not be available so be careful try: import boto3 - except ImportError: + except ImportError as e: message = 'boto3 package is required. Please ensure the package is installed.' - raise click.ClickException(message) + raise click.ClickException(message) from e creds = boto3.Session(profile_name=profile).get_credentials() session_id = creds.access_key @@ -1151,7 +1153,7 @@ def ssh_gcp_identity_aware_proxy(self, username, hostname, push_public_key, port ssh_dir = Path(self.config.path).parent.absolute() / 'ssh' ssh_dir.mkdir(exist_ok=True, parents=True) # create the directory if it doesn't exist already key_file = ssh_dir / uuid.uuid4().hex - with open(str(key_file), 'w') as f: + with open(str(key_file), 'w', encoding='utf-8') as f: f.write('\n'.join(future_keys)) commands = [ @@ -1170,7 +1172,7 @@ def ssh_gcp_identity_aware_proxy(self, username, hostname, push_public_key, port '--no-user-output-enabled', '--quiet' ] - subprocess.run(commands) + subprocess.run(commands, check=False) key_file.unlink(missing_ok=True) commands = [ @@ -1212,5 +1214,3 @@ def ssh_gcp_openssh_config(self, push_public_key, key_source): self.print('') self.print('') self.print('\n'.join(lines)) - - diff --git a/src/pybritive/cli_interface.py b/src/pybritive/cli_interface.py index 956d9ba..9d97aaa 100644 --- a/src/pybritive/cli_interface.py +++ b/src/pybritive/cli_interface.py @@ -1,3 +1,5 @@ +import os +import sys import click from .options.britive_options import britive_options from .commands.user import user as command_user @@ -14,8 +16,6 @@ from .commands.api import api as command_api from .commands.ssh import ssh as group_ssh from .commands.aws import aws as group_aws -import sys -import os def safe_cli(): @@ -33,8 +33,7 @@ def safe_cli(): return if debug: raise e - else: - raise e from None + raise e from None # this is the "main" app - it really does nothing but print the overview/help section From b7351557b51895564982a9329150351c23d0e74c Mon Sep 17 00:00:00 2001 From: theborch Date: Thu, 31 Aug 2023 18:41:19 -0500 Subject: [PATCH 16/27] missed one --- src/pybritive/britive_cli.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pybritive/britive_cli.py b/src/pybritive/britive_cli.py index 0b48b21..262578f 100644 --- a/src/pybritive/britive_cli.py +++ b/src/pybritive/britive_cli.py @@ -477,7 +477,7 @@ def checkout(self, alias, blocktime, console, justification, mode, maxpolltime, # these 2 modes implicitly say that console access should be checked out without having to provide # the --console flag if console := (mode and (mode == 'console' or mode.startswith('browser'))): - self.browser = mode.replace("browser-","") + self.browser = mode.replace('browser-', '') if mode.startswith('browser') else os.getenv('PYBRITIVE_BROWSER') self._validate_justification(justification) From 42e81d95ceb4d1eaa7d99f089cd36f7848f93f5b Mon Sep 17 00:00:00 2001 From: Thomas Rawley Date: Fri, 1 Sep 2023 10:12:19 -0400 Subject: [PATCH 17/27] double dash --- setup.cfg | 2 +- src/pybritive/helpers/credentials.py | 10 ++++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/setup.cfg b/setup.cfg index e2faa16..f1187c0 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = pybritive -version = 1.5.0 +version = 1.5.0rc2 author = Britive Inc. author_email = support@britive.com description = A pure Python CLI for Britive diff --git a/src/pybritive/helpers/credentials.py b/src/pybritive/helpers/credentials.py index e30ee07..7a91715 100644 --- a/src/pybritive/helpers/credentials.py +++ b/src/pybritive/helpers/credentials.py @@ -59,8 +59,14 @@ def __init__(self, tenant_name: str, tenant_alias: str, cli: any, federation_pro # not sure if we really need 32 random bytes or if any random string would work # but the current britive-cli in node.js does it this way so it will be done the same # way in python - self.verifier = b64_encode_url_safe(bytes([random.getrandbits(8) for _ in range(0, 32)])) - self.auth_token = b64_encode_url_safe(bytes(hashlib.sha512(self.verifier.encode('utf-8')).digest())) + while True: # will break eventually when we get values that do not include -- + self.verifier = b64_encode_url_safe(bytes([random.getrandbits(8) for _ in range(0, 32)])) + self.auth_token = b64_encode_url_safe(bytes(hashlib.sha512(self.verifier.encode('utf-8')).digest())) + + # WAF doesn't like to see `--` as it thinks it is a sql injection attempt + if '--' not in self.verifier and '--' not in self.auth_token: + break + self.credentials = self.load() or {} def _setup_requests_session(self): From 1bc3378a93f9623b229cc7bdd8ae37748d995676 Mon Sep 17 00:00:00 2001 From: Thomas Rawley Date: Fri, 1 Sep 2023 10:16:45 -0400 Subject: [PATCH 18/27] changelog --- CHANGELOG.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 81e6d72..9f9f28c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,23 @@ * As of v1.4.0 release candidates will be published in an effort to get new features out faster while still allowing time for full QA testing before moving the release candidate to a full release. +## v1.5.0rc1 [2023-09-XX] +#### What's New +* None + +#### Enhancements +* None + +#### Bug Fixes +* Fix issue with interactive login when randomly generated tokens include `--` which the WAF sometimes sees as a SQL injection attack + +#### Dependencies +* None + +#### Other +* None + + ## v1.4.0 [2023-07-25] #### What's New * `pybritive ssh gcp identity-aware-proxy` command - supports OS Login and SSH Instance Metadata From 85bc933e5699d04ae73443883d8811ab61b48651 Mon Sep 17 00:00:00 2001 From: Thomas Rawley Date: Fri, 1 Sep 2023 14:05:25 -0400 Subject: [PATCH 19/27] enrich api shell completion --- CHANGELOG.md | 2 +- src/pybritive/completers/api.py | 46 +++++++++++++++++++++---- src/pybritive/completers/api_command.py | 18 ++++++---- 3 files changed, 52 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9f9f28c..f91fc3d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ * None #### Enhancements -* None +* Enrich shell completion results for the `api` command #### Bug Fixes * Fix issue with interactive login when randomly generated tokens include `--` which the WAF sometimes sees as a SQL injection attack diff --git a/src/pybritive/completers/api.py b/src/pybritive/completers/api.py index 928716e..19af1f6 100644 --- a/src/pybritive/completers/api.py +++ b/src/pybritive/completers/api.py @@ -1,4 +1,6 @@ from britive.britive import Britive +from click.shell_completion import CompletionItem +import inspect def api_completer(ctx, param, incomplete): @@ -15,17 +17,49 @@ def api_completer(ctx, param, incomplete): for part in parts: b = getattr(b, part) + existing = '.'.join(parts) + options = [] # vars happen at all levels - options += [var for var, value in vars(b).items() if str(value).startswith(' 0: for i in range(1, len(defaults) + 1): name = names[-1 * i] default = defaults[-1 * i] - params[name]['default'] = default + params[name]['default'] = '' if default == '' else default try: # we don't REALLY need the doc string so if there are errors just eat them and move on doc_lines = inspect.getdoc(b) @@ -71,6 +70,11 @@ def get_dynamic_method_parameters(method): param_list.append(param) + param_list.append({ + 'flag': '---------------------', + 'help': 'separator between sdk parameters and cli parameters' + }) + return param_list except Exception: return [] From 39101bc3efde9a459ddbc72f83e44e3ba6b026d9 Mon Sep 17 00:00:00 2001 From: Thomas Rawley Date: Fri, 1 Sep 2023 14:09:14 -0400 Subject: [PATCH 20/27] inline if --- src/pybritive/completers/api.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/pybritive/completers/api.py b/src/pybritive/completers/api.py index 19af1f6..6215ae6 100644 --- a/src/pybritive/completers/api.py +++ b/src/pybritive/completers/api.py @@ -27,10 +27,7 @@ def api_completer(ctx, param, incomplete): if not str(value).startswith(' Date: Wed, 6 Sep 2023 10:52:24 -0400 Subject: [PATCH 21/27] ssh-add-fix --- CHANGELOG.md | 3 ++- src/pybritive/britive_cli.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f91fc3d..041223c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,8 @@ * Enrich shell completion results for the `api` command #### Bug Fixes -* Fix issue with interactive login when randomly generated tokens include `--` which the WAF sometimes sees as a SQL injection attack +* Fixes an issue with interactive login when randomly generated tokens include `--` which the WAF sometimes sees as a SQL injection attack +* Fix an issue with `ssh-add` and temporary keys filling up the `ssh-agent` due to the order of command flags #### Dependencies * None diff --git a/src/pybritive/britive_cli.py b/src/pybritive/britive_cli.py index 262578f..1a3f610 100644 --- a/src/pybritive/britive_cli.py +++ b/src/pybritive/britive_cli.py @@ -942,7 +942,7 @@ def _ssh_generate_key(self, username, hostname, key_source): # and if we are using ssh-agent we need to add the private key via ssh-add if key_source == 'ssh-agent': - subprocess.run(['ssh-add', str(pem_file), '-t', '60', '-q'], check=False) + subprocess.run(['ssh-add', '-t', '60', '-q', str(pem_file)], check=False) return { 'private_key_filename': pem_file, From 45d503cb193bb2cfe496202e3c490817918ace2e Mon Sep 17 00:00:00 2001 From: Thomas Rawley Date: Fri, 8 Sep 2023 08:11:08 -0400 Subject: [PATCH 22/27] checkin correct version of the profile --- CHANGELOG.md | 3 ++- src/pybritive/britive_cli.py | 9 ++++++--- src/pybritive/commands/checkin.py | 7 ++++--- src/pybritive/options/console.py | 2 +- 4 files changed, 13 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 041223c..11b74f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,8 @@ #### Bug Fixes * Fixes an issue with interactive login when randomly generated tokens include `--` which the WAF sometimes sees as a SQL injection attack -* Fix an issue with `ssh-add` and temporary keys filling up the `ssh-agent` due to the order of command flags +* Fixes an issue with `ssh-add` and temporary keys filling up the `ssh-agent` due to the order of command flags +* Fixes and issue with `checkin` checking in the wrong profile type (programmatic vs console) #### Dependencies * None diff --git a/src/pybritive/britive_cli.py b/src/pybritive/britive_cli.py index 1a3f610..034b10d 100644 --- a/src/pybritive/britive_cli.py +++ b/src/pybritive/britive_cli.py @@ -380,7 +380,7 @@ def __get_cloud_credential_printer(self, app_type, console, mode, profile, silen cli=self ) - def checkin(self, profile): + def checkin(self, profile, console): self.login() self._set_available_profiles() parts = self._split_profile_into_parts(profile) @@ -391,18 +391,21 @@ def checkin(self, profile): application_name=parts['app'] ) + access_type = 'CONSOLE' if console else 'PROGRAMMATIC' + transaction_id = None application_type = None for checked_out_profile in self.b.my_access.list_checked_out_profiles(): same_env = checked_out_profile['environmentId'] == ids['environment_id'] same_profile = checked_out_profile['papId'] == ids['profile_id'] - if all([same_env, same_profile]): + same_access_type = checked_out_profile['accessType'] == access_type + if all([same_env, same_profile, same_access_type]): transaction_id = checked_out_profile['transactionId'] for available_profile in self.available_profiles: same_env_2 = checked_out_profile['environmentId'] == available_profile['env_id'] same_profile_2 = checked_out_profile['papId'] == available_profile['profile_id'] - if all([same_env_2, same_profile_2]): + if all([same_env_2, same_profile_2, access_type == 'PROGRAMMATIC']): application_type = available_profile['app_type'].lower() break break diff --git a/src/pybritive/commands/checkin.py b/src/pybritive/commands/checkin.py index 51cb855..fc94fe3 100644 --- a/src/pybritive/commands/checkin.py +++ b/src/pybritive/commands/checkin.py @@ -6,14 +6,15 @@ @click.command() @build_britive -@britive_options(names='tenant,token,silent,passphrase,federation_provider') +@britive_options(names='console,tenant,token,silent,passphrase,federation_provider') @click_smart_profile_argument -def checkin(ctx, tenant, token, silent, passphrase, federation_provider, profile): +def checkin(ctx, console, tenant, token, silent, passphrase, federation_provider, profile): """Checkin a profile. This command takes 1 required argument `PROFILE`. This should be a string representation of the profile that should be checked in. Format is `application name/environment name/profile name`. """ ctx.obj.britive.checkin( - profile=profile + profile=profile, + console=console ) diff --git a/src/pybritive/options/console.py b/src/pybritive/options/console.py index 0e36a7e..cb57d36 100644 --- a/src/pybritive/options/console.py +++ b/src/pybritive/options/console.py @@ -6,5 +6,5 @@ default=False, is_flag=True, show_default=True, - help='Checkout the console access for the profile instead of programmatic access.' + help='Checkout/checkin the console access for the profile instead of programmatic access.' ) From 299d9694ebe23c8f8001609be4a6402e342b23bc Mon Sep 17 00:00:00 2001 From: Thomas Rawley Date: Mon, 18 Sep 2023 09:11:51 -0400 Subject: [PATCH 23/27] britive version and changelog --- CHANGELOG.md | 2 +- requirements.txt | 2 +- setup.cfg | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 11b74f6..c991520 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,7 +15,7 @@ * Fixes and issue with `checkin` checking in the wrong profile type (programmatic vs console) #### Dependencies -* None +* `britive>=2.21.0` #### Other * None diff --git a/requirements.txt b/requirements.txt index 89770ed..e664bd4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ boto3 -britive>=2.20.1 +britive>=2.21.0 certifi>=2022.12.7 charset-normalizer==2.1.0 click==8.1.3 diff --git a/setup.cfg b/setup.cfg index f1187c0..76e02a7 100644 --- a/setup.cfg +++ b/setup.cfg @@ -26,7 +26,7 @@ install_requires = toml cryptography>=41.0.0 python-dateutil - britive>=2.20.1 + britive>=2.21.0 jmespath pyjwt From afb829428f69a65e8007916ca4c7ff544f06f9a8 Mon Sep 17 00:00:00 2001 From: Thomas Rawley Date: Mon, 18 Sep 2023 09:28:33 -0400 Subject: [PATCH 24/27] changelog date and conditional shell completion for api --- CHANGELOG.md | 2 +- src/pybritive/helpers/api_method_argument_decorator.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c991520..188101a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ * As of v1.4.0 release candidates will be published in an effort to get new features out faster while still allowing time for full QA testing before moving the release candidate to a full release. -## v1.5.0rc1 [2023-09-XX] +## v1.5.0rc1 [2023-09-18] #### What's New * None diff --git a/src/pybritive/helpers/api_method_argument_decorator.py b/src/pybritive/helpers/api_method_argument_decorator.py index edead14..2dad05a 100644 --- a/src/pybritive/helpers/api_method_argument_decorator.py +++ b/src/pybritive/helpers/api_method_argument_decorator.py @@ -1,12 +1,13 @@ import click import pkg_resources -from ..completers.api import api_completer + click_major_version = int(pkg_resources.get_distribution('click').version.split('.')[0]) def click_smart_api_method_argument(func): if click_major_version >= 8: + from ..completers.api import api_completer dec = click.argument('method', shell_complete=api_completer) else: dec = click.argument('method') From 8df42c2adcc6db83a6dbc5442ec82a361aa65349 Mon Sep 17 00:00:00 2001 From: twratl Date: Mon, 18 Sep 2023 10:36:56 -0400 Subject: [PATCH 25/27] clean up some merge changes --- CHANGELOG.md | 17 +++++++++++++++++ requirements.txt | 3 --- setup.cfg | 4 ---- src/pybritive/britive_cli.py | 13 ++++++------- 4 files changed, 23 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 188101a..589ce1e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,23 @@ * As of v1.4.0 release candidates will be published in an effort to get new features out faster while still allowing time for full QA testing before moving the release candidate to a full release. +## v1.5.0rc2 [2023-09-XX] +#### What's New +* None + +#### Enhancements +* Support `browser` option to login +* Support environment variable `PYBRITIVE_BROWSER` to allow a user to specify a default browser option, as well as use non-standard `webbrowser` options. + +#### Bug Fixes +* Fixes bug which did not always honor the specified browser. + +#### Dependencies +* None + +#### Other +* None + ## v1.5.0rc1 [2023-09-18] #### What's New * None diff --git a/requirements.txt b/requirements.txt index 33f5809..e664bd4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,4 @@ -<<<<<<< HEAD -======= boto3 ->>>>>>> theborch-just_browsing britive>=2.21.0 certifi>=2022.12.7 charset-normalizer==2.1.0 diff --git a/setup.cfg b/setup.cfg index f1c6935..76e02a7 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,10 +1,6 @@ [metadata] name = pybritive -<<<<<<< HEAD -version = 1.5.0rc1 -======= version = 1.5.0rc2 ->>>>>>> theborch-just_browsing author = Britive Inc. author_email = support@britive.com description = A pure Python CLI for Britive diff --git a/src/pybritive/britive_cli.py b/src/pybritive/britive_cli.py index 6cf9ea9..066f05a 100644 --- a/src/pybritive/britive_cli.py +++ b/src/pybritive/britive_cli.py @@ -474,13 +474,16 @@ def checkout(self, alias, blocktime, console, justification, mode, maxpolltime, credentials = None app_type = None credential_process_creds_found = False - response = None self.verbose_checkout = verbose # these 2 modes implicitly say that console access should be checked out without having to provide # the --console flag - if console := (mode and (mode == 'console' or mode.startswith('browser'))): - self.browser = mode.replace('browser-', '') if mode.startswith('browser') else os.getenv('PYBRITIVE_BROWSER') + if mode and (mode == 'console' or mode.startswith('browser')): + console = True + if mode.startswith('browser'): + self.browser = mode.replace('browser-', '') + else: + self.browser = os.getenv('PYBRITIVE_BROWSER') self._validate_justification(justification) @@ -945,11 +948,7 @@ def _ssh_generate_key(self, username, hostname, key_source): # and if we are using ssh-agent we need to add the private key via ssh-add if key_source == 'ssh-agent': -<<<<<<< HEAD - subprocess.run(['ssh-add', '-t', '60', '-q', str(pem_file)]) -======= subprocess.run(['ssh-add', '-t', '60', '-q', str(pem_file)], check=False) ->>>>>>> theborch-just_browsing return { 'private_key_filename': pem_file, From 0902d5339a0bff1f46263926d5f3b3b68f3d5a6c Mon Sep 17 00:00:00 2001 From: twratl Date: Mon, 18 Sep 2023 13:42:52 -0400 Subject: [PATCH 26/27] incorporation of changes from theborch and various other updates --- CHANGELOG.md | 6 +++--- src/pybritive/britive_cli.py | 11 ++++++----- src/pybritive/commands/login.py | 2 +- src/pybritive/completers/api_command.py | 2 ++ src/pybritive/completers/powershell_completion.py | 5 ++--- src/pybritive/helpers/credentials.py | 13 +++++++------ src/pybritive/options/browser.py | 7 +++---- 7 files changed, 24 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 589ce1e..8ebd536 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,12 +2,12 @@ * As of v1.4.0 release candidates will be published in an effort to get new features out faster while still allowing time for full QA testing before moving the release candidate to a full release. -## v1.5.0rc2 [2023-09-XX] +## v1.5.0rc2 [2023-09-18] #### What's New * None #### Enhancements -* Support `browser` option to login +* Support `browser` option for `login` command * Support environment variable `PYBRITIVE_BROWSER` to allow a user to specify a default browser option, as well as use non-standard `webbrowser` options. #### Bug Fixes @@ -17,7 +17,7 @@ * None #### Other -* None +* Various linting ## v1.5.0rc1 [2023-09-18] #### What's New diff --git a/src/pybritive/britive_cli.py b/src/pybritive/britive_cli.py index 066f05a..d20a8bc 100644 --- a/src/pybritive/britive_cli.py +++ b/src/pybritive/britive_cli.py @@ -29,7 +29,7 @@ class BritiveCli: def __init__(self, tenant_name: str = None, token: str = None, silent: bool = False, - passphrase: str = None, federation_provider: str = None, browser: str = os.getenv('PYBRITIVE_BROWSER')): + passphrase: str = None, federation_provider: str = None): self.silent = silent self.output_format = None self.tenant_name = None @@ -44,7 +44,7 @@ def __init__(self, tenant_name: str = None, token: str = None, silent: bool = Fa self.credential_manager = None self.verbose_checkout = False self.checkout_progress_previous_message = None - self.browser = browser + self.browser = None def set_output_format(self, output_format: str): self.output_format = self.config.get_output_format(output_format) @@ -73,7 +73,9 @@ def set_credential_manager(self): else: raise click.ClickException(f'invalid credential backend {backend}.') - def login(self, explicit: bool = False): + def login(self, explicit: bool = False, browser: str = None): + self.browser = browser + self.print(self.browser) self.tenant_name = self.config.get_tenant()['name'] self.tenant_alias = self.config.alias if explicit and self.token: @@ -1064,8 +1066,7 @@ def aws_console(profile, duration, browser): # but use the url to pop open a browser console_url = requests.Request('GET', url, params=params).prepare().url - browser = webbrowser.get(using=os.getenv('PYBRITIVE_BROWSER', browser)) - browser.open(console_url) + webbrowser.get(using=browser).open(console_url) def request_disposition(self, request_id, decision): self.login() diff --git a/src/pybritive/commands/login.py b/src/pybritive/commands/login.py index c968db1..1bd9583 100644 --- a/src/pybritive/commands/login.py +++ b/src/pybritive/commands/login.py @@ -12,4 +12,4 @@ def login(ctx, tenant, token, silent, passphrase, federation_provider, browser): This only applies when an API token has not been specified via `--token,-T` or via environment variable `BRITIVE_API_TOKEN`. """ - ctx.obj.britive.login(explicit=True) + ctx.obj.britive.login(explicit=True, browser=browser) diff --git a/src/pybritive/completers/api_command.py b/src/pybritive/completers/api_command.py index 81d122c..c7729d4 100644 --- a/src/pybritive/completers/api_command.py +++ b/src/pybritive/completers/api_command.py @@ -105,6 +105,8 @@ def command_api_patch_shell_complete(cls): __class__ = cls # provide closure cell for super() def shell_complete(self, ctx: Context, incomplete: str) -> t.List["CompletionItem"]: + from click.shell_completion import CompletionItem # here since this method will be monkey patched in + results: t.List["CompletionItem"] = [] if incomplete and not incomplete[0].isalnum(): diff --git a/src/pybritive/completers/powershell_completion.py b/src/pybritive/completers/powershell_completion.py index 88312d4..4860090 100644 --- a/src/pybritive/completers/powershell_completion.py +++ b/src/pybritive/completers/powershell_completion.py @@ -5,7 +5,6 @@ from click.shell_completion import add_completion_class from click.shell_completion import ShellComplete, CompletionItem - # inspired by https://raw.githubusercontent.com/tibortakacs/powershell-argcomplete/master/mat.complete.ps1 _powershell_source = """\ $%(complete_func)s = { @@ -31,8 +30,8 @@ Invoke-Expression %(prog_name)s -ErrorAction SilentlyContinue | Tee-Object -Var completionResult | Out-Null # cleanup environment variables - Remove-Item Env:COMP_LINE | Out-Null - Remove-Item Env:%(complete_var)s | Out-Null + Remove-Item Env:\\COMP_LINE | Out-Null + Remove-Item Env:\\%(complete_var)s | Out-Null # get list of completion items $items = $completionResult -split '\\r?\\n' diff --git a/src/pybritive/helpers/credentials.py b/src/pybritive/helpers/credentials.py index 7a91715..0153b58 100644 --- a/src/pybritive/helpers/credentials.py +++ b/src/pybritive/helpers/credentials.py @@ -47,7 +47,8 @@ def b64_encode_url_safe(value: bytes): # this base class expects self.credentials to be a dict - so sub classes need to convert to dict class CredentialManager: - def __init__(self, tenant_name: str, tenant_alias: str, cli: any, federation_provider: str = None, browser: str = os.getenv('PYBRITIVE_BROWSER')): + def __init__(self, tenant_name: str, tenant_alias: str, cli: any, federation_provider: str = None, + browser: str = None): self.cli = cli self.tenant = tenant_name self.alias = tenant_alias @@ -98,8 +99,7 @@ def perform_interactive_login(self): self._setup_requests_session() try: - browser = webbrowser.get(using=self.browser) - browser.open(url) + webbrowser.get(using=self.browser).open(url) except webbrowser.Error: self.cli.print( 'No web browser found. Please manually navigate to the link below and authenticate.' @@ -243,7 +243,8 @@ def has_valid_credentials(self): class FileCredentialManager(CredentialManager): - def __init__(self, tenant_name: str, tenant_alias: str, cli: any, federation_provider: str = None, browser: str = os.getenv('PYBRITIVE_BROWSER')): + def __init__(self, tenant_name: str, tenant_alias: str, cli: any, federation_provider: str = None, + browser: str = None): home = os.getenv('PYBRITIVE_HOME_DIR', str(Path.home())) self.path = str(Path(home) / '.britive' / 'pybritive.credentials') super().__init__(tenant_name, tenant_alias, cli, federation_provider, browser) @@ -285,12 +286,12 @@ def delete(self): class EncryptedFileCredentialManager(CredentialManager): def __init__(self, tenant_name: str, tenant_alias: str, cli: any, passphrase: str = None, - federation_provider: str = None, browser: str = os.getenv('PYBRITIVE_BROWSER')): + federation_provider: str = None, browser: str = None): home = os.getenv('PYBRITIVE_HOME_DIR', str(Path.home())) self.path = str(Path(home) / '.britive' / 'pybritive.credentials.encrypted') self.passphrase = passphrase self.string_encryptor = StringEncryption(passphrase=self.passphrase) - super().__init__(tenant_name, tenant_alias, cli, federation_provider) + super().__init__(tenant_name, tenant_alias, cli, federation_provider, browser) def decrypt(self, encrypted_access_token: str): try: diff --git a/src/pybritive/options/browser.py b/src/pybritive/options/browser.py index efe7a91..3d6e7b0 100644 --- a/src/pybritive/options/browser.py +++ b/src/pybritive/options/browser.py @@ -2,14 +2,13 @@ from ..choices.browser import browser_choices -# as of v1.1.0 not setting a default value here on purpose as the config file now has an -# aws section which provides a default value if the --mode option is omitted -# the default to `json` will occur now in helpers/cloud_credential_printer::CloudCredentialPrinter.__init__ option = click.option( '--browser', type=browser_choices, default=None, show_choices=True, + envvar='PYBRITIVE_BROWSER', + show_envvar=True, help='The browser to use when opening a URL from the PyBritive CLI. Defaults to None which indicates the standard ' - 'webbrowser selection process should be used.' + 'webbrowser selection process should be used. Can also source from PYBRITIVE_BROWSER.' ) From 0bd37efd15fed60fbac48e7a3f27c4388f6971c1 Mon Sep 17 00:00:00 2001 From: twratl Date: Mon, 18 Sep 2023 14:31:30 -0400 Subject: [PATCH 27/27] bug fix --- requirements.txt | 2 +- src/pybritive/britive_cli.py | 2 +- tests/test_0200_configure.py | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/requirements.txt b/requirements.txt index e664bd4..b741df4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,7 +2,7 @@ boto3 britive>=2.21.0 certifi>=2022.12.7 charset-normalizer==2.1.0 -click==8.1.3 +click~=8.1.3 cryptography>=41.0.0 google-cloud-compute idna==3.3 diff --git a/src/pybritive/britive_cli.py b/src/pybritive/britive_cli.py index d20a8bc..015366c 100644 --- a/src/pybritive/britive_cli.py +++ b/src/pybritive/britive_cli.py @@ -75,7 +75,6 @@ def set_credential_manager(self): def login(self, explicit: bool = False, browser: str = None): self.browser = browser - self.print(self.browser) self.tenant_name = self.config.get_tenant()['name'] self.tenant_alias = self.config.alias if explicit and self.token: @@ -559,6 +558,7 @@ def checkout(self, alias, blocktime, console, justification, mode, maxpolltime, def import_existing_npm_config(self): profile_aliases = self.config.import_global_npm_config() + if len(profile_aliases) == 0: return self.print('') diff --git a/tests/test_0200_configure.py b/tests/test_0200_configure.py index 6978b6a..80abf6f 100644 --- a/tests/test_0200_configure.py +++ b/tests/test_0200_configure.py @@ -4,7 +4,7 @@ def read_config(): - with open(f'{conftest.home()}/.britive/pybritive.config', 'r') as f: + with open(f'{conftest.home()}/.britive/pybritive.config', 'r', encoding='utf-8') as f: return f.read() @@ -31,7 +31,7 @@ def write_npm_config(simple=True): f'testalias = "{alias}"' ] - path.write_text('\n'.join(contents)) + path.write_text('\n'.join(contents), encoding='utf-8') def common_asserts(result, substring=None, exit_code=0):