1 """Client library for talking to Google APIs."""
3 from __future__
import annotations
7 from typing
import Any, cast
10 from gcal_sync.auth
import AbstractAuth
11 from oauth2client.client
import (
15 OAuth2DeviceCodeError,
24 async_track_point_in_utc_time,
25 async_track_time_interval,
29 from .const
import CONF_CALENDAR_ACCESS, DEFAULT_FEATURE_ACCESS, FeatureAccess
31 _LOGGER = logging.getLogger(__name__)
34 EXCHANGE_TIMEOUT_SECONDS = 60
35 DEVICE_AUTH_CREDS =
"creds"
39 """OAuth related error."""
43 """Error with an invalid credential that does not support device auth."""
47 """OAuth implementation that supports both Web Auth (base class) and Device Auth."""
50 """Resolve a Google API Credentials object to Home Assistant token."""
51 if DEVICE_AUTH_CREDS
not in external_data:
54 creds: Credentials = external_data[DEVICE_AUTH_CREDS]
55 delta = creds.token_expiry.replace(tzinfo=datetime.UTC) - dt_util.utcnow()
57 "Token expires at %s (in %s)", creds.token_expiry, delta.total_seconds()
60 "access_token": creds.access_token,
61 "refresh_token": creds.refresh_token,
62 "scope":
" ".join(creds.scopes),
63 "token_type":
"Bearer",
64 "expires_in": delta.total_seconds(),
69 """OAuth2 device flow for exchanging a code for an access token."""
74 oauth_flow: OAuth2WebServerFlow,
75 device_flow_info: DeviceFlowInfo,
77 """Initialize DeviceFlow."""
80 self._device_flow_info: DeviceFlowInfo = device_flow_info
83 self.
_listener_listener: CALLBACK_TYPE |
None =
None
84 self.
_creds_creds: Credentials |
None =
None
88 """Return the verification url that the user should visit to enter the code."""
89 return self._device_flow_info.verification_url
93 """Return the code that the user should enter at the verification url."""
94 return self._device_flow_info.user_code
99 update_callback: CALLBACK_TYPE,
101 """Invoke the update callback when the exchange finishes or on timeout."""
105 def creds(self) -> Credentials | None:
106 """Return result of exchange step or None on timeout."""
110 """Start the device auth exchange flow polling."""
111 _LOGGER.debug(
"Starting exchange flow")
112 max_timeout = dt_util.utcnow() + datetime.timedelta(
113 seconds=EXCHANGE_TIMEOUT_SECONDS
117 user_code_expiry = self._device_flow_info.user_code_expiry.replace(
120 expiration_time =
min(user_code_expiry, max_timeout)
125 datetime.timedelta(seconds=self._device_flow_info.interval),
132 _LOGGER.debug(
"Attempting OAuth code exchange")
135 except FlowExchangeError:
136 _LOGGER.debug(
"Token not yet ready; trying again later")
141 return self.
_oauth_flow_oauth_flow.step2_exchange(device_flow_info=self._device_flow_info)
145 _LOGGER.debug(
"OAuth token exchange timeout")
159 """Return the desired calendar feature access."""
160 if config_entry.options
and CONF_CALENDAR_ACCESS
in config_entry.options:
161 return FeatureAccess[config_entry.options[CONF_CALENDAR_ACCESS]]
162 return DEFAULT_FEATURE_ACCESS
166 hass: HomeAssistant, client_id: str, client_secret: str, access: FeatureAccess
168 """Create a new Device flow."""
169 oauth_flow = OAuth2WebServerFlow(
171 client_secret=client_secret,
176 device_flow_info = await hass.async_add_executor_job(
177 oauth_flow.step1_get_device_and_user_codes
179 except OAuth2DeviceCodeError
as err:
180 _LOGGER.debug(
"OAuth2DeviceCodeError error: %s", err)
182 if "Error: invalid_client" in str(err):
185 return DeviceFlow(hass, oauth_flow, device_flow_info)
189 """Authentication implementation for google calendar api library."""
193 websession: aiohttp.ClientSession,
194 session: config_entry_oauth2_flow.OAuth2Session,
196 """Init the Google Calendar client library auth implementation."""
201 """Return a valid access token."""
202 await self.
_session_session.async_ensure_token_valid()
203 return cast(str, self.
_session_session.token[
"access_token"])
207 """Authentication implementation used during config flow, without refresh.
209 This exists to allow the config flow to use the API before it has fully
210 created a config entry required by OAuth2Session. This does not support
211 refreshing tokens, which is fine since it should have been just created.
216 websession: aiohttp.ClientSession,
219 """Init the Google Calendar client library auth implementation."""
224 """Return the access token."""
None __init__(self, aiohttp.ClientSession websession, str access_token)
str async_get_access_token(self)
None __init__(self, aiohttp.ClientSession websession, config_entry_oauth2_flow.OAuth2Session session)
str async_get_access_token(self)
None __init__(self, HomeAssistant hass, OAuth2WebServerFlow oauth_flow, DeviceFlowInfo device_flow_info)
None _async_poll_attempt(self, datetime.datetime now)
str verification_url(self)
Credentials|None creds(self)
None async_set_listener(self, CALLBACK_TYPE update_callback)
None _async_timeout(self, datetime.datetime now)
Credentials _exchange(self)
None async_start_exchange(self)
dict async_resolve_external_data(self, Any external_data)
FeatureAccess get_feature_access(ConfigEntry config_entry)
DeviceFlow async_create_device_flow(HomeAssistant hass, str client_id, str client_secret, FeatureAccess access)
CALLBACK_TYPE async_track_point_in_utc_time(HomeAssistant hass, HassJob[[datetime], Coroutine[Any, Any, None]|None]|Callable[[datetime], Coroutine[Any, Any, None]|None] action, datetime point_in_time)
CALLBACK_TYPE async_track_time_interval(HomeAssistant hass, Callable[[datetime], Coroutine[Any, Any, None]|None] action, timedelta interval, *str|None name=None, bool|None cancel_on_shutdown=None)