Skip to content

Commit

Permalink
Initial change to permissions
Browse files Browse the repository at this point in the history
  • Loading branch information
RogerSelwyn committed Feb 28, 2022
1 parent becf845 commit 0d3eeb0
Show file tree
Hide file tree
Showing 5 changed files with 243 additions and 58 deletions.
61 changes: 43 additions & 18 deletions custom_components/o365/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,22 +10,40 @@
from homeassistant.helpers.network import get_url
from O365 import Account, FileSystemTokenBackend

from .const import (AUTH_CALLBACK_NAME, AUTH_CALLBACK_PATH,
AUTH_CALLBACK_PATH_ALT, CONF_ALT_CONFIG, CONF_CALENDARS,
CONF_CLIENT_ID, CONF_CLIENT_SECRET, CONF_EMAIL_SENSORS,
CONF_STATUS_SENSORS, CONF_QUERY_SENSORS, CONF_TRACK_NEW,
CONFIG_SCHEMA, CONFIGURATOR_DESCRIPTION,
CONFIGURATOR_LINK_NAME, CONFIGURATOR_SUBMIT_CAPTION,
DEFAULT_CACHE_PATH, DEFAULT_NAME, DOMAIN, SCOPE,
TOKEN_FILENAME)
from .utils import build_config_file_path, validate_permissions
from .const import (
AUTH_CALLBACK_NAME,
AUTH_CALLBACK_PATH,
AUTH_CALLBACK_PATH_ALT,
CONF_ALT_CONFIG,
CONF_CALENDARS,
CONF_CLIENT_ID,
CONF_CLIENT_SECRET,
CONF_EMAIL_SENSORS,
CONF_QUERY_SENSORS,
CONF_STATUS_SENSORS,
CONF_TRACK_NEW,
CONFIG_SCHEMA,
CONFIGURATOR_DESCRIPTION,
CONFIGURATOR_LINK_NAME,
CONFIGURATOR_SUBMIT_CAPTION,
DEFAULT_CACHE_PATH,
DEFAULT_NAME,
DOMAIN,
TOKEN_FILENAME,
)
from .utils import (
build_config_file_path,
build_minimum_permissions,
build_requested_permissions,
validate_permissions,
)

_LOGGER = logging.getLogger(__name__)


async def async_setup(hass, config):
"""Set up the O365 platform."""
validate_permissions(hass)
# validate_permissions(hass)
conf = config.get(DOMAIN, {})
CONFIG_SCHEMA(conf)
credentials = (conf.get(CONF_CLIENT_ID), conf.get(CONF_CLIENT_SECRET))
Expand All @@ -41,12 +59,14 @@ async def async_setup(hass, config):

account = Account(credentials, token_backend=token_backend, timezone="UTC")
is_authenticated = account.is_authenticated
permissions = validate_permissions(hass)
minimum_permissions = build_minimum_permissions(conf)
permissions = validate_permissions(hass, minimum_permissions)
if is_authenticated and permissions:
do_setup(hass, conf, account)
else:
scope = build_requested_permissions(conf)
url, state = account.con.get_authorization_url(
requested_scopes=SCOPE, redirect_uri=callback_url
requested_scopes=scope, redirect_uri=callback_url
)
_LOGGER.info("no token; requesting authorization")
callback_view = O365AuthCallbackView(
Expand Down Expand Up @@ -75,11 +95,15 @@ def do_setup(hass, config, account):
"and will be removed in a future release. Please see the docs for how to proceed",
FutureWarning,
)
email_sensors = config.get(CONF_EMAIL_SENSORS, [])
query_sensors = config.get(CONF_QUERY_SENSORS, [])
status_sensors = config.get(CONF_STATUS_SENSORS, [])

hass.data[DOMAIN] = {
"account": account,
CONF_EMAIL_SENSORS: config.get(CONF_EMAIL_SENSORS, []),
CONF_QUERY_SENSORS: config.get(CONF_QUERY_SENSORS, []),
CONF_STATUS_SENSORS: config.get(CONF_STATUS_SENSORS, []),
CONF_EMAIL_SENSORS: email_sensors,
CONF_QUERY_SENSORS: query_sensors,
CONF_STATUS_SENSORS: status_sensors,
CONF_TRACK_NEW: config.get(CONF_TRACK_NEW, True),
}
hass.async_create_task(
Expand All @@ -88,9 +112,10 @@ def do_setup(hass, config, account):
hass.async_create_task(
discovery.async_load_platform(hass, "notify", DOMAIN, {}, config)
)
hass.async_create_task(
discovery.async_load_platform(hass, "sensor", DOMAIN, {}, config)
)
if len(email_sensors) > 0 or len(query_sensors) > 0 or len(status_sensors) > 0:
hass.async_create_task(
discovery.async_load_platform(hass, "sensor", DOMAIN, {}, config)
)


def request_configuration(hass, url, callback_view):
Expand Down
75 changes: 62 additions & 13 deletions custom_components/o365/calendar.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,46 @@
from datetime import datetime, timedelta
from operator import attrgetter, itemgetter

from homeassistant.components.calendar import (CalendarEventDevice,
calculate_offset,
is_offset_reached)
from homeassistant.components.calendar import (
CalendarEventDevice,
calculate_offset,
is_offset_reached,
)
from homeassistant.helpers.entity import generate_entity_id
from homeassistant.util import Throttle, dt

from .const import (CALENDAR_ENTITY_ID_FORMAT, CALENDAR_SERVICE_CREATE_SCHEMA,
CALENDAR_SERVICE_MODIFY_SCHEMA,
CALENDAR_SERVICE_REMOVE_SCHEMA,
CALENDAR_SERVICE_RESPOND_SCHEMA, CONF_DEVICE_ID,
CONF_ENTITIES, CONF_HOURS_BACKWARD_TO_GET,
CONF_HOURS_FORWARD_TO_GET, CONF_MAX_RESULTS, CONF_NAME,
CONF_SEARCH, CONF_TRACK, CONF_TRACK_NEW, DEFAULT_OFFSET,
DOMAIN, MIN_TIME_BETWEEN_UPDATES, YAML_CALENDARS)
from .utils import (add_call_data_to_event, build_config_file_path, clean_html,
format_event_data, load_calendars, update_calendar_file)
from .const import (
CALENDAR_ENTITY_ID_FORMAT,
CALENDAR_SERVICE_CREATE_SCHEMA,
CALENDAR_SERVICE_MODIFY_SCHEMA,
CALENDAR_SERVICE_REMOVE_SCHEMA,
CALENDAR_SERVICE_RESPOND_SCHEMA,
CONF_DEVICE_ID,
CONF_ENTITIES,
CONF_HOURS_BACKWARD_TO_GET,
CONF_HOURS_FORWARD_TO_GET,
CONF_MAX_RESULTS,
CONF_NAME,
CONF_SEARCH,
CONF_TRACK,
CONF_TRACK_NEW,
DEFAULT_OFFSET,
DOMAIN,
MIN_TIME_BETWEEN_UPDATES,
PERM_CALENDARS_READWRITE,
PERM_MINIMUM_CALENDAR_WRITE,
YAML_CALENDARS,
)
from .utils import (
add_call_data_to_event,
build_config_file_path,
clean_html,
format_event_data,
get_permissions,
load_calendars,
update_calendar_file,
validate_minimum_permission,
)

_LOGGER = logging.getLogger(__name__)

Expand Down Expand Up @@ -291,9 +315,13 @@ def __init__(self, account, track_new_found_calendars, hass):
self.schedule = self.account.schedule()
self.track_new_found_calendars = track_new_found_calendars
self._hass = hass
self._permissions = get_permissions(self._hass)

def modify_calendar_event(self, call):
"""Modify the event."""
if not self._validate_permissions("modify"):
return

event_data = call.data
CALENDAR_SERVICE_MODIFY_SCHEMA(dict(event_data.items()))
calendar = self.schedule.get_calendar(calendar_id=event_data.get("calendar_id"))
Expand All @@ -303,6 +331,9 @@ def modify_calendar_event(self, call):

def create_calendar_event(self, call):
"""Create the event."""
if not self._validate_permissions("create"):
return

event_data = call.data
CALENDAR_SERVICE_CREATE_SCHEMA(dict(event_data.items()))
calendar = self.schedule.get_calendar(calendar_id=event_data.get("calendar_id"))
Expand All @@ -312,6 +343,9 @@ def create_calendar_event(self, call):

def remove_calendar_event(self, call):
"""Remove the event."""
if not self._validate_permissions("delete"):
return

event_data = call.data
CALENDAR_SERVICE_REMOVE_SCHEMA(dict(event_data.items()))
calendar = self.schedule.get_calendar(calendar_id=event_data.get("calendar_id"))
Expand All @@ -320,6 +354,9 @@ def remove_calendar_event(self, call):

def respond_calendar_event(self, call):
"""Respond to calendar event."""
if not self._validate_permissions("respond to"):
return

event_data = call.data
CALENDAR_SERVICE_RESPOND_SCHEMA(dict(event_data.items()))
calendar = self.schedule.get_calendar(calendar_id=event_data.get("calendar_id"))
Expand Down Expand Up @@ -350,3 +387,15 @@ def scan_for_calendars(self, call): # pylint: disable=unused-argument
for calendar in calendars:
track = self.track_new_found_calendars
update_calendar_file(YAML_CALENDARS, calendar, self._hass, track)

def _validate_permissions(self, error_message):
if not validate_minimum_permission(
PERM_MINIMUM_CALENDAR_WRITE, self._permissions
):
_LOGGER.error(
"Not authorisied to %s calendar event - requires permission: %s",
error_message,
PERM_CALENDARS_READWRITE,
)
return False
return True
58 changes: 42 additions & 16 deletions custom_components/o365/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,12 @@

import homeassistant.helpers.config_validation as cv
import voluptuous as vol
from homeassistant.components.notify import (ATTR_DATA, ATTR_MESSAGE,
ATTR_TARGET, ATTR_TITLE)
from homeassistant.components.notify import (
ATTR_DATA,
ATTR_MESSAGE,
ATTR_TARGET,
ATTR_TITLE,
)
from homeassistant.config import get_default_config_dir
from homeassistant.const import CONF_NAME
from O365.calendar import AttendeeType # pylint: disable=no-name-in-module
Expand Down Expand Up @@ -94,22 +98,44 @@ class EventResponse(Enum):
ICON = "mdi:office"
MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=15)
DEFAULT_OFFSET = "!!"
SCOPE = [
"offline_access",
"User.Read",
"Calendars.ReadWrite",
"Calendars.ReadWrite.Shared",
"Mail.ReadWrite",
"Mail.ReadWrite.Shared",
"Mail.Send",
"Mail.Send.Shared",
PERM_CALENDARS_READ = "Calendars.Read"
PERM_CALENDARS_READ_SHARED = "Calendars.Read.Shared"
PERM_CALENDARS_READWRITE = "Calendars.ReadWrite"
PERM_CALENDARS_READWRITE_SHARED = "Calendars.ReadWrite.Shared"
PERM_MAIL_READ = "Mail.Read"
PERM_MAIL_READ_SHARED = "Mail.Read.Shared"
PERM_MAIL_READWRITE = "Mail.ReadWrite"
PERM_MAIL_READWRITE_SHARED = "Mail.ReadWrite.Shared"
PERM_MAIL_SEND = "Mail.Send"
PERM_MAIL_SEND_SHARED = "Mail.Send.Shared"
PERM_OFFLINE_ACCESS = "offline_access"
PERM_PRESENCE_READ = "Presence.Read"
PERM_USER_READ = "User.Read"
PERM_MINIMUM_PRESENCE = [PERM_PRESENCE_READ, []]
PERM_MINIMUM_USER = [PERM_USER_READ, []]
PERM_MINIMUM_MAIL = [
PERM_MAIL_READ,
[PERM_MAIL_READ_SHARED, PERM_MAIL_READWRITE, PERM_MAIL_READWRITE_SHARED],
]
PERM_MINIMUM_CALENDAR = [
PERM_CALENDARS_READ,
[
PERM_CALENDARS_READ_SHARED,
PERM_CALENDARS_READWRITE,
PERM_CALENDARS_READWRITE_SHARED,
],
]
MINIMUM_REQUIRED_SCOPES = [
"User.Read",
"Calendars.ReadWrite",
"Mail.ReadWrite",
"Mail.Send",
PERM_MINIMUM_CALENDAR_WRITE = [
PERM_CALENDARS_READWRITE,
[
PERM_CALENDARS_READWRITE_SHARED,
],
]
PERM_MINIMUM_SEND = [
PERM_MAIL_SEND,
[PERM_MAIL_SEND_SHARED],
]

YAML_CALENDARS = f"{DOMAIN}_calendars.yaml"

CALENDAR_SCHEMA = vol.Schema(
Expand Down
20 changes: 17 additions & 3 deletions custom_components/o365/notify.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,15 @@
ATTR_ZIP_NAME,
DOMAIN,
NOTIFY_BASE_SCHEMA,
PERM_MAIL_SEND,
PERM_MINIMUM_SEND,
)
from .utils import (
get_ha_filepath,
get_permissions,
validate_minimum_permission,
zip_files,
)
from .utils import get_ha_filepath, zip_files

_LOGGER = logging.getLogger(__name__)

Expand All @@ -31,15 +38,16 @@ async def async_get_service(
is_authenticated = account.is_authenticated
if not is_authenticated:
return
return O365EmailService(account)
return O365EmailService(account, hass)


class O365EmailService(BaseNotificationService):
"""Implement the notification service for O365."""

def __init__(self, account):
def __init__(self, account, hass):
"""Initialize the service."""
self.account = account
self._permissions = get_permissions(hass)

@property
def targets(self):
Expand All @@ -48,6 +56,12 @@ def targets(self):

def send_message(self, message="", **kwargs):
"""Send a message to a user."""
if not validate_minimum_permission(PERM_MINIMUM_SEND, self._permissions):
_LOGGER.error(
"Not authorisied to send mail - requires permission: %s", PERM_MAIL_SEND
)
return

cleanup_files = []
account = self.account
NOTIFY_BASE_SCHEMA(kwargs)
Expand Down
Loading

0 comments on commit 0d3eeb0

Please sign in to comment.