diff --git a/b2/console_tool.py b/b2/console_tool.py index f802ca29a..8fe8b4bc7 100644 --- a/b2/console_tool.py +++ b/b2/console_tool.py @@ -1044,7 +1044,7 @@ def _setup_parser(cls, parser): def _run(self, args): # Handle internal options for testing inside Backblaze. # These are not documented in the usage string. - realm = self._get_realm(args) + realm = self._get_user_requested_realm(args) if args.applicationKeyId is None: args.applicationKeyId = ( @@ -1060,17 +1060,21 @@ def _run(self, args): return self.authorize(args.applicationKeyId, args.applicationKey, realm) - def authorize(self, application_key_id, application_key, realm): + def authorize(self, application_key_id, application_key, realm: str | None): """ Perform the authorization and capability checks, report errors. :param application_key_id: application key ID used to authenticate :param application_key: application key - :param realm: authorization realm + :param realm: authorization realm; if None, production is used :return: exit status """ + verbose_realm = bool(realm) + realm = realm or 'production' url = REALM_URLS.get(realm, realm) - self._print_stderr(f"Using {url}") + logger.info(f"Using {url}") + if verbose_realm: + self._print_stderr(f'Using {url}') try: self.api.authorize_account(realm, application_key_id, application_key) @@ -1099,7 +1103,10 @@ def authorize(self, application_key_id, application_key, realm): return 1 @classmethod - def _get_realm(cls, args): + def _get_user_requested_realm(cls, args) -> str | None: + """ + Determine the realm to use for authorization. + """ if args.dev: return 'dev' if args.staging: @@ -1107,7 +1114,7 @@ def _get_realm(cls, args): if args.environment: return args.environment - return os.environ.get(B2_ENVIRONMENT_ENV_VAR, 'production') + return os.environ.get(B2_ENVIRONMENT_ENV_VAR) @B2.register_subcommand @@ -4032,9 +4039,9 @@ def authorize_from_env(self, command_class): f'Please provide both "{B2_APPLICATION_KEY_ENV_VAR}" and "{B2_APPLICATION_KEY_ID_ENV_VAR}" environment variables or none of them' ) return 1 - realm = os.environ.get(B2_ENVIRONMENT_ENV_VAR, 'production') + realm = os.environ.get(B2_ENVIRONMENT_ENV_VAR) - if self.api.account_info.is_same_key(key_id, realm): + if self.api.account_info.is_same_key(key_id, realm or 'production'): return 0 logger.info('authorize-account is being run from env variables') diff --git a/changelog.d/949.fixed.md b/changelog.d/949.fixed.md new file mode 100644 index 000000000..72b54cf28 --- /dev/null +++ b/changelog.d/949.fixed.md @@ -0,0 +1 @@ +Don't print `Using https://REALM" in stderr unless explicitly set by user. diff --git a/test/unit/console_tool/conftest.py b/test/unit/console_tool/conftest.py index aae3562fc..cd9bae128 100644 --- a/test/unit/console_tool/conftest.py +++ b/test/unit/console_tool/conftest.py @@ -13,6 +13,8 @@ import pytest +import b2.console_tool + class ConsoleToolTester(BaseConsoleToolTest): def authorize(self): @@ -22,6 +24,21 @@ def run(self, *args, **kwargs): return self._run_command(*args, **kwargs) +@pytest.fixture +def cwd_path(tmp_path): + """Set up a test directory and return its path.""" + prev_cwd = os.getcwd() + os.chdir(tmp_path) + yield tmp_path + os.chdir(prev_cwd) + + +@pytest.fixture +def b2_cli_log_fix(caplog): + caplog.set_level(0) # prevent pytest from blocking logs + b2.console_tool.logger.setLevel(0) # reset logger level to default + + @pytest.fixture def b2_cli(): cli_tester = ConsoleToolTester() diff --git a/test/unit/console_tool/test_authorize_account.py b/test/unit/console_tool/test_authorize_account.py new file mode 100644 index 000000000..85201b047 --- /dev/null +++ b/test/unit/console_tool/test_authorize_account.py @@ -0,0 +1,124 @@ +###################################################################### +# +# File: test/unit/console_tool/test_authorize_account.py +# +# Copyright 2023 Backblaze Inc. All Rights Reserved. +# +# License https://www.backblaze.com/using_b2_code.html +# +###################################################################### +from unittest import mock + +import pytest + +from b2._cli.const import ( + B2_APPLICATION_KEY_ENV_VAR, + B2_APPLICATION_KEY_ID_ENV_VAR, + B2_ENVIRONMENT_ENV_VAR, +) + + +@pytest.fixture +def b2_cli_is_authorized_afterwards(b2_cli): + assert b2_cli.account_info.get_account_auth_token() is None + yield b2_cli + assert b2_cli.account_info.get_account_auth_token() is not None + + +def test_authorize_with_bad_key(b2_cli): + expected_stdout = "" + expected_stderr = """ + ERROR: unable to authorize account: Invalid authorization token. Server said: secret key is wrong (unauthorized) + """ + + b2_cli._run_command( + ["authorize-account", b2_cli.account_id, "bad-app-key"], + expected_stdout, + expected_stderr, + 1, + ) + assert b2_cli.account_info.get_account_auth_token() is None + + +@pytest.mark.parametrize( + "command", + [ + "authorize-account", + "authorize_account", + ], +) +def test_authorize_with_good_key(b2_cli, b2_cli_is_authorized_afterwards, command): + assert b2_cli.account_info.get_account_auth_token() is None + + expected_stderr = """ + """ + + b2_cli._run_command([command, b2_cli.account_id, b2_cli.master_key], "", expected_stderr, 0) + + assert b2_cli.account_info.get_account_auth_token() is not None + + +def test_authorize_using_env_variables(b2_cli): + assert b2_cli.account_info.get_account_auth_token() is None + + expected_stderr = """ + """ + + with mock.patch.dict( + "os.environ", + { + B2_APPLICATION_KEY_ID_ENV_VAR: b2_cli.account_id, + B2_APPLICATION_KEY_ENV_VAR: b2_cli.master_key, + }, + ): + b2_cli._run_command(["authorize-account"], "", expected_stderr, 0) + + assert b2_cli.account_info.get_account_auth_token() is not None + + +@pytest.mark.parametrize( + "flags,realm_url", + [ + ([], "http://production.example.com"), + (["--debugLogs"], "http://production.example.com"), + (["--environment", "http://custom.example.com"], "http://custom.example.com"), + (["--environment", "production"], "http://production.example.com"), + (["--dev"], "http://api.backblazeb2.xyz:8180"), + (["--staging"], "https://api.backblaze.net"), + ], +) +def test_authorize_towards_realm( + b2_cli, b2_cli_is_authorized_afterwards, flags, realm_url, cwd_path, b2_cli_log_fix +): + expected_stderr = f"Using {realm_url}\n" if any(f != "--debugLogs" for f in flags) else "" + + b2_cli._run_command( + ["authorize-account", *flags, b2_cli.account_id, b2_cli.master_key], + "", + expected_stderr, + 0, + ) + log_path = cwd_path / "b2_cli.log" + if "--debugLogs" in flags: + assert f"Using {realm_url}\n" in log_path.read_text() + else: + assert not log_path.exists() + + +def test_authorize_towards_custom_realm_using_env(b2_cli, b2_cli_is_authorized_afterwards): + expected_stderr = """ + Using http://custom2.example.com + """ + + with mock.patch.dict( + "os.environ", + { + B2_ENVIRONMENT_ENV_VAR: "http://custom2.example.com", + }, + ): + b2_cli._run_command( + ["authorize-account", b2_cli.account_id, b2_cli.master_key], + "", + expected_stderr, + 0, + ) diff --git a/test/unit/test_console_tool.py b/test/unit/test_console_tool.py index dd58ac162..8593797e0 100644 --- a/test/unit/test_console_tool.py +++ b/test/unit/test_console_tool.py @@ -256,106 +256,6 @@ def _upload_multiple_files(cls, bucket): class TestConsoleTool(BaseConsoleToolTest): - def test_authorize_with_bad_key(self): - expected_stdout = '' - expected_stderr = ''' - Using http://production.example.com - ERROR: unable to authorize account: Invalid authorization token. Server said: secret key is wrong (unauthorized) - ''' - - self._run_command( - ['authorize-account', self.account_id, 'bad-app-key'], expected_stdout, expected_stderr, - 1 - ) - - def test_authorize_with_good_key_using_hyphen(self): - # Initial condition - assert self.account_info.get_account_auth_token() is None - - # Authorize an account with a good api key. - expected_stderr = """ - Using http://production.example.com - """ - - self._run_command( - ['authorize-account', self.account_id, self.master_key], '', expected_stderr, 0 - ) - - # Auth token should be in account info now - assert self.account_info.get_account_auth_token() is not None - - def test_authorize_with_good_key_using_underscore(self): - # Initial condition - assert self.account_info.get_account_auth_token() is None - - # Authorize an account with a good api key. - expected_stderr = """ - Using http://production.example.com - """ - self._run_command( - ['authorize_account', self.account_id, self.master_key], '', expected_stderr, 0 - ) - - # Auth token should be in account info now - assert self.account_info.get_account_auth_token() is not None - - def test_authorize_using_env_variables(self): - # Initial condition - assert self.account_info.get_account_auth_token() is None - - # Authorize an account with a good api key. - expected_stderr = """ - Using http://production.example.com - """ - - # Setting up environment variables - with mock.patch.dict( - 'os.environ', { - B2_APPLICATION_KEY_ID_ENV_VAR: self.account_id, - B2_APPLICATION_KEY_ENV_VAR: self.master_key, - } - ): - assert B2_APPLICATION_KEY_ID_ENV_VAR in os.environ - assert B2_APPLICATION_KEY_ENV_VAR in os.environ - - self._run_command(['authorize-account'], '', expected_stderr, 0) - - # Auth token should be in account info now - assert self.account_info.get_account_auth_token() is not None - - def test_authorize_towards_custom_realm(self): - # Initial condition - assert self.account_info.get_account_auth_token() is None - - # Authorize an account with a good api key. - expected_stderr = """ - Using http://custom.example.com - """ - - # realm provided with args - self._run_command( - [ - 'authorize-account', '--environment', 'http://custom.example.com', self.account_id, - self.master_key - ], '', expected_stderr, 0 - ) - - # Auth token should be in account info now - assert self.account_info.get_account_auth_token() is not None - - expected_stderr = """ - Using http://custom2.example.com - """ - # realm provided with env var - with mock.patch.dict( - 'os.environ', { - B2_ENVIRONMENT_ENV_VAR: 'http://custom2.example.com', - } - ): - self._run_command( - ['authorize-account', self.account_id, self.master_key], '', expected_stderr, 0 - ) - def test_create_key_and_authorize_with_it(self): # Start with authorizing with the master key self._authorize_account() @@ -372,7 +272,7 @@ def test_create_key_and_authorize_with_it(self): self._run_command( ['authorize-account', 'appKeyId0', 'appKey0'], '', - 'Using http://production.example.com\n', + '', 0, ) @@ -396,7 +296,7 @@ def test_create_key_with_authorization_from_env_vars(self): self._run_command( ['create-key', 'key1', 'listBuckets,listKeys'], 'appKeyId0 appKey0\n', - 'Using http://production.example.com\n', + '', 0, ) @@ -418,7 +318,7 @@ def test_create_key_with_authorization_from_env_vars(self): self._run_command( ['create-key', 'key1', 'listBuckets,listKeys'], 'appKeyId2 appKey2\n', - 'Using http://production.example.com\n', + '', 0, ) @@ -445,7 +345,6 @@ def test_authorize_key_without_list_buckets(self): self._run_command( ['authorize-account', 'appKeyId0', 'appKey0'], '', - 'Using http://production.example.com\n' 'ERROR: application key has no listBuckets capability, which is required for the b2 command-line tool\n', 1, ) @@ -519,7 +418,7 @@ def test_create_bucket_key_and_authorize_with_it(self): self._run_command( ['authorize-account', 'appKeyId0', 'appKey0'], '', - 'Using http://production.example.com\n', + '', 0, ) @@ -770,10 +669,7 @@ def test_keys(self): self._run_command(['list-keys', '--long'], expected_list_keys_out_long, '', 0) # authorize and make calls using application key with no restrictions - self._run_command( - ['authorize-account', 'appKeyId0', 'appKey0'], '', - 'Using http://production.example.com\n', 0 - ) + self._run_command(['authorize-account', 'appKeyId0', 'appKey0'], '', '', 0) self._run_command( ['list-buckets'], 'bucket_0 allPublic my-bucket-a\nbucket_2 allPublic my-bucket-c\n', '', 0 @@ -796,10 +692,7 @@ def test_keys(self): self._run_command(['get-bucket', 'my-bucket-a'], expected_json_in_stdout=expected_json) # authorize and make calls using an application key with bucket restrictions - self._run_command( - ['authorize-account', 'appKeyId1', 'appKey1'], '', - 'Using http://production.example.com\n', 0 - ) + self._run_command(['authorize-account', 'appKeyId1', 'appKey1'], '', '', 0) self._run_command( ['list-buckets'], '', 'ERROR: Application key is restricted to bucket: my-bucket-a\n', 1 @@ -2350,7 +2243,6 @@ def test_list_buckets_not_allowed_for_app_key(self): self._run_command( ['authorize-account', 'appKeyId0', 'appKey0'], '', - 'Using http://production.example.com\n' 'ERROR: application key has no listBuckets capability, which is required for the b2 command-line tool\n', 1, ) @@ -2379,7 +2271,6 @@ def test_bucket_missing_for_bucket_key(self): self._run_command( ['authorize-account', 'appKeyId0', 'appKey0'], '', - "Using http://production.example.com\n" "ERROR: unable to authorize account: Application key is restricted to a bucket that doesn't exist\n", 1, )