Cloud Updates (#11404)
* Verify stored keys on startup * Handle Google Assistant messages * Fix tests * Don't verify expiration when getting claims * Remove email based check * Lint * Lint * Lint
This commit is contained in:
parent
86e1d0f952
commit
f314b6cb6c
7 changed files with 178 additions and 108 deletions
|
@ -5,13 +5,17 @@ import json
|
|||
import logging
|
||||
import os
|
||||
|
||||
import aiohttp
|
||||
import async_timeout
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.const import (
|
||||
EVENT_HOMEASSISTANT_START, CONF_REGION, CONF_MODE)
|
||||
from homeassistant.helpers import entityfilter
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
from homeassistant.util import dt as dt_util
|
||||
from homeassistant.components.alexa import smart_home
|
||||
from homeassistant.components.alexa import smart_home as alexa_sh
|
||||
from homeassistant.components.google_assistant import smart_home as ga_sh
|
||||
|
||||
from . import http_api, iot
|
||||
from .const import CONFIG_DIR, DOMAIN, SERVERS
|
||||
|
@ -21,7 +25,8 @@ REQUIREMENTS = ['warrant==0.6.1']
|
|||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
CONF_ALEXA = 'alexa'
|
||||
CONF_ALEXA_FILTER = 'filter'
|
||||
CONF_GOOGLE_ASSISTANT = 'google_assistant'
|
||||
CONF_FILTER = 'filter'
|
||||
CONF_COGNITO_CLIENT_ID = 'cognito_client_id'
|
||||
CONF_RELAYER = 'relayer'
|
||||
CONF_USER_POOL_ID = 'user_pool_id'
|
||||
|
@ -30,9 +35,9 @@ MODE_DEV = 'development'
|
|||
DEFAULT_MODE = 'production'
|
||||
DEPENDENCIES = ['http']
|
||||
|
||||
ALEXA_SCHEMA = vol.Schema({
|
||||
ASSISTANT_SCHEMA = vol.Schema({
|
||||
vol.Optional(
|
||||
CONF_ALEXA_FILTER,
|
||||
CONF_FILTER,
|
||||
default=lambda: entityfilter.generate_filter([], [], [], [])
|
||||
): entityfilter.FILTER_SCHEMA,
|
||||
})
|
||||
|
@ -46,7 +51,8 @@ CONFIG_SCHEMA = vol.Schema({
|
|||
vol.Optional(CONF_USER_POOL_ID): str,
|
||||
vol.Optional(CONF_REGION): str,
|
||||
vol.Optional(CONF_RELAYER): str,
|
||||
vol.Optional(CONF_ALEXA): ALEXA_SCHEMA
|
||||
vol.Optional(CONF_ALEXA): ASSISTANT_SCHEMA,
|
||||
vol.Optional(CONF_GOOGLE_ASSISTANT): ASSISTANT_SCHEMA,
|
||||
}),
|
||||
}, extra=vol.ALLOW_EXTRA)
|
||||
|
||||
|
@ -60,17 +66,19 @@ def async_setup(hass, config):
|
|||
kwargs = {CONF_MODE: DEFAULT_MODE}
|
||||
|
||||
if CONF_ALEXA not in kwargs:
|
||||
kwargs[CONF_ALEXA] = ALEXA_SCHEMA({})
|
||||
kwargs[CONF_ALEXA] = ASSISTANT_SCHEMA({})
|
||||
|
||||
kwargs[CONF_ALEXA] = smart_home.Config(**kwargs[CONF_ALEXA])
|
||||
if CONF_GOOGLE_ASSISTANT not in kwargs:
|
||||
kwargs[CONF_GOOGLE_ASSISTANT] = ASSISTANT_SCHEMA({})
|
||||
|
||||
kwargs[CONF_ALEXA] = alexa_sh.Config(**kwargs[CONF_ALEXA])
|
||||
kwargs['gass_should_expose'] = kwargs.pop(CONF_GOOGLE_ASSISTANT)
|
||||
cloud = hass.data[DOMAIN] = Cloud(hass, **kwargs)
|
||||
|
||||
@asyncio.coroutine
|
||||
def init_cloud(event):
|
||||
"""Initialize connection."""
|
||||
yield from cloud.initialize()
|
||||
success = yield from cloud.initialize()
|
||||
|
||||
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START, init_cloud)
|
||||
if not success:
|
||||
return False
|
||||
|
||||
yield from http_api.async_setup(hass)
|
||||
return True
|
||||
|
@ -79,12 +87,16 @@ def async_setup(hass, config):
|
|||
class Cloud:
|
||||
"""Store the configuration of the cloud connection."""
|
||||
|
||||
def __init__(self, hass, mode, cognito_client_id=None, user_pool_id=None,
|
||||
region=None, relayer=None, alexa=None):
|
||||
def __init__(self, hass, mode, alexa, gass_should_expose,
|
||||
cognito_client_id=None, user_pool_id=None, region=None,
|
||||
relayer=None):
|
||||
"""Create an instance of Cloud."""
|
||||
self.hass = hass
|
||||
self.mode = mode
|
||||
self.alexa_config = alexa
|
||||
self._gass_should_expose = gass_should_expose
|
||||
self._gass_config = None
|
||||
self.jwt_keyset = None
|
||||
self.id_token = None
|
||||
self.access_token = None
|
||||
self.refresh_token = None
|
||||
|
@ -104,11 +116,6 @@ class Cloud:
|
|||
self.region = info['region']
|
||||
self.relayer = info['relayer']
|
||||
|
||||
@property
|
||||
def cognito_email_based(self):
|
||||
"""Return if cognito is email based."""
|
||||
return not self.user_pool_id.endswith('GmV')
|
||||
|
||||
@property
|
||||
def is_logged_in(self):
|
||||
"""Get if cloud is logged in."""
|
||||
|
@ -128,37 +135,37 @@ class Cloud:
|
|||
|
||||
@property
|
||||
def claims(self):
|
||||
"""Get the claims from the id token."""
|
||||
from jose import jwt
|
||||
return jwt.get_unverified_claims(self.id_token)
|
||||
"""Return the claims from the id token."""
|
||||
return self._decode_claims(self.id_token)
|
||||
|
||||
@property
|
||||
def user_info_path(self):
|
||||
"""Get path to the stored auth."""
|
||||
return self.path('{}_auth.json'.format(self.mode))
|
||||
|
||||
@property
|
||||
def gass_config(self):
|
||||
"""Return the Google Assistant config."""
|
||||
if self._gass_config is None:
|
||||
self._gass_config = ga_sh.Config(
|
||||
should_expose=self._gass_should_expose,
|
||||
agent_user_id=self.claims['cognito:username']
|
||||
)
|
||||
|
||||
return self._gass_config
|
||||
|
||||
@asyncio.coroutine
|
||||
def initialize(self):
|
||||
"""Initialize and load cloud info."""
|
||||
def load_config():
|
||||
"""Load the configuration."""
|
||||
# Ensure config dir exists
|
||||
path = self.hass.config.path(CONFIG_DIR)
|
||||
if not os.path.isdir(path):
|
||||
os.mkdir(path)
|
||||
jwt_success = yield from self._fetch_jwt_keyset()
|
||||
|
||||
user_info = self.user_info_path
|
||||
if os.path.isfile(user_info):
|
||||
with open(user_info, 'rt') as file:
|
||||
info = json.loads(file.read())
|
||||
self.id_token = info['id_token']
|
||||
self.access_token = info['access_token']
|
||||
self.refresh_token = info['refresh_token']
|
||||
if not jwt_success:
|
||||
return False
|
||||
|
||||
yield from self.hass.async_add_job(load_config)
|
||||
self.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START,
|
||||
self._start_cloud)
|
||||
|
||||
if self.id_token is not None:
|
||||
yield from self.iot.connect()
|
||||
return True
|
||||
|
||||
def path(self, *parts):
|
||||
"""Get config path inside cloud dir.
|
||||
|
@ -175,6 +182,7 @@ class Cloud:
|
|||
self.id_token = None
|
||||
self.access_token = None
|
||||
self.refresh_token = None
|
||||
self._gass_config = None
|
||||
|
||||
yield from self.hass.async_add_job(
|
||||
lambda: os.remove(self.user_info_path))
|
||||
|
@ -187,3 +195,79 @@ class Cloud:
|
|||
'access_token': self.access_token,
|
||||
'refresh_token': self.refresh_token,
|
||||
}, indent=4))
|
||||
|
||||
def _start_cloud(self, event):
|
||||
"""Start the cloud component."""
|
||||
# Ensure config dir exists
|
||||
path = self.hass.config.path(CONFIG_DIR)
|
||||
if not os.path.isdir(path):
|
||||
os.mkdir(path)
|
||||
|
||||
user_info = self.user_info_path
|
||||
if not os.path.isfile(user_info):
|
||||
return
|
||||
|
||||
with open(user_info, 'rt') as file:
|
||||
info = json.loads(file.read())
|
||||
|
||||
# Validate tokens
|
||||
try:
|
||||
for token in 'id_token', 'access_token':
|
||||
self._decode_claims(info[token])
|
||||
except ValueError as err: # Raised when token is invalid
|
||||
_LOGGER.warning('Found invalid token %s: %s', token, err)
|
||||
return
|
||||
|
||||
self.id_token = info['id_token']
|
||||
self.access_token = info['access_token']
|
||||
self.refresh_token = info['refresh_token']
|
||||
|
||||
self.hass.add_job(self.iot.connect())
|
||||
|
||||
@asyncio.coroutine
|
||||
def _fetch_jwt_keyset(self):
|
||||
"""Fetch the JWT keyset for the Cognito instance."""
|
||||
session = async_get_clientsession(self.hass)
|
||||
url = ("https://cognito-idp.us-east-1.amazonaws.com/"
|
||||
"{}/.well-known/jwks.json".format(self.user_pool_id))
|
||||
|
||||
try:
|
||||
with async_timeout.timeout(10, loop=self.hass.loop):
|
||||
req = yield from session.get(url)
|
||||
self.jwt_keyset = yield from req.json()
|
||||
|
||||
return True
|
||||
|
||||
except (asyncio.TimeoutError, aiohttp.ClientError) as err:
|
||||
_LOGGER.error("Error fetching Cognito keyset: %s", err)
|
||||
return False
|
||||
|
||||
def _decode_claims(self, token):
|
||||
"""Decode the claims in a token."""
|
||||
from jose import jwt, exceptions as jose_exceptions
|
||||
try:
|
||||
header = jwt.get_unverified_header(token)
|
||||
except jose_exceptions.JWTError as err:
|
||||
raise ValueError(str(err)) from None
|
||||
kid = header.get("kid")
|
||||
|
||||
if kid is None:
|
||||
raise ValueError('No kid in header')
|
||||
|
||||
# Locate the key for this kid
|
||||
key = None
|
||||
for key_dict in self.jwt_keyset["keys"]:
|
||||
if key_dict["kid"] == kid:
|
||||
key = key_dict
|
||||
break
|
||||
if not key:
|
||||
raise ValueError(
|
||||
"Unable to locate kid ({}) in keyset".format(kid))
|
||||
|
||||
try:
|
||||
return jwt.decode(
|
||||
token, key, audience=self.cognito_client_id, options={
|
||||
'verify_exp': False,
|
||||
})
|
||||
except jose_exceptions.JWTError as err:
|
||||
raise ValueError(str(err)) from None
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
"""Package to communicate with the authentication API."""
|
||||
import hashlib
|
||||
import logging
|
||||
|
||||
|
||||
|
@ -58,11 +57,6 @@ def _map_aws_exception(err):
|
|||
return ex(err.response['Error']['Message'])
|
||||
|
||||
|
||||
def _generate_username(email):
|
||||
"""Generate a username from an email address."""
|
||||
return hashlib.sha512(email.encode('utf-8')).hexdigest()
|
||||
|
||||
|
||||
def register(cloud, email, password):
|
||||
"""Register a new account."""
|
||||
from botocore.exceptions import ClientError
|
||||
|
@ -72,10 +66,7 @@ def register(cloud, email, password):
|
|||
# https://github.com/capless/warrant/pull/82
|
||||
cognito.add_base_attributes()
|
||||
try:
|
||||
if cloud.cognito_email_based:
|
||||
cognito.register(email, password)
|
||||
else:
|
||||
cognito.register(_generate_username(email), password)
|
||||
cognito.register(email, password)
|
||||
except ClientError as err:
|
||||
raise _map_aws_exception(err)
|
||||
|
||||
|
@ -86,11 +77,7 @@ def confirm_register(cloud, confirmation_code, email):
|
|||
|
||||
cognito = _cognito(cloud)
|
||||
try:
|
||||
if cloud.cognito_email_based:
|
||||
cognito.confirm_sign_up(confirmation_code, email)
|
||||
else:
|
||||
cognito.confirm_sign_up(confirmation_code,
|
||||
_generate_username(email))
|
||||
cognito.confirm_sign_up(confirmation_code, email)
|
||||
except ClientError as err:
|
||||
raise _map_aws_exception(err)
|
||||
|
||||
|
@ -114,10 +101,7 @@ def forgot_password(cloud, email):
|
|||
"""Initiate forgotten password flow."""
|
||||
from botocore.exceptions import ClientError
|
||||
|
||||
if cloud.cognito_email_based:
|
||||
cognito = _cognito(cloud, username=email)
|
||||
else:
|
||||
cognito = _cognito(cloud, username=_generate_username(email))
|
||||
cognito = _cognito(cloud, username=email)
|
||||
|
||||
try:
|
||||
cognito.initiate_forgot_password()
|
||||
|
@ -129,10 +113,7 @@ def confirm_forgot_password(cloud, confirmation_code, email, new_password):
|
|||
"""Confirm forgotten password code and change password."""
|
||||
from botocore.exceptions import ClientError
|
||||
|
||||
if cloud.cognito_email_based:
|
||||
cognito = _cognito(cloud, username=email)
|
||||
else:
|
||||
cognito = _cognito(cloud, username=_generate_username(email))
|
||||
cognito = _cognito(cloud, username=email)
|
||||
|
||||
try:
|
||||
cognito.confirm_forgot_password(confirmation_code, new_password)
|
||||
|
|
|
@ -252,6 +252,6 @@ def _account_data(cloud):
|
|||
|
||||
return {
|
||||
'email': claims['email'],
|
||||
'sub_exp': claims.get('custom:sub-exp'),
|
||||
'sub_exp': claims['custom:sub-exp'],
|
||||
'cloud': cloud.iot.state,
|
||||
}
|
||||
|
|
|
@ -5,7 +5,8 @@ import logging
|
|||
from aiohttp import hdrs, client_exceptions, WSMsgType
|
||||
|
||||
from homeassistant.const import EVENT_HOMEASSISTANT_STOP
|
||||
from homeassistant.components.alexa import smart_home
|
||||
from homeassistant.components.alexa import smart_home as alexa
|
||||
from homeassistant.components.google_assistant import smart_home as ga
|
||||
from homeassistant.util.decorator import Registry
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
from . import auth_api
|
||||
|
@ -204,9 +205,18 @@ def async_handle_message(hass, cloud, handler_name, payload):
|
|||
@asyncio.coroutine
|
||||
def async_handle_alexa(hass, cloud, payload):
|
||||
"""Handle an incoming IoT message for Alexa."""
|
||||
return (yield from smart_home.async_handle_message(hass,
|
||||
cloud.alexa_config,
|
||||
payload))
|
||||
result = yield from alexa.async_handle_message(hass, cloud.alexa_config,
|
||||
payload)
|
||||
return result
|
||||
|
||||
|
||||
@HANDLERS.register('google_assistant')
|
||||
@asyncio.coroutine
|
||||
def async_handle_google_assistant(hass, cloud, payload):
|
||||
"""Handle an incoming IoT message for Google Assistant."""
|
||||
result = yield from ga.async_handle_message(hass, cloud.gass_config,
|
||||
payload)
|
||||
return result
|
||||
|
||||
|
||||
@HANDLERS.register('cloud')
|
||||
|
|
|
@ -78,21 +78,17 @@ def test_login(mock_cognito):
|
|||
def test_register(mock_cognito):
|
||||
"""Test registering an account."""
|
||||
cloud = MagicMock()
|
||||
cloud.cognito_email_based = False
|
||||
cloud = MagicMock()
|
||||
cloud.cognito_email_based = False
|
||||
auth_api.register(cloud, 'email@home-assistant.io', 'password')
|
||||
assert len(mock_cognito.register.mock_calls) == 1
|
||||
result_user, result_password = mock_cognito.register.mock_calls[0][1]
|
||||
assert result_user == \
|
||||
auth_api._generate_username('email@home-assistant.io')
|
||||
assert result_user == 'email@home-assistant.io'
|
||||
assert result_password == 'password'
|
||||
|
||||
|
||||
def test_register_fails(mock_cognito):
|
||||
"""Test registering an account."""
|
||||
cloud = MagicMock()
|
||||
cloud.cognito_email_based = False
|
||||
mock_cognito.register.side_effect = aws_error('SomeError')
|
||||
with pytest.raises(auth_api.CloudError):
|
||||
auth_api.register(cloud, 'email@home-assistant.io', 'password')
|
||||
|
@ -101,19 +97,16 @@ def test_register_fails(mock_cognito):
|
|||
def test_confirm_register(mock_cognito):
|
||||
"""Test confirming a registration of an account."""
|
||||
cloud = MagicMock()
|
||||
cloud.cognito_email_based = False
|
||||
auth_api.confirm_register(cloud, '123456', 'email@home-assistant.io')
|
||||
assert len(mock_cognito.confirm_sign_up.mock_calls) == 1
|
||||
result_code, result_user = mock_cognito.confirm_sign_up.mock_calls[0][1]
|
||||
assert result_user == \
|
||||
auth_api._generate_username('email@home-assistant.io')
|
||||
assert result_user == 'email@home-assistant.io'
|
||||
assert result_code == '123456'
|
||||
|
||||
|
||||
def test_confirm_register_fails(mock_cognito):
|
||||
"""Test an error during confirmation of an account."""
|
||||
cloud = MagicMock()
|
||||
cloud.cognito_email_based = False
|
||||
mock_cognito.confirm_sign_up.side_effect = aws_error('SomeError')
|
||||
with pytest.raises(auth_api.CloudError):
|
||||
auth_api.confirm_register(cloud, '123456', 'email@home-assistant.io')
|
||||
|
@ -138,7 +131,6 @@ def test_resend_email_confirm_fails(mock_cognito):
|
|||
def test_forgot_password(mock_cognito):
|
||||
"""Test starting forgot password flow."""
|
||||
cloud = MagicMock()
|
||||
cloud.cognito_email_based = False
|
||||
auth_api.forgot_password(cloud, 'email@home-assistant.io')
|
||||
assert len(mock_cognito.initiate_forgot_password.mock_calls) == 1
|
||||
|
||||
|
@ -146,7 +138,6 @@ def test_forgot_password(mock_cognito):
|
|||
def test_forgot_password_fails(mock_cognito):
|
||||
"""Test failure when starting forgot password flow."""
|
||||
cloud = MagicMock()
|
||||
cloud.cognito_email_based = False
|
||||
mock_cognito.initiate_forgot_password.side_effect = aws_error('SomeError')
|
||||
with pytest.raises(auth_api.CloudError):
|
||||
auth_api.forgot_password(cloud, 'email@home-assistant.io')
|
||||
|
@ -155,7 +146,6 @@ def test_forgot_password_fails(mock_cognito):
|
|||
def test_confirm_forgot_password(mock_cognito):
|
||||
"""Test confirming forgot password."""
|
||||
cloud = MagicMock()
|
||||
cloud.cognito_email_based = False
|
||||
auth_api.confirm_forgot_password(
|
||||
cloud, '123456', 'email@home-assistant.io', 'new password')
|
||||
assert len(mock_cognito.confirm_forgot_password.mock_calls) == 1
|
||||
|
@ -168,7 +158,6 @@ def test_confirm_forgot_password(mock_cognito):
|
|||
def test_confirm_forgot_password_fails(mock_cognito):
|
||||
"""Test failure when confirming forgot password."""
|
||||
cloud = MagicMock()
|
||||
cloud.cognito_email_based = False
|
||||
mock_cognito.confirm_forgot_password.side_effect = aws_error('SomeError')
|
||||
with pytest.raises(auth_api.CloudError):
|
||||
auth_api.confirm_forgot_password(
|
||||
|
|
|
@ -14,7 +14,8 @@ from tests.common import mock_coro
|
|||
@pytest.fixture
|
||||
def cloud_client(hass, test_client):
|
||||
"""Fixture that can fetch from the cloud client."""
|
||||
with patch('homeassistant.components.cloud.Cloud.initialize'):
|
||||
with patch('homeassistant.components.cloud.Cloud.initialize',
|
||||
return_value=mock_coro(True)):
|
||||
hass.loop.run_until_complete(async_setup_component(hass, 'cloud', {
|
||||
'cloud': {
|
||||
'mode': 'development',
|
||||
|
@ -24,6 +25,8 @@ def cloud_client(hass, test_client):
|
|||
'relayer': 'relayer',
|
||||
}
|
||||
}))
|
||||
hass.data['cloud']._decode_claims = \
|
||||
lambda token: jwt.get_unverified_claims(token)
|
||||
with patch('homeassistant.components.cloud.Cloud.write_user_info'):
|
||||
yield hass.loop.run_until_complete(test_client(hass.http.app))
|
||||
|
||||
|
|
|
@ -3,7 +3,6 @@ import asyncio
|
|||
import json
|
||||
from unittest.mock import patch, MagicMock, mock_open
|
||||
|
||||
from jose import jwt
|
||||
import pytest
|
||||
|
||||
from homeassistant.components import cloud
|
||||
|
@ -31,7 +30,8 @@ def test_constructor_loads_info_from_constant():
|
|||
'region': 'test-region',
|
||||
'relayer': 'test-relayer',
|
||||
}
|
||||
}):
|
||||
}), patch('homeassistant.components.cloud.Cloud._fetch_jwt_keyset',
|
||||
return_value=mock_coro(True)):
|
||||
result = yield from cloud.async_setup(hass, {
|
||||
'cloud': {cloud.CONF_MODE: 'beer'}
|
||||
})
|
||||
|
@ -50,15 +50,17 @@ def test_constructor_loads_info_from_config():
|
|||
"""Test non-dev mode loads info from SERVERS constant."""
|
||||
hass = MagicMock(data={})
|
||||
|
||||
result = yield from cloud.async_setup(hass, {
|
||||
'cloud': {
|
||||
cloud.CONF_MODE: cloud.MODE_DEV,
|
||||
'cognito_client_id': 'test-cognito_client_id',
|
||||
'user_pool_id': 'test-user_pool_id',
|
||||
'region': 'test-region',
|
||||
'relayer': 'test-relayer',
|
||||
}
|
||||
})
|
||||
with patch('homeassistant.components.cloud.Cloud._fetch_jwt_keyset',
|
||||
return_value=mock_coro(True)):
|
||||
result = yield from cloud.async_setup(hass, {
|
||||
'cloud': {
|
||||
cloud.CONF_MODE: cloud.MODE_DEV,
|
||||
'cognito_client_id': 'test-cognito_client_id',
|
||||
'user_pool_id': 'test-user_pool_id',
|
||||
'region': 'test-region',
|
||||
'relayer': 'test-relayer',
|
||||
}
|
||||
})
|
||||
assert result
|
||||
|
||||
cl = hass.data['cloud']
|
||||
|
@ -79,12 +81,13 @@ def test_initialize_loads_info(mock_os, hass):
|
|||
'refresh_token': 'test-refresh-token',
|
||||
}))
|
||||
|
||||
cl = cloud.Cloud(hass, cloud.MODE_DEV)
|
||||
cl = cloud.Cloud(hass, cloud.MODE_DEV, None, None)
|
||||
cl.iot = MagicMock()
|
||||
cl.iot.connect.return_value = mock_coro()
|
||||
|
||||
with patch('homeassistant.components.cloud.open', mopen, create=True):
|
||||
yield from cl.initialize()
|
||||
with patch('homeassistant.components.cloud.open', mopen, create=True), \
|
||||
patch('homeassistant.components.cloud.Cloud._decode_claims'):
|
||||
cl._start_cloud(None)
|
||||
|
||||
assert cl.id_token == 'test-id-token'
|
||||
assert cl.access_token == 'test-access-token'
|
||||
|
@ -95,7 +98,7 @@ def test_initialize_loads_info(mock_os, hass):
|
|||
@asyncio.coroutine
|
||||
def test_logout_clears_info(mock_os, hass):
|
||||
"""Test logging out disconnects and removes info."""
|
||||
cl = cloud.Cloud(hass, cloud.MODE_DEV)
|
||||
cl = cloud.Cloud(hass, cloud.MODE_DEV, None, None)
|
||||
cl.iot = MagicMock()
|
||||
cl.iot.disconnect.return_value = mock_coro()
|
||||
|
||||
|
@ -113,7 +116,7 @@ def test_write_user_info():
|
|||
"""Test writing user info works."""
|
||||
mopen = mock_open()
|
||||
|
||||
cl = cloud.Cloud(MagicMock(), cloud.MODE_DEV)
|
||||
cl = cloud.Cloud(MagicMock(), cloud.MODE_DEV, None, None)
|
||||
cl.id_token = 'test-id-token'
|
||||
cl.access_token = 'test-access-token'
|
||||
cl.refresh_token = 'test-refresh-token'
|
||||
|
@ -135,24 +138,24 @@ def test_write_user_info():
|
|||
@asyncio.coroutine
|
||||
def test_subscription_expired():
|
||||
"""Test subscription being expired."""
|
||||
cl = cloud.Cloud(None, cloud.MODE_DEV)
|
||||
cl.id_token = jwt.encode({
|
||||
cl = cloud.Cloud(None, cloud.MODE_DEV, None, None)
|
||||
token_val = {
|
||||
'custom:sub-exp': '2017-11-13'
|
||||
}, 'test')
|
||||
|
||||
with patch('homeassistant.util.dt.utcnow',
|
||||
return_value=utcnow().replace(year=2018)):
|
||||
}
|
||||
with patch.object(cl, '_decode_claims', return_value=token_val), \
|
||||
patch('homeassistant.util.dt.utcnow',
|
||||
return_value=utcnow().replace(year=2018)):
|
||||
assert cl.subscription_expired
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def test_subscription_not_expired():
|
||||
"""Test subscription not being expired."""
|
||||
cl = cloud.Cloud(None, cloud.MODE_DEV)
|
||||
cl.id_token = jwt.encode({
|
||||
cl = cloud.Cloud(None, cloud.MODE_DEV, None, None)
|
||||
token_val = {
|
||||
'custom:sub-exp': '2017-11-13'
|
||||
}, 'test')
|
||||
|
||||
with patch('homeassistant.util.dt.utcnow',
|
||||
return_value=utcnow().replace(year=2017, month=11, day=9)):
|
||||
}
|
||||
with patch.object(cl, '_decode_claims', return_value=token_val), \
|
||||
patch('homeassistant.util.dt.utcnow',
|
||||
return_value=utcnow().replace(year=2017, month=11, day=9)):
|
||||
assert not cl.subscription_expired
|
||||
|
|
Loading…
Add table
Reference in a new issue