diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index f5e25ba0..9674c534 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `pyproject.toml` can now be supplied via `--requirements-file` for deploy and write-manifest. +- Perform case insensitive matching of the configured Snowflake connection authenticator. ## [1.29.0] - 2026-04-29 diff --git a/rsconnect/api.py b/rsconnect/api.py index cf1fb93e..bf643c8f 100644 --- a/rsconnect/api.py +++ b/rsconnect/api.py @@ -274,7 +274,11 @@ def fmt_payload(self): raise RSConnectException("No Snowflake connection found.") authenticator = params.get("authenticator") - if authenticator == "SNOWFLAKE_JWT": + if not authenticator: + raise NotImplementedError("Snowflake connection does not declare an authenticator.") + + authenticator = authenticator.lower() + if authenticator == "snowflake_jwt": spcs_url = urlparse(self.url) scope = f"session:role:{params['role']} {spcs_url.netloc}" if params.get("role") else spcs_url.netloc jwt = generate_jwt(self.snowflake_connection_name) diff --git a/tests/test_api.py b/tests/test_api.py index b42f816c..0be3a763 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -293,7 +293,7 @@ def test_token_endpoint_with_none_params(self, mock_get_parameters): server.token_endpoint() @patch("rsconnect.api.get_parameters") - def test_fmt_payload(self, mock_get_parameters): + def test_fmt_payload_jwt_uppercase(self, mock_get_parameters): server = SPCSConnectServer("https://spcs.example.com", "test-api-key", "example_connection") mock_get_parameters.return_value = { "account": "test_account", @@ -315,6 +315,50 @@ def test_fmt_payload(self, mock_get_parameters): mock_get_parameters.assert_called_once_with("example_connection") mock_generate_jwt.assert_called_once_with("example_connection") + @patch("rsconnect.api.get_parameters") + def test_fmt_payload_jwt_lowercase(self, mock_get_parameters): + server = SPCSConnectServer("https://spcs.example.com", "test-api-key", "example_connection") + mock_get_parameters.return_value = { + "account": "test_account", + "role": "test_role", + "authenticator": "snowflake_jwt", + } + + with patch("rsconnect.api.generate_jwt") as mock_generate_jwt: + mock_generate_jwt.return_value = "mocked_jwt" + payload = server.fmt_payload() + + assert ( + payload["body"] + == "scope=session%3Arole%3Atest_role+spcs.example.com&assertion=mocked_jwt&grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer" # noqa + ) + assert payload["headers"] == {"Content-Type": "application/x-www-form-urlencoded"} + assert payload["path"] == "/oauth/token" + + mock_get_parameters.assert_called_once_with("example_connection") + mock_generate_jwt.assert_called_once_with("example_connection") + + @patch("rsconnect.api.get_parameters") + def test_fmt_payload_with_unsupported_authenticator(self, mock_get_parameters): + server = SPCSConnectServer("https://spcs.example.com", "test-api-key", "example_connection") + mock_get_parameters.return_value = { + "account": "test_account", + "role": "test_role", + "authenticator": "unrecognized", + } + with pytest.raises(NotImplementedError, match="Unsupported authenticator for SPCS Connect: unrecognized"): + server.fmt_payload() + + @patch("rsconnect.api.get_parameters") + def test_fmt_payload_with_no_authenticator(self, mock_get_parameters): + server = SPCSConnectServer("https://spcs.example.com", "test-api-key", "example_connection") + mock_get_parameters.return_value = { + "account": "test_account", + "role": "test_role", + } + with pytest.raises(NotImplementedError, match="Snowflake connection does not declare an authenticator."): + server.fmt_payload() + @patch("rsconnect.api.get_parameters") def test_fmt_payload_with_none_params(self, mock_get_parameters): server = SPCSConnectServer("https://spcs.example.com", "test-api-key", "example_connection")