From 4ea66239aee9a9752741a1d28c0323a9101a0b70 Mon Sep 17 00:00:00 2001 From: BottlecapDave Date: Sat, 6 Feb 2021 14:27:32 +0000 Subject: [PATCH 1/7] Added support for turning on/off calendar features which adjust required scopes --- custom_components/o365/__init__.py | 12 ++++++---- custom_components/o365/calendar.py | 38 ++++++++++++++++++------------ custom_components/o365/const.py | 29 +++++++++++++++++------ custom_components/o365/notify.py | 4 ++++ custom_components/o365/utils.py | 32 +++++++++++++++++++++---- 5 files changed, 84 insertions(+), 31 deletions(-) diff --git a/custom_components/o365/__init__.py b/custom_components/o365/__init__.py index f3d490d..97be8f6 100644 --- a/custom_components/o365/__init__.py +++ b/custom_components/o365/__init__.py @@ -16,7 +16,6 @@ AUTH_CALLBACK_PATH, AUTH_CALLBACK_PATH_ALT, TOKEN_BACKEND, - SCOPE, CONF_CALENDARS, DEFAULT_NAME, CONFIGURATOR_LINK_NAME, @@ -29,14 +28,16 @@ CONF_TRACK_NEW, ) -from .utils import validate_permissions +from .utils import ( + validate_permissions, + get_scopes +) _LOGGER = logging.getLogger(__name__) def setup(hass, config): """Set up the O365 platform.""" - validate_permissions() conf = config.get(DOMAIN, {}) CONFIG_SCHEMA(conf) credentials = (conf.get(CONF_CLIENT_ID), conf.get(CONF_CLIENT_SECRET)) @@ -51,10 +52,11 @@ def setup(hass, config): account = Account(credentials, token_backend=TOKEN_BACKEND) is_authenticated = account.is_authenticated - permissions = validate_permissions() + scopes = get_scopes(conf) + permissions = validate_permissions(scopes) if not is_authenticated or not permissions: url, state = account.con.get_authorization_url( - requested_scopes=SCOPE, redirect_uri=callback_url + requested_scopes=scopes, redirect_uri=callback_url ) _LOGGER.info("no token; requesting authorization") callback_view = O365AuthCallbackView( diff --git a/custom_components/o365/calendar.py b/custom_components/o365/calendar.py index aab0348..e2af05e 100644 --- a/custom_components/o365/calendar.py +++ b/custom_components/o365/calendar.py @@ -28,6 +28,8 @@ CONF_MAX_RESULTS, CALENDAR_ENTITY_ID_FORMAT, CONF_TRACK_NEW, + CONF_CALENDAR_READ, + CONF_CALENDAR_WRITE ) from .utils import ( clean_html, @@ -51,6 +53,10 @@ def setup_platform(hass, config, add_devices, discovery_info=None): if not is_authenticated: return False + conf = config.get(DOMAIN, {}) + if ((not conf.get(CONF_CALENDAR_READ, True)) and (not conf.get(CONF_CALENDAR_WRITE, True))): + return False + calendar_services = CalendarServices(account, track_new, hass) calendar_services.scan_for_calendars(None) @@ -68,21 +74,23 @@ def setup_platform(hass, config, add_devices, discovery_info=None): devices.append(cal) add_devices(devices, True) - hass.services.register( - DOMAIN, "modify_calendar_event", calendar_services.modify_calendar_event - ) - hass.services.register( - DOMAIN, "create_calendar_event", calendar_services.create_calendar_event - ) - hass.services.register( - DOMAIN, "remove_calendar_event", calendar_services.remove_calendar_event - ) - hass.services.register( - DOMAIN, "respond_calendar_event", calendar_services.respond_calendar_event - ) - hass.services.register( - DOMAIN, "scan_for_calendars", calendar_services.scan_for_calendars - ) + if not conf.get(CONF_CALENDAR_WRITE, True): + hass.services.register( + DOMAIN, "modify_calendar_event", calendar_services.modify_calendar_event + ) + hass.services.register( + DOMAIN, "create_calendar_event", calendar_services.create_calendar_event + ) + hass.services.register( + DOMAIN, "remove_calendar_event", calendar_services.remove_calendar_event + ) + hass.services.register( + DOMAIN, "respond_calendar_event", calendar_services.respond_calendar_event + ) + if not conf.get(CONF_CALENDAR_READ, True): + hass.services.register( + DOMAIN, "scan_for_calendars", calendar_services.scan_for_calendars + ) return True diff --git a/custom_components/o365/const.py b/custom_components/o365/const.py index 2d16812..ce9d7a7 100644 --- a/custom_components/o365/const.py +++ b/custom_components/o365/const.py @@ -76,6 +76,10 @@ class EventResponse(Enum): CONF_SUBJECT_CONTAINS = "subject_contains" CONF_SUBJECT_IS = "subject_is" CONF_TRACK_NEW = "track_new_calendar" +CONF_CALENDAR_READ = "calendar_read" +CONF_CALENDAR_WRITE = "calendar_write" +CONF_EMAIL_READ = "email_read" +CONF_EMAIL_WRITE = "email_write" CONFIG_BASE_DIR = get_default_config_dir() CONFIGURATOR_DESCRIPTION = ( "To link your O365 account, click the link, login, and authorize:" @@ -91,22 +95,28 @@ class EventResponse(Enum): ICON = "mdi:office" MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=15) DEFAULT_OFFSET = "!!" -SCOPE = [ +BASE_SCOPES = [ "offline_access", "User.Read", +] +CALENDAR_READ_SCOPES = [ + "Calendars.Read", + "Calendars.Read.Shared", +] +CALENDAR_READ_WRITE_SCOPES = [ "Calendars.ReadWrite", "Calendars.ReadWrite.Shared", +] +EMAIL_READ_SCOPES = [ + "Mail.Read", + "Mail.Read.Shared", +] +EMAIL_READ_WRITE_SCOPES = [ "Mail.ReadWrite", "Mail.ReadWrite.Shared", "Mail.Send", "Mail.Send.Shared", ] -MINIMUM_REQUIRED_SCOPES = [ - "User.Read", - "Calendars.ReadWrite", - "Mail.ReadWrite", - "Mail.Send", -] TOKEN_BACKEND = FileSystemTokenBackend( token_path=DEFAULT_CACHE_PATH, token_filename="o365.token" ) @@ -151,6 +161,11 @@ class EventResponse(Enum): vol.Optional(CONF_CALENDARS, default=[]): [CALENDAR_SCHEMA], vol.Optional(CONF_EMAIL_SENSORS): [EMAIL_SENSOR], vol.Optional(CONF_QUERY_SENSORS): [QUERY_SENSOR], + + vol.Optional(CONF_CALENDAR_READ, default=True): bool, + vol.Optional(CONF_CALENDAR_WRITE, default=True): bool, + vol.Optional(CONF_EMAIL_READ, default=True): bool, + vol.Optional(CONF_EMAIL_WRITE, default=True): bool, }, ) }, diff --git a/custom_components/o365/notify.py b/custom_components/o365/notify.py index 0299862..7629b84 100644 --- a/custom_components/o365/notify.py +++ b/custom_components/o365/notify.py @@ -13,6 +13,7 @@ ATTR_ZIP_ATTACHMENTS, ATTR_ZIP_NAME, NOTIFY_BASE_SCHEMA, + CONF_EMAIL_WRITE, ) _LOGGER = logging.getLogger(__name__) @@ -25,6 +26,9 @@ async def async_get_service(hass, config, discovery_info=None): is_authenticated = account.is_authenticated if not is_authenticated: return + conf = config.get(DOMAIN, {}) + if not conf.get(CONF_EMAIL_WRITE, True): + return email_service = O365EmailService(account) return email_service diff --git a/custom_components/o365/utils.py b/custom_components/o365/utils.py index e68bd29..e8d337e 100644 --- a/custom_components/o365/utils.py +++ b/custom_components/o365/utils.py @@ -6,7 +6,11 @@ from bs4 import BeautifulSoup from .const import ( DEFAULT_CACHE_PATH, - MINIMUM_REQUIRED_SCOPES, + BASE_SCOPES, + CALENDAR_READ_SCOPES, + CALENDAR_READ_WRITE_SCOPES, + EMAIL_READ_SCOPES, + EMAIL_READ_WRITE_SCOPES, CONFIG_BASE_DIR, DATETIME_FORMAT, CALENDAR_DEVICE_SCHEMA, @@ -15,6 +19,10 @@ CONF_TRACK, CONF_NAME, CONF_DEVICE_ID, + CONF_CALENDAR_READ, + CONF_CALENDAR_WRITE, + CONF_EMAIL_READ, + CONF_EMAIL_WRITE ) from O365.calendar import Attendee from homeassistant.util import dt @@ -34,8 +42,25 @@ def clean_html(html): else: return html +def get_scopes(conf): + scopes = [x for x in BASE_SCOPES] -def validate_permissions(token_path=DEFAULT_CACHE_PATH, token_filename="o365.token"): + if conf.get(CONF_CALENDAR_WRITE, True): + scopes = scopes + CALENDAR_READ_WRITE_SCOPES + elif conf.get(CONF_CALENDAR_READ, True): + scopes = scopes + CALENDAR_READ_SCOPES + + if conf.get(CONF_EMAIL_WRITE, True): + scopes = scopes + EMAIL_READ_WRITE_SCOPES + elif conf.get(CONF_EMAIL_READ, True): + scopes = scopes + EMAIL_READ_SCOPES + + _LOGGER.error(f"Required scopes: {scopes}") + + return scopes + + +def validate_permissions(scopes, token_path=DEFAULT_CACHE_PATH, token_filename="o365.token"): full_token_path = os.path.join(token_path, token_filename) if not os.path.exists(full_token_path) or not os.path.isfile(full_token_path): _LOGGER.warning(f"Could not loacte token at {full_token_path}") @@ -43,8 +68,7 @@ def validate_permissions(token_path=DEFAULT_CACHE_PATH, token_filename="o365.tok with open(full_token_path, "r", encoding="UTF-8") as fh: raw = fh.read() permissions = json.loads(raw)["scope"] - scope = [x for x in MINIMUM_REQUIRED_SCOPES] - all_permissions_granted = all([x in permissions for x in scope]) + all_permissions_granted = all([x in permissions for x in scopes]) if not all_permissions_granted: _LOGGER.warning(f"All permissions granted: {all_permissions_granted}") return all_permissions_granted From 1d33611a95b68a68963dfd71a942b82724a33365 Mon Sep 17 00:00:00 2001 From: BottlecapDave Date: Sat, 6 Feb 2021 15:15:35 +0000 Subject: [PATCH 2/7] Updated feature access configuration to more closely match the underlying scopes. --- custom_components/o365/calendar.py | 10 +++++----- custom_components/o365/const.py | 19 +++++++++++-------- custom_components/o365/notify.py | 5 +++-- custom_components/o365/utils.py | 17 ++++++++--------- 4 files changed, 27 insertions(+), 24 deletions(-) diff --git a/custom_components/o365/calendar.py b/custom_components/o365/calendar.py index e2af05e..b8b289c 100644 --- a/custom_components/o365/calendar.py +++ b/custom_components/o365/calendar.py @@ -28,8 +28,8 @@ CONF_MAX_RESULTS, CALENDAR_ENTITY_ID_FORMAT, CONF_TRACK_NEW, - CONF_CALENDAR_READ, - CONF_CALENDAR_WRITE + CONF_CALENDAR_ACCESS, + FeatureAccess ) from .utils import ( clean_html, @@ -54,7 +54,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): return False conf = config.get(DOMAIN, {}) - if ((not conf.get(CONF_CALENDAR_READ, True)) and (not conf.get(CONF_CALENDAR_WRITE, True))): + if conf.get(CONF_CALENDAR_ACCESS) is not FeatureAccess.Disabled: return False calendar_services = CalendarServices(account, track_new, hass) @@ -74,7 +74,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): devices.append(cal) add_devices(devices, True) - if not conf.get(CONF_CALENDAR_WRITE, True): + if conf.get(CONF_CALENDAR_ACCESS) is FeatureAccess.ReadWrite: hass.services.register( DOMAIN, "modify_calendar_event", calendar_services.modify_calendar_event ) @@ -87,7 +87,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): hass.services.register( DOMAIN, "respond_calendar_event", calendar_services.respond_calendar_event ) - if not conf.get(CONF_CALENDAR_READ, True): + if conf.get(CONF_CALENDAR_ACCESS) is not FeatureAccess.Disabled: hass.services.register( DOMAIN, "scan_for_calendars", calendar_services.scan_for_calendars ) diff --git a/custom_components/o365/const.py b/custom_components/o365/const.py index ce9d7a7..885d542 100644 --- a/custom_components/o365/const.py +++ b/custom_components/o365/const.py @@ -21,6 +21,11 @@ class EventResponse(Enum): Tentative = "tentative" Decline = "decline" +class FeatureAccess(Enum): + Disabled = "disabled" + Read = "read" + ReadWrite = "readwrite" + ATTR_ATTACHMENTS = "attachments" ATTR_ATTENDEES = "attendees" @@ -76,10 +81,10 @@ class EventResponse(Enum): CONF_SUBJECT_CONTAINS = "subject_contains" CONF_SUBJECT_IS = "subject_is" CONF_TRACK_NEW = "track_new_calendar" -CONF_CALENDAR_READ = "calendar_read" -CONF_CALENDAR_WRITE = "calendar_write" -CONF_EMAIL_READ = "email_read" -CONF_EMAIL_WRITE = "email_write" + +CONF_CALENDAR_ACCESS = "calendar_access" +CONF_EMAIL_ACCESS = "email_access" + CONFIG_BASE_DIR = get_default_config_dir() CONFIGURATOR_DESCRIPTION = ( "To link your O365 account, click the link, login, and authorize:" @@ -162,10 +167,8 @@ class EventResponse(Enum): vol.Optional(CONF_EMAIL_SENSORS): [EMAIL_SENSOR], vol.Optional(CONF_QUERY_SENSORS): [QUERY_SENSOR], - vol.Optional(CONF_CALENDAR_READ, default=True): bool, - vol.Optional(CONF_CALENDAR_WRITE, default=True): bool, - vol.Optional(CONF_EMAIL_READ, default=True): bool, - vol.Optional(CONF_EMAIL_WRITE, default=True): bool, + vol.Optional(CONF_CALENDAR_ACCESS, default='ReadWrite'): cv.enum(FeatureAccess), + vol.Optional(CONF_EMAIL_ACCESS, default='ReadWrite'): cv.enum(FeatureAccess) }, ) }, diff --git a/custom_components/o365/notify.py b/custom_components/o365/notify.py index 7629b84..1c6a021 100644 --- a/custom_components/o365/notify.py +++ b/custom_components/o365/notify.py @@ -13,7 +13,8 @@ ATTR_ZIP_ATTACHMENTS, ATTR_ZIP_NAME, NOTIFY_BASE_SCHEMA, - CONF_EMAIL_WRITE, + CONF_EMAIL_ACCESS, + FeatureAccess, ) _LOGGER = logging.getLogger(__name__) @@ -27,7 +28,7 @@ async def async_get_service(hass, config, discovery_info=None): if not is_authenticated: return conf = config.get(DOMAIN, {}) - if not conf.get(CONF_EMAIL_WRITE, True): + if conf.get(CONF_EMAIL_ACCESS) is not FeatureAccess.ReadWrite: return email_service = O365EmailService(account) return email_service diff --git a/custom_components/o365/utils.py b/custom_components/o365/utils.py index e8d337e..796fb3b 100644 --- a/custom_components/o365/utils.py +++ b/custom_components/o365/utils.py @@ -19,10 +19,9 @@ CONF_TRACK, CONF_NAME, CONF_DEVICE_ID, - CONF_CALENDAR_READ, - CONF_CALENDAR_WRITE, - CONF_EMAIL_READ, - CONF_EMAIL_WRITE + CONF_CALENDAR_ACCESS, + CONF_EMAIL_ACCESS, + FeatureAccess, ) from O365.calendar import Attendee from homeassistant.util import dt @@ -45,17 +44,17 @@ def clean_html(html): def get_scopes(conf): scopes = [x for x in BASE_SCOPES] - if conf.get(CONF_CALENDAR_WRITE, True): + if conf.get(CONF_CALENDAR_ACCESS) is FeatureAccess.ReadWrite: scopes = scopes + CALENDAR_READ_WRITE_SCOPES - elif conf.get(CONF_CALENDAR_READ, True): + elif conf.get(CONF_CALENDAR_ACCESS) is FeatureAccess.Read: scopes = scopes + CALENDAR_READ_SCOPES - if conf.get(CONF_EMAIL_WRITE, True): + if conf.get(CONF_EMAIL_ACCESS) is FeatureAccess.ReadWrite: scopes = scopes + EMAIL_READ_WRITE_SCOPES - elif conf.get(CONF_EMAIL_READ, True): + elif conf.get(CONF_EMAIL_ACCESS) is FeatureAccess.Read: scopes = scopes + EMAIL_READ_SCOPES - _LOGGER.error(f"Required scopes: {scopes}") + _LOGGER.warning(f"Required scopes: {scopes}") return scopes From 1211633c769fb6fb3ac2360d0c3fd39973995f66 Mon Sep 17 00:00:00 2001 From: BottlecapDave Date: Sat, 6 Feb 2021 15:41:02 +0000 Subject: [PATCH 3/7] Updated readme and fixed issue wth calendar not initialising correctly --- README.md | 19 ++++++++++++++++--- custom_components/o365/calendar.py | 10 +++++----- custom_components/o365/utils.py | 2 +- 3 files changed, 22 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 7d08b68..694b3d8 100644 --- a/README.md +++ b/README.md @@ -30,16 +30,27 @@ Write down the Application (client) ID. You will need this value. Under "Certificates & secrets", generate a new client secret. Set the expiration preferably to never. Write down the value of the client secret created now. It will be hidden later on. Under "Api Permissions" add the following delegated permission from the Microsoft Graph API collection -* Calendars.ReadWrite - *Read and write user calendars* -* Calendars.ReadWrite.Shared - *Read and write user and shared calendars* * offline_access - *Maintain access to data you have given it access to* * Users.Read - *Sign in and read user profile* -* email - *View users' email address* + +If `calendar_access` is equal to `ReadWrite` +* Calendars.ReadWrite - *Read and write user calendars* +* Calendars.ReadWrite.Shared - *Read and write user and shared calendars* + +If `calendar_access` is equal to `Read` +* Calendars.Read - *Read user calendars* +* Calendars.Read.Shared - *Read user and shared calendars* + +If `email_access` is equal to `ReadWrite` * Mail.ReadWrite - *Read and write access to user mail* * Mail.ReadWrite.Shared - *Read and write user and shared mail* * Mail.Send - *Send mail as a user* * Mail.Send.Shared - *Send mail on behalf of others* +If `email_access` is equal to `Read` +* Mail.Read - *Read access to user mail* +* Mail.Read.Shared - *Read user and shared mail* + ## Adding to Home Assistant ### Manual installation @@ -107,6 +118,8 @@ Key | Type | Required | Description `calendars` | `list` | `False` | List of calendar config entries `email_sensors` | `list` | `False` | List of email_sensor config entries `query_sensors` | `list` | `False` | List of query_sensor config entries +`calendar_access` | `Disabled`, `Read` or `ReadWrite` | `False` | Determines the access level for calendars. Defaults to `ReadWrite`. +`email_access` | `Disabled`, `Read` or `ReadWrite` | `False` | Determines the access level for email. Defaults to `ReadWrite`. ### email_sensors Key | Type | Required | Description diff --git a/custom_components/o365/calendar.py b/custom_components/o365/calendar.py index b8b289c..f6cd6e1 100644 --- a/custom_components/o365/calendar.py +++ b/custom_components/o365/calendar.py @@ -54,7 +54,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): return False conf = config.get(DOMAIN, {}) - if conf.get(CONF_CALENDAR_ACCESS) is not FeatureAccess.Disabled: + if conf.get(CONF_CALENDAR_ACCESS) is FeatureAccess.Disabled: return False calendar_services = CalendarServices(account, track_new, hass) @@ -74,6 +74,10 @@ def setup_platform(hass, config, add_devices, discovery_info=None): devices.append(cal) add_devices(devices, True) + hass.services.register( + DOMAIN, "scan_for_calendars", calendar_services.scan_for_calendars + ) + if conf.get(CONF_CALENDAR_ACCESS) is FeatureAccess.ReadWrite: hass.services.register( DOMAIN, "modify_calendar_event", calendar_services.modify_calendar_event @@ -87,10 +91,6 @@ def setup_platform(hass, config, add_devices, discovery_info=None): hass.services.register( DOMAIN, "respond_calendar_event", calendar_services.respond_calendar_event ) - if conf.get(CONF_CALENDAR_ACCESS) is not FeatureAccess.Disabled: - hass.services.register( - DOMAIN, "scan_for_calendars", calendar_services.scan_for_calendars - ) return True diff --git a/custom_components/o365/utils.py b/custom_components/o365/utils.py index 796fb3b..df13db3 100644 --- a/custom_components/o365/utils.py +++ b/custom_components/o365/utils.py @@ -62,7 +62,7 @@ def get_scopes(conf): def validate_permissions(scopes, token_path=DEFAULT_CACHE_PATH, token_filename="o365.token"): full_token_path = os.path.join(token_path, token_filename) if not os.path.exists(full_token_path) or not os.path.isfile(full_token_path): - _LOGGER.warning(f"Could not loacte token at {full_token_path}") + _LOGGER.warning(f"Could not locate token at {full_token_path}") return False with open(full_token_path, "r", encoding="UTF-8") as fh: raw = fh.read() From ec289247cc64b45524f9933998fc1dffa75b838e Mon Sep 17 00:00:00 2001 From: BottlecapDave Date: Sun, 7 Feb 2021 08:07:05 +0000 Subject: [PATCH 4/7] Fixed validate_permissions to handle certain scopes not being returned in the access token. --- custom_components/o365/const.py | 9 +++++++++ custom_components/o365/utils.py | 4 +++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/custom_components/o365/const.py b/custom_components/o365/const.py index 885d542..ea01018 100644 --- a/custom_components/o365/const.py +++ b/custom_components/o365/const.py @@ -122,6 +122,15 @@ class FeatureAccess(Enum): "Mail.Send", "Mail.Send.Shared", ] +# Scopes that might not exist in the retrieved token +IGNORABLE_SCOPES = [ + "offline_access", + "Calendars.Read.Shared", + "Calendars.ReadWrite.Shared", + "Mail.Read.Shared", + "Mail.ReadWrite.Shared", + "Mail.Send.Shared", +] TOKEN_BACKEND = FileSystemTokenBackend( token_path=DEFAULT_CACHE_PATH, token_filename="o365.token" ) diff --git a/custom_components/o365/utils.py b/custom_components/o365/utils.py index df13db3..0f1f534 100644 --- a/custom_components/o365/utils.py +++ b/custom_components/o365/utils.py @@ -11,6 +11,7 @@ CALENDAR_READ_WRITE_SCOPES, EMAIL_READ_SCOPES, EMAIL_READ_WRITE_SCOPES, + IGNORABLE_SCOPES, CONFIG_BASE_DIR, DATETIME_FORMAT, CALENDAR_DEVICE_SCHEMA, @@ -67,7 +68,8 @@ def validate_permissions(scopes, token_path=DEFAULT_CACHE_PATH, token_filename=" with open(full_token_path, "r", encoding="UTF-8") as fh: raw = fh.read() permissions = json.loads(raw)["scope"] - all_permissions_granted = all([x in permissions for x in scopes]) + mandatory_scopes = [x for x in scopes if x not in IGNORABLE_SCOPES] + all_permissions_granted = all([x in permissions for x in mandatory_scopes]) if not all_permissions_granted: _LOGGER.warning(f"All permissions granted: {all_permissions_granted}") return all_permissions_granted From 0b3dea3355b018a63306b9d6fffccadae2dab05c Mon Sep 17 00:00:00 2001 From: BottlecapDave Date: Sun, 7 Feb 2021 08:08:13 +0000 Subject: [PATCH 5/7] Updated mail sensor to not get setup if mail is disabled. --- custom_components/o365/sensor.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/custom_components/o365/sensor.py b/custom_components/o365/sensor.py index d2ff659..17c1431 100644 --- a/custom_components/o365/sensor.py +++ b/custom_components/o365/sensor.py @@ -14,6 +14,7 @@ CONF_IS_UNREAD, CONF_EMAIL_SENSORS, CONF_QUERY_SENSORS, + CONF_EMAIL_ACCESS, ) from .utils import get_email_attributes @@ -29,6 +30,10 @@ def setup_platform(hass, config, add_devices, discovery_info=None): if not is_authenticated: return False + conf = config.get(DOMAIN, {}) + if conf.get(CONF_EMAIL_ACCESS) is FeatureAccess.Disabled: + return False + unread_sensors = hass.data[DOMAIN].get(CONF_EMAIL_SENSORS, []) for conf in unread_sensors: sensor = O365InboxSensor(account, conf) From 6305cf5a94405a3a0d0e1c770789f14353dbcdcf Mon Sep 17 00:00:00 2001 From: BottlecapDave Date: Sun, 7 Feb 2021 08:15:53 +0000 Subject: [PATCH 6/7] Added missing import --- custom_components/o365/sensor.py | 1 + 1 file changed, 1 insertion(+) diff --git a/custom_components/o365/sensor.py b/custom_components/o365/sensor.py index 17c1431..d012c37 100644 --- a/custom_components/o365/sensor.py +++ b/custom_components/o365/sensor.py @@ -15,6 +15,7 @@ CONF_EMAIL_SENSORS, CONF_QUERY_SENSORS, CONF_EMAIL_ACCESS, + FeatureAccess, ) from .utils import get_email_attributes From fbd84c66ca4fca9bf74f24178ad4da8133336dad Mon Sep 17 00:00:00 2001 From: Sourcery AI <> Date: Sun, 7 Feb 2021 08:16:00 +0000 Subject: [PATCH 7/7] 'Refactored by Sourcery' --- custom_components/o365/calendar.py | 8 +++----- custom_components/o365/notify.py | 3 +-- custom_components/o365/utils.py | 13 ++++++------- 3 files changed, 10 insertions(+), 14 deletions(-) diff --git a/custom_components/o365/calendar.py b/custom_components/o365/calendar.py index f6cd6e1..f363c88 100644 --- a/custom_components/o365/calendar.py +++ b/custom_components/o365/calendar.py @@ -262,15 +262,13 @@ def to_datetime(obj): @staticmethod def get_end_date(obj): if hasattr(obj, "end"): - enddate = obj.end + return obj.end elif hasattr(obj, "duration"): - enddate = obj.start + obj.duration.value + return obj.start + obj.duration.value else: - enddate = obj.start + timedelta(days=1) - - return enddate + return obj.start + timedelta(days=1) class CalendarServices: diff --git a/custom_components/o365/notify.py b/custom_components/o365/notify.py index 1c6a021..f305595 100644 --- a/custom_components/o365/notify.py +++ b/custom_components/o365/notify.py @@ -30,8 +30,7 @@ async def async_get_service(hass, config, discovery_info=None): conf = config.get(DOMAIN, {}) if conf.get(CONF_EMAIL_ACCESS) is not FeatureAccess.ReadWrite: return - email_service = O365EmailService(account) - return email_service + return O365EmailService(account) class O365EmailService(BaseNotificationService): diff --git a/custom_components/o365/utils.py b/custom_components/o365/utils.py index 0f1f534..6e22f92 100644 --- a/custom_components/o365/utils.py +++ b/custom_components/o365/utils.py @@ -46,14 +46,14 @@ def get_scopes(conf): scopes = [x for x in BASE_SCOPES] if conf.get(CONF_CALENDAR_ACCESS) is FeatureAccess.ReadWrite: - scopes = scopes + CALENDAR_READ_WRITE_SCOPES + scopes += CALENDAR_READ_WRITE_SCOPES elif conf.get(CONF_CALENDAR_ACCESS) is FeatureAccess.Read: - scopes = scopes + CALENDAR_READ_SCOPES + scopes += CALENDAR_READ_SCOPES if conf.get(CONF_EMAIL_ACCESS) is FeatureAccess.ReadWrite: - scopes = scopes + EMAIL_READ_WRITE_SCOPES + scopes += EMAIL_READ_WRITE_SCOPES elif conf.get(CONF_EMAIL_ACCESS) is FeatureAccess.Read: - scopes = scopes + EMAIL_READ_SCOPES + scopes += EMAIL_READ_SCOPES _LOGGER.warning(f"Required scopes: {scopes}") @@ -69,7 +69,7 @@ def validate_permissions(scopes, token_path=DEFAULT_CACHE_PATH, token_filename=" raw = fh.read() permissions = json.loads(raw)["scope"] mandatory_scopes = [x for x in scopes if x not in IGNORABLE_SCOPES] - all_permissions_granted = all([x in permissions for x in mandatory_scopes]) + all_permissions_granted = all(x in permissions for x in mandatory_scopes) if not all_permissions_granted: _LOGGER.warning(f"All permissions granted: {all_permissions_granted}") return all_permissions_granted @@ -216,7 +216,7 @@ def load_calendars(path): def get_calendar_info(hass, calendar, track_new_devices): """Convert data from O365 into DEVICE_SCHEMA.""" - calendar_info = CALENDAR_DEVICE_SCHEMA( + return CALENDAR_DEVICE_SCHEMA( { CONF_CAL_ID: calendar.calendar_id, CONF_ENTITIES: [ @@ -228,7 +228,6 @@ def get_calendar_info(hass, calendar, track_new_devices): ], } ) - return calendar_info def update_calendar_file(path, calendar, hass, track_new_devices):