Feature/google calendar read only support ()

* 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
This commit is contained in:
David Kendall 2021-07-25 20:33:21 +01:00 committed by GitHub
parent 5e6853b9e1
commit f3ba71748b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 42 additions and 11 deletions
homeassistant/components/google

View file

@ -1,5 +1,6 @@
"""Support for Google - Calendar Event Devices.""" """Support for Google - Calendar Event Devices."""
from datetime import datetime, timedelta from datetime import datetime, timedelta
from enum import Enum
import logging import logging
import os import os
@ -41,6 +42,7 @@ CONF_TRACK = "track"
CONF_SEARCH = "search" CONF_SEARCH = "search"
CONF_IGNORE_AVAILABILITY = "ignore_availability" CONF_IGNORE_AVAILABILITY = "ignore_availability"
CONF_MAX_RESULTS = "max_results" CONF_MAX_RESULTS = "max_results"
CONF_CALENDAR_ACCESS = "calendar_access"
DEFAULT_CONF_TRACK_NEW = True DEFAULT_CONF_TRACK_NEW = True
DEFAULT_CONF_OFFSET = "!!" DEFAULT_CONF_OFFSET = "!!"
@ -70,10 +72,26 @@ SERVICE_ADD_EVENT = "add_event"
DATA_INDEX = "google_calendars" DATA_INDEX = "google_calendars"
YAML_DEVICES = f"{DOMAIN}_calendars.yaml" YAML_DEVICES = f"{DOMAIN}_calendars.yaml"
SCOPES = "https://www.googleapis.com/auth/calendar"
TOKEN_FILE = f".{DOMAIN}.token" 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( CONFIG_SCHEMA = vol.Schema(
{ {
DOMAIN: vol.Schema( DOMAIN: vol.Schema(
@ -81,6 +99,9 @@ CONFIG_SCHEMA = vol.Schema(
vol.Required(CONF_CLIENT_ID): cv.string, vol.Required(CONF_CLIENT_ID): cv.string,
vol.Required(CONF_CLIENT_SECRET): cv.string, vol.Required(CONF_CLIENT_SECRET): cv.string,
vol.Optional(CONF_TRACK_NEW): cv.boolean, 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( oauth = OAuth2WebServerFlow(
client_id=config[CONF_CLIENT_ID], client_id=config[CONF_CLIENT_ID],
client_secret=config[CONF_CLIENT_SECRET], client_secret=config[CONF_CLIENT_SECRET],
scope="https://www.googleapis.com/auth/calendar", scope=config[CONF_CALENDAR_ACCESS].scope,
redirect_uri="Home-Assistant.io", redirect_uri="Home-Assistant.io",
) )
try: try:
@ -213,7 +234,7 @@ def setup(hass, config):
if not os.path.isfile(token_file): if not os.path.isfile(token_file):
do_authentication(hass, config, conf) do_authentication(hass, config, conf)
else: else:
if not check_correct_scopes(token_file): if not check_correct_scopes(token_file, conf):
do_authentication(hass, config, conf) do_authentication(hass, config, conf)
else: else:
do_setup(hass, config, conf) do_setup(hass, config, conf)
@ -221,16 +242,22 @@ def setup(hass, config):
return True return True
def check_correct_scopes(token_file): def check_correct_scopes(token_file, config):
"""Check for the correct scopes in file.""" """Check for the correct scopes in file."""
with open(token_file) as tokenfile: 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") _LOGGER.warning("Please re-authenticate with Google")
return False return False
return True 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.""" """Set up the service listeners."""
def _found_calendar(call): def _found_calendar(call):
@ -312,6 +339,8 @@ def setup_services(hass, hass_config, track_new_found_calendars, calendar_servic
service_data = {"calendarId": call.data[EVENT_CALENDAR_ID], "body": event} service_data = {"calendarId": call.data[EVENT_CALENDAR_ID], "body": event}
event = service.events().insert(**service_data).execute() event = service.events().insert(**service_data).execute()
# 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( hass.services.register(
DOMAIN, SERVICE_ADD_EVENT, _add_event, schema=ADD_EVENT_SERVICE_SCHEMA DOMAIN, SERVICE_ADD_EVENT, _add_event, schema=ADD_EVENT_SERVICE_SCHEMA
) )
@ -327,7 +356,9 @@ def do_setup(hass, hass_config, config):
track_new_found_calendars = convert( track_new_found_calendars = convert(
config.get(CONF_TRACK_NEW), bool, DEFAULT_CONF_TRACK_NEW 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(): for calendar in hass.data[DATA_INDEX].values():
discovery.load_platform(hass, "calendar", DOMAIN, calendar, hass_config) discovery.load_platform(hass, "calendar", DOMAIN, calendar, hass_config)

View file

@ -1,7 +1,7 @@
{ {
"domain": "google", "domain": "google",
"name": "Google Calendars", "name": "Google Calendars",
"documentation": "https://www.home-assistant.io/integrations/google", "documentation": "https://www.home-assistant.io/integrations/calendar.google/",
"requirements": [ "requirements": [
"google-api-python-client==1.6.4", "google-api-python-client==1.6.4",
"httplib2==0.19.0", "httplib2==0.19.0",