Skip to content

Commit

Permalink
Handle expired credentials in reauth in google calendar initialization (
Browse files Browse the repository at this point in the history
#69772)

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
  • Loading branch information
2 people authored and balloob committed Apr 11, 2022
1 parent 7c06514 commit 16a1a93
Show file tree
Hide file tree
Showing 5 changed files with 69 additions and 5 deletions.
14 changes: 13 additions & 1 deletion homeassistant/components/google/__init__.py
Expand Up @@ -7,6 +7,7 @@
import logging
from typing import Any

import aiohttp
from httplib2.error import ServerNotFoundError
from oauth2client.file import Storage
import voluptuous as vol
Expand All @@ -24,7 +25,11 @@
CONF_OFFSET,
)
from homeassistant.core import HomeAssistant, ServiceCall
from homeassistant.exceptions import ConfigEntryAuthFailed, HomeAssistantError
from homeassistant.exceptions import (
ConfigEntryAuthFailed,
ConfigEntryNotReady,
HomeAssistantError,
)
from homeassistant.helpers import config_entry_oauth2_flow
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.dispatcher import async_dispatcher_send
Expand Down Expand Up @@ -191,7 +196,14 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
if session.token["expires_at"] >= datetime(2070, 1, 1).timestamp():
session.token["expires_in"] = 0
session.token["expires_at"] = datetime.now().timestamp()
try:
await session.async_ensure_token_valid()
except aiohttp.ClientResponseError as err:
if 400 <= err.status < 500:
raise ConfigEntryAuthFailed from err
raise ConfigEntryNotReady from err
except aiohttp.ClientError as err:
raise ConfigEntryNotReady from err

required_scope = hass.data[DOMAIN][DATA_CONFIG][CONF_CALENDAR_ACCESS].scope
if required_scope not in session.token.get("scope", []):
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/google/config_flow.py
Expand Up @@ -34,7 +34,7 @@ def logger(self) -> logging.Logger:
return logging.getLogger(__name__)

async def async_step_import(self, info: dict[str, Any]) -> FlowResult:
"""Import existing auth from Nest."""
"""Import existing auth into a new config entry."""
if self._async_current_entries():
return self.async_abort(reason="single_instance_allowed")
implementations = await config_entry_oauth2_flow.async_get_implementations(
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/google/strings.json
Expand Up @@ -6,7 +6,7 @@
},
"reauth_confirm": {
"title": "[%key:common::config_flow::title::reauth%]",
"description": "The Nest integration needs to re-authenticate your account"
"description": "The Google Calendar integration needs to re-authenticate your account"
},
"auth": {
"title": "Link Google Account"
Expand Down
4 changes: 2 additions & 2 deletions homeassistant/components/google/translations/en.json
Expand Up @@ -3,7 +3,7 @@
"abort": {
"already_configured": "Account is already configured",
"already_in_progress": "Configuration flow is already in progress",
"code_expired": "Authentication code expired, please try again.",
"code_expired": "Authentication code expired or credential setup is invalid, please try again.",
"invalid_access_token": "Invalid access token",
"missing_configuration": "The component is not configured. Please follow the documentation.",
"oauth_error": "Received invalid token data.",
Expand All @@ -23,7 +23,7 @@
"title": "Pick Authentication Method"
},
"reauth_confirm": {
"description": "The Nest integration needs to re-authenticate your account",
"description": "The Google Calendar integration needs to re-authenticate your account",
"title": "Reauthenticate Integration"
}
}
Expand Down
52 changes: 52 additions & 0 deletions tests/components/google/test_init.py
Expand Up @@ -3,6 +3,7 @@

from collections.abc import Awaitable, Callable
import datetime
import http
import time
from typing import Any
from unittest.mock import Mock, call, patch
Expand Down Expand Up @@ -32,6 +33,8 @@
from tests.common import MockConfigEntry
from tests.test_util.aiohttp import AiohttpClientMocker

EXPIRED_TOKEN_TIMESTAMP = datetime.datetime(2022, 4, 8).timestamp()

# Typing helpers
HassApi = Callable[[], Awaitable[dict[str, Any]]]

Expand Down Expand Up @@ -505,3 +508,52 @@ async def test_invalid_token_expiry_in_config_entry(
assert entries[0].state is ConfigEntryState.LOADED
assert entries[0].data["token"]["access_token"] == "some-updated-token"
assert entries[0].data["token"]["expires_in"] == expires_in


@pytest.mark.parametrize("config_entry_token_expiry", [EXPIRED_TOKEN_TIMESTAMP])
async def test_expired_token_refresh_internal_error(
hass: HomeAssistant,
component_setup: ComponentSetup,
setup_config_entry: MockConfigEntry,
aioclient_mock: AiohttpClientMocker,
) -> None:
"""Generic errors on reauth are treated as a retryable setup error."""

aioclient_mock.post(
"https://oauth2.googleapis.com/token",
status=http.HTTPStatus.INTERNAL_SERVER_ERROR,
)

assert await component_setup()

entries = hass.config_entries.async_entries(DOMAIN)
assert len(entries) == 1
assert entries[0].state is ConfigEntryState.SETUP_RETRY


@pytest.mark.parametrize(
"config_entry_token_expiry",
[EXPIRED_TOKEN_TIMESTAMP],
)
async def test_expired_token_requires_reauth(
hass: HomeAssistant,
component_setup: ComponentSetup,
setup_config_entry: MockConfigEntry,
aioclient_mock: AiohttpClientMocker,
) -> None:
"""Test case where reauth is required for token that cannot be refreshed."""

aioclient_mock.post(
"https://oauth2.googleapis.com/token",
status=http.HTTPStatus.BAD_REQUEST,
)

assert await component_setup()

entries = hass.config_entries.async_entries(DOMAIN)
assert len(entries) == 1
assert entries[0].state is ConfigEntryState.SETUP_ERROR

flows = hass.config_entries.flow.async_progress()
assert len(flows) == 1
assert flows[0]["step_id"] == "reauth_confirm"

0 comments on commit 16a1a93

Please sign in to comment.