Fix Google Mail expired authorization (#102735)
* Fix Google Mail expired authorization * add test * raise HomeAssistantError * handle in api module * uno mas
This commit is contained in:
parent
ba7dbc5927
commit
7dbe0c3a48
4 changed files with 49 additions and 22 deletions
|
@ -1,12 +1,9 @@
|
||||||
"""Support for Google Mail."""
|
"""Support for Google Mail."""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from aiohttp.client_exceptions import ClientError, ClientResponseError
|
|
||||||
|
|
||||||
from homeassistant.config_entries import ConfigEntry, ConfigEntryState
|
from homeassistant.config_entries import ConfigEntry, ConfigEntryState
|
||||||
from homeassistant.const import CONF_NAME, Platform
|
from homeassistant.const import CONF_NAME, Platform
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
|
|
||||||
from homeassistant.helpers import config_validation as cv, discovery
|
from homeassistant.helpers import config_validation as cv, discovery
|
||||||
from homeassistant.helpers.config_entry_oauth2_flow import (
|
from homeassistant.helpers.config_entry_oauth2_flow import (
|
||||||
OAuth2Session,
|
OAuth2Session,
|
||||||
|
@ -35,16 +32,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
implementation = await async_get_config_entry_implementation(hass, entry)
|
implementation = await async_get_config_entry_implementation(hass, entry)
|
||||||
session = OAuth2Session(hass, entry, implementation)
|
session = OAuth2Session(hass, entry, implementation)
|
||||||
auth = AsyncConfigEntryAuth(session)
|
auth = AsyncConfigEntryAuth(session)
|
||||||
try:
|
await auth.check_and_refresh_token()
|
||||||
await auth.check_and_refresh_token()
|
|
||||||
except ClientResponseError as err:
|
|
||||||
if 400 <= err.status < 500:
|
|
||||||
raise ConfigEntryAuthFailed(
|
|
||||||
"OAuth session is not valid, reauth required"
|
|
||||||
) from err
|
|
||||||
raise ConfigEntryNotReady from err
|
|
||||||
except ClientError as err:
|
|
||||||
raise ConfigEntryNotReady from err
|
|
||||||
hass.data[DOMAIN][entry.entry_id] = auth
|
hass.data[DOMAIN][entry.entry_id] = auth
|
||||||
|
|
||||||
hass.async_create_task(
|
hass.async_create_task(
|
||||||
|
|
|
@ -1,9 +1,16 @@
|
||||||
"""API for Google Mail bound to Home Assistant OAuth."""
|
"""API for Google Mail bound to Home Assistant OAuth."""
|
||||||
|
from aiohttp.client_exceptions import ClientError, ClientResponseError
|
||||||
from google.auth.exceptions import RefreshError
|
from google.auth.exceptions import RefreshError
|
||||||
from google.oauth2.credentials import Credentials
|
from google.oauth2.credentials import Credentials
|
||||||
from googleapiclient.discovery import Resource, build
|
from googleapiclient.discovery import Resource, build
|
||||||
|
|
||||||
|
from homeassistant.config_entries import ConfigEntryState
|
||||||
from homeassistant.const import CONF_ACCESS_TOKEN
|
from homeassistant.const import CONF_ACCESS_TOKEN
|
||||||
|
from homeassistant.exceptions import (
|
||||||
|
ConfigEntryAuthFailed,
|
||||||
|
ConfigEntryNotReady,
|
||||||
|
HomeAssistantError,
|
||||||
|
)
|
||||||
from homeassistant.helpers import config_entry_oauth2_flow
|
from homeassistant.helpers import config_entry_oauth2_flow
|
||||||
|
|
||||||
|
|
||||||
|
@ -24,14 +31,30 @@ class AsyncConfigEntryAuth:
|
||||||
|
|
||||||
async def check_and_refresh_token(self) -> str:
|
async def check_and_refresh_token(self) -> str:
|
||||||
"""Check the token."""
|
"""Check the token."""
|
||||||
await self.oauth_session.async_ensure_token_valid()
|
try:
|
||||||
|
await self.oauth_session.async_ensure_token_valid()
|
||||||
|
except (RefreshError, ClientResponseError, ClientError) as ex:
|
||||||
|
if (
|
||||||
|
self.oauth_session.config_entry.state
|
||||||
|
is ConfigEntryState.SETUP_IN_PROGRESS
|
||||||
|
):
|
||||||
|
if isinstance(ex, ClientResponseError) and 400 <= ex.status < 500:
|
||||||
|
raise ConfigEntryAuthFailed(
|
||||||
|
"OAuth session is not valid, reauth required"
|
||||||
|
) from ex
|
||||||
|
raise ConfigEntryNotReady from ex
|
||||||
|
if (
|
||||||
|
isinstance(ex, RefreshError)
|
||||||
|
or hasattr(ex, "status")
|
||||||
|
and ex.status == 400
|
||||||
|
):
|
||||||
|
self.oauth_session.config_entry.async_start_reauth(
|
||||||
|
self.oauth_session.hass
|
||||||
|
)
|
||||||
|
raise HomeAssistantError(ex) from ex
|
||||||
return self.access_token
|
return self.access_token
|
||||||
|
|
||||||
async def get_resource(self) -> Resource:
|
async def get_resource(self) -> Resource:
|
||||||
"""Get current resource."""
|
"""Get current resource."""
|
||||||
try:
|
credentials = Credentials(await self.check_and_refresh_token())
|
||||||
credentials = Credentials(await self.check_and_refresh_token())
|
|
||||||
except RefreshError as ex:
|
|
||||||
self.oauth_session.config_entry.async_start_reauth(self.oauth_session.hass)
|
|
||||||
raise ex
|
|
||||||
return build("gmail", "v1", credentials=credentials)
|
return build("gmail", "v1", credentials=credentials)
|
||||||
|
|
|
@ -73,8 +73,13 @@ async def test_expired_token_refresh_success(
|
||||||
http.HTTPStatus.INTERNAL_SERVER_ERROR,
|
http.HTTPStatus.INTERNAL_SERVER_ERROR,
|
||||||
ConfigEntryState.SETUP_RETRY,
|
ConfigEntryState.SETUP_RETRY,
|
||||||
),
|
),
|
||||||
|
(
|
||||||
|
time.time() - 3600,
|
||||||
|
http.HTTPStatus.BAD_REQUEST,
|
||||||
|
ConfigEntryState.SETUP_ERROR,
|
||||||
|
),
|
||||||
],
|
],
|
||||||
ids=["failure_requires_reauth", "transient_failure"],
|
ids=["failure_requires_reauth", "transient_failure", "revoked_auth"],
|
||||||
)
|
)
|
||||||
async def test_expired_token_refresh_failure(
|
async def test_expired_token_refresh_failure(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
"""Services tests for the Google Mail integration."""
|
"""Services tests for the Google Mail integration."""
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
from aiohttp.client_exceptions import ClientResponseError
|
||||||
from google.auth.exceptions import RefreshError
|
from google.auth.exceptions import RefreshError
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from homeassistant import config_entries
|
from homeassistant import config_entries
|
||||||
from homeassistant.components.google_mail import DOMAIN
|
from homeassistant.components.google_mail import DOMAIN
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
|
|
||||||
from .conftest import BUILD, SENSOR, TOKEN, ComponentSetup
|
from .conftest import BUILD, SENSOR, TOKEN, ComponentSetup
|
||||||
|
|
||||||
|
@ -57,13 +59,22 @@ async def test_set_vacation(
|
||||||
assert len(mock_client.mock_calls) == 5
|
assert len(mock_client.mock_calls) == 5
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("side_effect"),
|
||||||
|
(
|
||||||
|
(RefreshError,),
|
||||||
|
(ClientResponseError("", (), status=400),),
|
||||||
|
),
|
||||||
|
)
|
||||||
async def test_reauth_trigger(
|
async def test_reauth_trigger(
|
||||||
hass: HomeAssistant, setup_integration: ComponentSetup
|
hass: HomeAssistant,
|
||||||
|
setup_integration: ComponentSetup,
|
||||||
|
side_effect,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test reauth is triggered after a refresh error during service call."""
|
"""Test reauth is triggered after a refresh error during service call."""
|
||||||
await setup_integration()
|
await setup_integration()
|
||||||
|
|
||||||
with patch(TOKEN, side_effect=RefreshError), pytest.raises(RefreshError):
|
with patch(TOKEN, side_effect=side_effect), pytest.raises(HomeAssistantError):
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
"set_vacation",
|
"set_vacation",
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue