Don't use keyset (#17984)

pull/17970/head
Paulus Schoutsen 2018-10-29 21:25:17 +01:00 committed by Paulus Schoutsen
parent a91d894132
commit 1e03f945b5
2 changed files with 13 additions and 80 deletions

View File

@ -4,20 +4,16 @@ Component to integrate the Home Assistant cloud.
For more details about this component, please refer to the documentation at For more details about this component, please refer to the documentation at
https://home-assistant.io/components/cloud/ https://home-assistant.io/components/cloud/
""" """
import asyncio
from datetime import datetime, timedelta from datetime import datetime, timedelta
import json import json
import logging import logging
import os import os
import aiohttp
import async_timeout
import voluptuous as vol import voluptuous as vol
from homeassistant.const import ( from homeassistant.const import (
EVENT_HOMEASSISTANT_START, CONF_REGION, CONF_MODE, CONF_NAME) EVENT_HOMEASSISTANT_START, CONF_REGION, CONF_MODE, CONF_NAME)
from homeassistant.helpers import entityfilter, config_validation as cv from homeassistant.helpers import entityfilter, config_validation as cv
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.util import dt as dt_util from homeassistant.util import dt as dt_util
from homeassistant.components.alexa import smart_home as alexa_sh from homeassistant.components.alexa import smart_home as alexa_sh
from homeassistant.components.google_assistant import helpers as ga_h from homeassistant.components.google_assistant import helpers as ga_h
@ -129,7 +125,6 @@ class Cloud:
self._google_actions = google_actions self._google_actions = google_actions
self._gactions_config = None self._gactions_config = None
self._prefs = None self._prefs = None
self.jwt_keyset = None
self.id_token = None self.id_token = None
self.access_token = None self.access_token = None
self.refresh_token = None self.refresh_token = None
@ -262,13 +257,6 @@ class Cloud:
} }
self._prefs = prefs self._prefs = prefs
success = await self._fetch_jwt_keyset()
# Fetching keyset can fail if internet is not up yet.
if not success:
self.hass.helpers.event.async_call_later(5, self.async_start)
return
def load_config(): def load_config():
"""Load config.""" """Load config."""
# Ensure config dir exists # Ensure config dir exists
@ -288,14 +276,6 @@ class Cloud:
if info is None: if info is None:
return return
# 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.id_token = info['id_token']
self.access_token = info['access_token'] self.access_token = info['access_token']
self.refresh_token = info['refresh_token'] self.refresh_token = info['refresh_token']
@ -311,49 +291,7 @@ class Cloud:
self._prefs[STORAGE_ENABLE_ALEXA] = alexa_enabled self._prefs[STORAGE_ENABLE_ALEXA] = alexa_enabled
await self._store.async_save(self._prefs) await self._store.async_save(self._prefs)
async def _fetch_jwt_keyset(self): def _decode_claims(self, token): # pylint: disable=no-self-use
"""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 = await session.get(url)
self.jwt_keyset = await 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.""" """Decode the claims in a token."""
from jose import jwt, exceptions as jose_exceptions from jose import jwt
try: return jwt.get_unverified_claims(token)
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

View File

@ -32,8 +32,7 @@ def test_constructor_loads_info_from_constant():
'google_actions_sync_url': 'test-google_actions_sync_url', 'google_actions_sync_url': 'test-google_actions_sync_url',
'subscription_info_url': 'test-subscription-info-url' 'subscription_info_url': 'test-subscription-info-url'
} }
}), patch('homeassistant.components.cloud.Cloud._fetch_jwt_keyset', }):
return_value=mock_coro(True)):
result = yield from cloud.async_setup(hass, { result = yield from cloud.async_setup(hass, {
'cloud': {cloud.CONF_MODE: 'beer'} 'cloud': {cloud.CONF_MODE: 'beer'}
}) })
@ -54,17 +53,15 @@ def test_constructor_loads_info_from_config():
"""Test non-dev mode loads info from SERVERS constant.""" """Test non-dev mode loads info from SERVERS constant."""
hass = MagicMock(data={}) hass = MagicMock(data={})
with patch('homeassistant.components.cloud.Cloud._fetch_jwt_keyset', result = yield from cloud.async_setup(hass, {
return_value=mock_coro(True)): 'cloud': {
result = yield from cloud.async_setup(hass, { cloud.CONF_MODE: cloud.MODE_DEV,
'cloud': { 'cognito_client_id': 'test-cognito_client_id',
cloud.CONF_MODE: cloud.MODE_DEV, 'user_pool_id': 'test-user_pool_id',
'cognito_client_id': 'test-cognito_client_id', 'region': 'test-region',
'user_pool_id': 'test-user_pool_id', 'relayer': 'test-relayer',
'region': 'test-region', }
'relayer': 'test-relayer', })
}
})
assert result assert result
cl = hass.data['cloud'] cl = hass.data['cloud']
@ -89,8 +86,6 @@ async def test_initialize_loads_info(mock_os, hass):
cl.iot.connect.return_value = mock_coro() cl.iot.connect.return_value = mock_coro()
with patch('homeassistant.components.cloud.open', mopen, create=True), \ with patch('homeassistant.components.cloud.open', mopen, create=True), \
patch('homeassistant.components.cloud.Cloud._fetch_jwt_keyset',
return_value=mock_coro(True)), \
patch('homeassistant.components.cloud.Cloud._decode_claims'): patch('homeassistant.components.cloud.Cloud._decode_claims'):
await cl.async_start(None) await cl.async_start(None)