From f3ba71748b5d4ce9211adb05c493ab3613c8182c Mon Sep 17 00:00:00 2001 From: David Kendall Date: Sun, 25 Jul 2021 20:33:21 +0100 Subject: [PATCH] Feature/google calendar read only support (#52790) * feat(google): Added support for read only access in google calendar. By default the current read/write access will be given, but the user has the option to set read only access which stops the add event service from registering * fix(google): Updated documentation link * docs(google-calendar): Added style fixes * feat(calendar-google): Updated scopes to be defined on enum property. This was done as a MR suggestion to simplify the code. * feat(calendar-google):Removed constants no longer needed. * feat(calendar-google): Reduced scope check to minimum. * style: Fixed style issues --- homeassistant/components/google/__init__.py | 51 +++++++++++++++---- homeassistant/components/google/manifest.json | 2 +- 2 files changed, 42 insertions(+), 11 deletions(-) diff --git a/homeassistant/components/google/__init__.py b/homeassistant/components/google/__init__.py index b929c3c4c37..5ed7bc93b78 100644 --- a/homeassistant/components/google/__init__.py +++ b/homeassistant/components/google/__init__.py @@ -1,5 +1,6 @@ """Support for Google - Calendar Event Devices.""" from datetime import datetime, timedelta +from enum import Enum import logging import os @@ -41,6 +42,7 @@ CONF_TRACK = "track" CONF_SEARCH = "search" CONF_IGNORE_AVAILABILITY = "ignore_availability" CONF_MAX_RESULTS = "max_results" +CONF_CALENDAR_ACCESS = "calendar_access" DEFAULT_CONF_TRACK_NEW = True DEFAULT_CONF_OFFSET = "!!" @@ -70,10 +72,26 @@ SERVICE_ADD_EVENT = "add_event" DATA_INDEX = "google_calendars" YAML_DEVICES = f"{DOMAIN}_calendars.yaml" -SCOPES = "https://www.googleapis.com/auth/calendar" TOKEN_FILE = f".{DOMAIN}.token" + +class FeatureAccess(Enum): + """Class to represent different access scopes.""" + + read_only = "https://www.googleapis.com/auth/calendar.readonly" + read_write = "https://www.googleapis.com/auth/calendar" + + def __init__(self, scope: str) -> None: + """Init instance.""" + self._scope = scope + + @property + def scope(self) -> str: + """Google calendar scope for the feature.""" + return self._scope + + CONFIG_SCHEMA = vol.Schema( { DOMAIN: vol.Schema( @@ -81,6 +99,9 @@ CONFIG_SCHEMA = vol.Schema( vol.Required(CONF_CLIENT_ID): cv.string, vol.Required(CONF_CLIENT_SECRET): cv.string, vol.Optional(CONF_TRACK_NEW): cv.boolean, + vol.Optional(CONF_CALENDAR_ACCESS, default="read_write"): cv.enum( + FeatureAccess + ), } ) }, @@ -139,7 +160,7 @@ def do_authentication(hass, hass_config, config): oauth = OAuth2WebServerFlow( client_id=config[CONF_CLIENT_ID], client_secret=config[CONF_CLIENT_SECRET], - scope="https://www.googleapis.com/auth/calendar", + scope=config[CONF_CALENDAR_ACCESS].scope, redirect_uri="Home-Assistant.io", ) try: @@ -213,7 +234,7 @@ def setup(hass, config): if not os.path.isfile(token_file): do_authentication(hass, config, conf) else: - if not check_correct_scopes(token_file): + if not check_correct_scopes(token_file, conf): do_authentication(hass, config, conf) else: do_setup(hass, config, conf) @@ -221,16 +242,22 @@ def setup(hass, config): return True -def check_correct_scopes(token_file): +def check_correct_scopes(token_file, config): """Check for the correct scopes in file.""" with open(token_file) as tokenfile: - if "readonly" in tokenfile.read(): + contents = tokenfile.read() + + # Check for quoted scope as our scopes can be subsets of other scopes + target_scope = f'"{config.get(CONF_CALENDAR_ACCESS).scope}"' + if target_scope not in contents: _LOGGER.warning("Please re-authenticate with Google") return False return True -def setup_services(hass, hass_config, track_new_found_calendars, calendar_service): +def setup_services( + hass, hass_config, config, track_new_found_calendars, calendar_service +): """Set up the service listeners.""" def _found_calendar(call): @@ -312,9 +339,11 @@ def setup_services(hass, hass_config, track_new_found_calendars, calendar_servic service_data = {"calendarId": call.data[EVENT_CALENDAR_ID], "body": event} event = service.events().insert(**service_data).execute() - hass.services.register( - DOMAIN, SERVICE_ADD_EVENT, _add_event, schema=ADD_EVENT_SERVICE_SCHEMA - ) + # Only expose the add event service if we have the correct permissions + if config.get(CONF_CALENDAR_ACCESS) is FeatureAccess.read_write: + hass.services.register( + DOMAIN, SERVICE_ADD_EVENT, _add_event, schema=ADD_EVENT_SERVICE_SCHEMA + ) return True @@ -327,7 +356,9 @@ def do_setup(hass, hass_config, config): track_new_found_calendars = convert( config.get(CONF_TRACK_NEW), bool, DEFAULT_CONF_TRACK_NEW ) - setup_services(hass, hass_config, track_new_found_calendars, calendar_service) + setup_services( + hass, hass_config, config, track_new_found_calendars, calendar_service + ) for calendar in hass.data[DATA_INDEX].values(): discovery.load_platform(hass, "calendar", DOMAIN, calendar, hass_config) diff --git a/homeassistant/components/google/manifest.json b/homeassistant/components/google/manifest.json index 9b6f7d77f26..e96cf4ec0c6 100644 --- a/homeassistant/components/google/manifest.json +++ b/homeassistant/components/google/manifest.json @@ -1,7 +1,7 @@ { "domain": "google", "name": "Google Calendars", - "documentation": "https://www.home-assistant.io/integrations/google", + "documentation": "https://www.home-assistant.io/integrations/calendar.google/", "requirements": [ "google-api-python-client==1.6.4", "httplib2==0.19.0",