diff --git a/homeassistant/components/google_health/config_flow.py b/homeassistant/components/google_health/config_flow.py index 25772446bbcd..1192cee386d4 100644 --- a/homeassistant/components/google_health/config_flow.py +++ b/homeassistant/components/google_health/config_flow.py @@ -8,7 +8,8 @@ from google_health_api import GoogleHealthApi from google_health_api.const import HealthApiScope from google_health_api.exceptions import ( GoogleHealthApiError, - HealthApiForbiddenException, + HealthApiScopeInsufficientException, + HealthApiServiceDisabledException, ) from homeassistant.config_entries import ( @@ -81,19 +82,22 @@ class OAuth2FlowHandler( try: identity = await api.get_identity() - except HealthApiForbiddenException as err: + except HealthApiServiceDisabledException as err: _LOGGER.error("Error getting Google Health identity: %s", err) return self.async_abort( reason="api_not_enabled", description_placeholders={"url": API_CONSOLE_URL}, ) + except HealthApiScopeInsufficientException as err: + _LOGGER.error("Error getting Google Health identity: %s", err) + return self.async_abort(reason="missing_profile_scope") except GoogleHealthApiError as err: _LOGGER.error("Error getting Google Health identity: %s", err) return self.async_abort(reason="cannot_connect") if not identity.health_user_id: _LOGGER.error("Google Health identity has no health_user_id") - return self.async_abort(reason="cannot_connect") + return self.async_abort(reason="missing_profile_scope") await self.async_set_unique_id(identity.health_user_id) if self.source in (SOURCE_REAUTH, SOURCE_RECONFIGURE): @@ -110,7 +114,7 @@ class OAuth2FlowHandler( try: userinfo = await api.get_user_info() display_name = userinfo.given_name or userinfo.name - except Exception as err: # pylint: disable=broad-except # noqa: BLE001 + except GoogleHealthApiError as err: _LOGGER.warning("Error fetching user profile name: %s", err) return self.async_create_entry( diff --git a/homeassistant/components/google_health/strings.json b/homeassistant/components/google_health/strings.json index d0f13b6778a5..1e50e1cf2c4a 100644 --- a/homeassistant/components/google_health/strings.json +++ b/homeassistant/components/google_health/strings.json @@ -10,7 +10,7 @@ "authorize_url_timeout": "[%key:common::config_flow::abort::oauth2_authorize_url_timeout%]", "cannot_connect": "Failed to connect.", "missing_configuration": "[%key:common::config_flow::abort::oauth2_missing_configuration%]", - "missing_profile_scope": "Missing required Google Health profile read permission.", + "missing_profile_scope": "Missing required Google Health profile read permission. Please try again and select the right permission.", "no_url_available": "[%key:common::config_flow::abort::oauth2_no_url_available%]", "oauth_error": "[%key:common::config_flow::abort::oauth2_error%]", "oauth_failed": "[%key:common::config_flow::abort::oauth2_failed%]", diff --git a/tests/components/google_health/test_config_flow.py b/tests/components/google_health/test_config_flow.py index 63fe0956bd32..2b6d30440828 100644 --- a/tests/components/google_health/test_config_flow.py +++ b/tests/components/google_health/test_config_flow.py @@ -5,7 +5,8 @@ from unittest.mock import AsyncMock, patch from google_health_api.const import HealthApiScope from google_health_api.exceptions import ( GoogleHealthApiError, - HealthApiForbiddenException, + HealthApiScopeInsufficientException, + HealthApiServiceDisabledException, ) from google_health_api.model import Identity import pytest @@ -243,8 +244,8 @@ async def test_config_flow_api_not_enabled( mock_google_health_client: AsyncMock, ) -> None: """Test config flow aborts if the Google Health API is not enabled.""" - mock_google_health_client.get_identity.side_effect = HealthApiForbiddenException( - "Forbidden" + mock_google_health_client.get_identity.side_effect = ( + HealthApiServiceDisabledException ) result = await hass.config_entries.flow.async_init( @@ -280,6 +281,50 @@ async def test_config_flow_api_not_enabled( } +@pytest.mark.usefixtures( + "current_request_with_host", "mock_setup_entry", "setup_credentials" +) +async def test_config_flow_scope_insufficient( + hass: HomeAssistant, + hass_client_no_auth: ClientSessionGenerator, + aioclient_mock: AiohttpClientMocker, + mock_google_health_client: AsyncMock, +) -> None: + """Test config flow aborts if the OAuth token has insufficient scope.""" + mock_google_health_client.get_identity.side_effect = ( + HealthApiScopeInsufficientException + ) + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_USER} + ) + state = config_entry_oauth2_flow._encode_jwt( + hass, + { + "flow_id": result["flow_id"], + "redirect_uri": "https://example.com/auth/external/callback", + }, + ) + + client = await hass_client_no_auth() + await client.get(f"/auth/external/callback?code=abcd&state={state}") + + aioclient_mock.post( + OAUTH2_TOKEN, + json={ + "refresh_token": "mock-refresh-token", + "access_token": "mock-access-token", + "type": "Bearer", + "expires_in": 60, + "scope": " ".join(OAUTH_SCOPES), + }, + ) + + result = await hass.config_entries.flow.async_configure(result["flow_id"]) + assert result["type"] is FlowResultType.ABORT + assert result["reason"] == "missing_profile_scope" + + @pytest.mark.usefixtures( "current_request_with_host", "mock_setup_entry", "setup_credentials" ) @@ -321,7 +366,7 @@ async def test_config_flow_missing_health_user_id( result = await hass.config_entries.flow.async_configure(result["flow_id"]) assert result["type"] is FlowResultType.ABORT - assert result["reason"] == "cannot_connect" + assert result["reason"] == "missing_profile_scope" @pytest.mark.usefixtures(