Skip to content

Commit

Permalink
Merge pull request #30 from NabuCasa/dev
Browse files Browse the repository at this point in the history
Release 0.6
  • Loading branch information
pvizeli committed Mar 15, 2019
2 parents 8bad0c1 + a25ba37 commit 7122453
Show file tree
Hide file tree
Showing 9 changed files with 57 additions and 39 deletions.
18 changes: 10 additions & 8 deletions hass_nabucasa/acme.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import josepy as jose

from . import cloud_api
from .utils import UTC

FILE_ACCOUNT_KEY = "acme_account.pem"
FILE_PRIVATE_KEY = "remote_private.pem"
Expand Down Expand Up @@ -104,7 +105,7 @@ def expire_date(self) -> Optional[datetime]:
"""Return datetime of expire date for certificate."""
if not self._x509:
return None
return self._x509.not_valid_after
return self._x509.not_valid_after.replace(tzinfo=UTC)

@property
def common_name(self) -> Optional[str]:
Expand Down Expand Up @@ -344,14 +345,15 @@ async def issue_certificate(self) -> None:
challenge = await self.cloud.run_executor(self._start_challenge, csr)

# Update DNS
async with async_timeout.timeout(10):
resp = await cloud_api.async_remote_challenge_txt(
self.cloud, challenge.validation
)

if resp.status != 200:
try:
async with async_timeout.timeout(15):
resp = await cloud_api.async_remote_challenge_txt(
self.cloud, challenge.validation
)
assert resp.status == 200
except (asyncio.TimeoutError, AssertionError):
_LOGGER.error("Can't set challenge token to NabuCasa DNS!")
raise AcmeNabuCasaError()
raise AcmeNabuCasaError() from None

# Finish validation
try:
Expand Down
14 changes: 8 additions & 6 deletions hass_nabucasa/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,6 @@ async def cleanups(self) -> None:
"""Called on logout."""
raise NotImplementedError()

async def async_user_message(
self, identifier: str, title: str, message: str
) -> None:
"""Create a message for user to UI."""
raise NotImplementedError()

async def async_alexa_message(self, payload: Dict[Any, Any]) -> Dict[Any, Any]:
"""process cloud alexa message to client."""
raise NotImplementedError()
Expand All @@ -64,3 +58,11 @@ async def async_webhook_message(self, payload: Dict[Any, Any]) -> Dict[Any, Any]
async def async_cloudhooks_update(self, data: Dict[str, Dict[str, str]]) -> None:
"""Update local list of cloudhooks."""
raise NotImplementedError()

def dispatcher_message(self, identifier: str, data: Any = None) -> None:
"""Send data to dispatcher."""
raise NotImplementedError()

def user_message(self, identifier: str, title: str, message: str) -> None:
"""Create a message for user to UI."""
raise NotImplementedError()
3 changes: 3 additions & 0 deletions hass_nabucasa/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
STATE_CONNECTED = "connected"
STATE_DISCONNECTED = "disconnected"

DISPATCH_REMOTE_CONNECT = "remote_connect"
DISPATCH_REMOTE_DISCONNECT = "remote_disconnect"

SERVERS = {
"production": {
"cognito_client_id": "60i2uvhvbiref2mftj7rgcrt9u",
Expand Down
4 changes: 2 additions & 2 deletions hass_nabucasa/iot.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ async def _handle_connection(self):
except Unauthenticated as err:
_LOGGER.error("Unable to refresh token: %s", err)

await self.cloud.client.async_user_message(
self.cloud.client.user_message(
"cloud_subscription_expired", "Home Assistant Cloud", MESSAGE_AUTH_FAIL
)

Expand All @@ -155,7 +155,7 @@ async def _handle_connection(self):
return

if self.cloud.subscription_expired:
await self.cloud.client.async_user_message(
self.cloud.client.user_message(
"cloud_subscription_expired", "Home Assistant Cloud", MESSAGE_EXPIRATION
)
self.close_requested = True
Expand Down
35 changes: 20 additions & 15 deletions hass_nabucasa/remote.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,8 @@
from snitun.utils.aes import generate_aes_keyset
from snitun.utils.aiohttp_client import SniTunClientAioHttp

from . import cloud_api, utils
from . import cloud_api, utils, const
from .acme import AcmeClientError, AcmeHandler
from .const import MESSAGE_REMOTE_SETUP, MESSAGE_REMOTE_READY

_LOGGER = logging.getLogger(__name__)

Expand Down Expand Up @@ -117,10 +116,11 @@ async def load_backend(self) -> None:
self._acme_task = self.cloud.run_task(self._certificate_handler())

# Load instance data from backend
async with async_timeout.timeout(10):
resp = await cloud_api.async_remote_register(self.cloud)

if resp.status != 200:
try:
async with async_timeout.timeout(15):
resp = await cloud_api.async_remote_register(self.cloud)
assert resp.status == 200
except (asyncio.TimeoutError, AssertionError):
_LOGGER.error("Can't update remote details from Home Assistant cloud")
return
data = await resp.json()
Expand Down Expand Up @@ -148,17 +148,17 @@ async def load_backend(self) -> None:
try:
await self._acme.issue_certificate()
except AcmeClientError:
await self.cloud.client.async_user_message(
self.cloud.client.user_message(
"cloud_remote_acme",
"Home Assistant Cloud",
MESSAGE_REMOTE_SETUP
const.MESSAGE_REMOTE_SETUP,
)
return
else:
await self.cloud.client.async_user_message(
self.cloud.client.user_message(
"cloud_remote_acme",
"Home Assistant Cloud",
MESSAGE_REMOTE_READY,
const.MESSAGE_REMOTE_READY,
)

# Setup snitun / aiohttp wrapper
Expand Down Expand Up @@ -219,13 +219,14 @@ async def _refresh_snitun_token(self) -> None:

# Generate session token
aes_key, aes_iv = generate_aes_keyset()
async with async_timeout.timeout(10):
resp = await cloud_api.async_remote_token(self.cloud, aes_key, aes_iv)
try:
async with async_timeout.timeout(15):
resp = await cloud_api.async_remote_token(self.cloud, aes_key, aes_iv)
assert resp.status == 200
except (asyncio.TimeoutError, AssertionError):
raise RemoteBackendError() from None

if resp.status != 200:
raise RemoteBackendError()
data = await resp.json()

self._token = SniTunToken(
data["token"].encode(),
aes_key,
Expand All @@ -248,6 +249,8 @@ async def connect(self) -> None:
await self._snitun.connect(
self._token.fernet, self._token.aes_key, self._token.aes_iv
)

self.cloud.client.dispatcher_message(const.DISPATCH_REMOTE_CONNECT)
except SniTunConnectionError:
_LOGGER.error("Connection problem to snitun server")
except RemoteBackendError:
Expand All @@ -271,6 +274,7 @@ async def disconnect(self) -> None:
if not self._snitun.is_connected:
return
await self._snitun.disconnect()
self.cloud.client.dispatcher_message(const.DISPATCH_REMOTE_DISCONNECT)

async def _reconnect_snitun(self) -> None:
"""Reconnect after disconnect."""
Expand All @@ -279,6 +283,7 @@ async def _reconnect_snitun(self) -> None:
if self._snitun.is_connected:
await self._snitun.wait()

self.cloud.client.dispatcher_message(const.DISPATCH_REMOTE_DISCONNECT)
await asyncio.sleep(random.randint(1, 15))
await self.connect()
except asyncio.CancelledError:
Expand Down
6 changes: 3 additions & 3 deletions hass_nabucasa/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,11 +58,11 @@ def server_context_modern() -> ssl.SSLContext:


def next_midnight() -> int:
"""Return the seconds till next midnight."""
midnight = dt.datetime.utcnow().replace(
"""Return the seconds till next local midnight."""
midnight = dt.datetime.now().replace(
hour=0, minute=0, second=0, microsecond=0
) + dt.timedelta(days=1)
return (midnight - dt.datetime.utcnow()).total_seconds()
return (midnight - dt.datetime.now()).total_seconds()


class Registry(dict):
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from setuptools import setup

VERSION = "0.5"
VERSION = "0.6"

setup(
name="hass-nabucasa",
Expand Down
11 changes: 7 additions & 4 deletions tests/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from datetime import datetime
from pathlib import Path
import tempfile
from typing import Optional
from typing import Optional, Any

from hass_nabucasa.client import CloudClient

Expand Down Expand Up @@ -36,6 +36,7 @@ def __init__(self, loop, websession):
self.prop_remote_autostart = True

self.mock_user = []
self.mock_dispatcher = []
self.mock_alexa = []
self.mock_google = []
self.mock_webhooks = []
Expand Down Expand Up @@ -75,12 +76,14 @@ def remote_autostart(self) -> bool:
async def cleanups(self):
"""Need nothing to do."""

async def async_user_message(
self, identifier: str, title: str, message: str
) -> None:
def user_message(self, identifier: str, title: str, message: str) -> None:
"""Create a message for user to UI."""
self.mock_user.append((identifier, title, message))

def dispatcher_message(self, identifier: str, data: Any = None) -> None:
"""Send data to dispatcher."""
self.mock_dispatcher.append((identifier, data))

async def async_alexa_message(self, payload):
"""process cloud alexa message to client."""
self.mock_alexa.append(payload)
Expand Down
3 changes: 3 additions & 0 deletions tests/test_remote.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

from hass_nabucasa.remote import RemoteUI
from hass_nabucasa.utils import utcnow
from hass_nabucasa.const import DISPATCH_REMOTE_CONNECT, DISPATCH_REMOTE_DISCONNECT

from .common import mock_coro, MockAcme, MockSnitun

Expand Down Expand Up @@ -278,6 +279,7 @@ async def test_call_disconnect(
await remote.disconnect()
assert snitun_mock.call_disconnect
assert not remote.is_connected
assert cloud_mock.client.mock_dispatcher[-1][0] == DISPATCH_REMOTE_DISCONNECT


async def test_load_backend_no_autostart(
Expand Down Expand Up @@ -319,6 +321,7 @@ async def test_load_backend_no_autostart(

assert snitun_mock.call_connect
assert snitun_mock.connect_args[0] == b"test-token"
assert cloud_mock.client.mock_dispatcher[-1][0] == DISPATCH_REMOTE_CONNECT


async def test_get_certificate_details(
Expand Down

0 comments on commit 7122453

Please sign in to comment.