From 98a531236c7e65dad56bfa61d865896b8720633f Mon Sep 17 00:00:00 2001 From: Michal Wojcik Date: Tue, 28 Apr 2026 13:06:59 +0200 Subject: [PATCH 1/2] TPT-4175: cli: Update interactive config token access check --- linodecli/configuration/auth.py | 9 +++++++- tests/unit/test_configuration.py | 39 ++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/linodecli/configuration/auth.py b/linodecli/configuration/auth.py index 1b633c7bb..f73d89ce5 100644 --- a/linodecli/configuration/auth.py +++ b/linodecli/configuration/auth.py @@ -176,7 +176,14 @@ def _check_full_access(base_url: str, token: str) -> bool: verify=API_CA_PATH, ) - _handle_response_status(result, exit_on_error=True) + # IAM-enrolled users receive a 403 from /profile/grants since that + # endpoint is not accessible to them. Treat 403 as a valid response + # (i.e. not full access) rather than a fatal error. + _handle_response_status( + result, + exit_on_error=True, + status_validator=lambda status: status == 403, + ) return result.status_code == 204 diff --git a/tests/unit/test_configuration.py b/tests/unit/test_configuration.py index 409250330..d08bb641b 100644 --- a/tests/unit/test_configuration.py +++ b/tests/unit/test_configuration.py @@ -19,6 +19,7 @@ _default_text_input, _default_thing_input, ) +from linodecli.configuration.auth import _check_full_access class TestConfiguration: @@ -676,3 +677,41 @@ def test_custom_config_path(self, monkeypatch, tmp_path): for i, _ in enumerate(expected_configs): assert expected_configs[i] == configs[i] + + +class TestCheckFullAccess: + """ + Unit tests for _check_full_access + """ + + base_url = "https://linode-test.com" + test_token = "cli-dev-token" + + def test_full_access_returns_true(self): + """ + 204 No Content means the token has full (unrestricted) access. + """ + with requests_mock.Mocker() as m: + m.get(f"{self.base_url}/profile/grants", status_code=204) + assert _check_full_access(self.base_url, self.test_token) is True + + def test_restricted_access_returns_false(self): + """ + 200 with a grants body means the token has restricted access. + """ + with requests_mock.Mocker() as m: + m.get( + f"{self.base_url}/profile/grants", + status_code=200, + json={"linode": []}, + ) + assert _check_full_access(self.base_url, self.test_token) is False + + def test_iam_user_403_returns_false(self): + """ + IAM-enrolled users receive a 403 from /profile/grants. + This should be treated as "not full access" rather than a fatal error. + """ + with requests_mock.Mocker() as m: + m.get(f"{self.base_url}/profile/grants", status_code=403) + assert _check_full_access(self.base_url, self.test_token) is False From c738a6112ff314f8c3afebaf545b47fcaf17b1ac Mon Sep 17 00:00:00 2001 From: Michal Wojcik <32574975+mgwoj@users.noreply.github.com> Date: Tue, 28 Apr 2026 13:41:28 +0200 Subject: [PATCH 2/2] Update tests/unit/test_configuration.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- tests/unit/test_configuration.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/test_configuration.py b/tests/unit/test_configuration.py index d08bb641b..afe164cab 100644 --- a/tests/unit/test_configuration.py +++ b/tests/unit/test_configuration.py @@ -16,10 +16,10 @@ from linodecli import configuration from linodecli.configuration import ( _bool_input, + _check_full_access, _default_text_input, _default_thing_input, ) -from linodecli.configuration.auth import _check_full_access class TestConfiguration: