Add devices check to iCloud config flow (#31950)

* Add devices check to iCloud config flow

* Some test rename

* Bump pyicloud to catch KeyError
This commit is contained in:
Quentame 2020-03-10 11:42:04 +01:00 committed by GitHub
parent 8c52e2c923
commit 2e802c88f8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 64 additions and 15 deletions

View file

@ -1,7 +1,8 @@
{
"config": {
"abort": {
"already_configured": "Account already configured"
"already_configured": "Account already configured",
"no_device": "None of your devices have \"Find my iPhone\" activated"
},
"error": {
"login": "Login error: please check your email & password",

View file

@ -5,7 +5,11 @@ import operator
from typing import Dict
from pyicloud import PyiCloudService
from pyicloud.exceptions import PyiCloudFailedLoginException, PyiCloudNoDevicesException
from pyicloud.exceptions import (
PyiCloudFailedLoginException,
PyiCloudNoDevicesException,
PyiCloudServiceNotActivatedException,
)
from pyicloud.services.findmyiphone import AppleDevice
from homeassistant.components.zone import async_active_zone
@ -109,7 +113,7 @@ class IcloudAccount:
api_devices = self.api.devices
# Gets device owners infos
user_info = api_devices.response["userInfo"]
except (KeyError, PyiCloudNoDevicesException):
except (PyiCloudServiceNotActivatedException, PyiCloudNoDevicesException):
_LOGGER.error("No iCloud device found")
raise ConfigEntryNotReady

View file

@ -3,7 +3,12 @@ import logging
import os
from pyicloud import PyiCloudService
from pyicloud.exceptions import PyiCloudException, PyiCloudFailedLoginException
from pyicloud.exceptions import (
PyiCloudException,
PyiCloudFailedLoginException,
PyiCloudNoDevicesException,
PyiCloudServiceNotActivatedException,
)
import voluptuous as vol
from homeassistant import config_entries
@ -101,6 +106,17 @@ class IcloudFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
if self.api.requires_2sa:
return await self.async_step_trusted_device()
try:
devices = await self.hass.async_add_executor_job(
getattr, self.api, "devices"
)
if not devices:
raise PyiCloudNoDevicesException()
except (PyiCloudServiceNotActivatedException, PyiCloudNoDevicesException):
_LOGGER.error("No device found in the iCloud account: %s", self._username)
self.api = None
return self.async_abort(reason="no_device")
return self.async_create_entry(
title=self._username,
data={

View file

@ -3,7 +3,7 @@
"name": "Apple iCloud",
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/icloud",
"requirements": ["pyicloud==0.9.3"],
"requirements": ["pyicloud==0.9.4"],
"dependencies": [],
"codeowners": ["@Quentame"]
}

View file

@ -31,7 +31,8 @@
"validate_verification_code": "Failed to verify your verification code, choose a trust device and start the verification again"
},
"abort": {
"already_configured": "Account already configured"
"already_configured": "Account already configured",
"no_device": "None of your devices have \"Find my iPhone\" activated"
}
}
}

View file

@ -1312,7 +1312,7 @@ pyhomeworks==0.0.6
pyialarm==0.3
# homeassistant.components.icloud
pyicloud==0.9.3
pyicloud==0.9.4
# homeassistant.components.intesishome
pyintesishome==1.6

View file

@ -480,7 +480,7 @@ pyheos==0.6.0
pyhomematic==0.1.65
# homeassistant.components.icloud
pyicloud==0.9.3
pyicloud==0.9.4
# homeassistant.components.ipma
pyipma==2.0.5

View file

@ -46,8 +46,8 @@ def mock_controller_service():
yield service_mock
@pytest.fixture(name="service_with_cookie")
def mock_controller_service_with_cookie():
@pytest.fixture(name="service_authenticated")
def mock_controller_service_authenticated():
"""Mock a successful service while already authenticate."""
with patch(
"homeassistant.components.icloud.config_flow.PyiCloudService"
@ -59,6 +59,20 @@ def mock_controller_service_with_cookie():
yield service_mock
@pytest.fixture(name="service_authenticated_no_device")
def mock_controller_service_authenticated_no_device():
"""Mock a successful service while already authenticate, but without device."""
with patch(
"homeassistant.components.icloud.config_flow.PyiCloudService"
) as service_mock:
service_mock.return_value.requires_2sa = False
service_mock.return_value.trusted_devices = TRUSTED_DEVICES
service_mock.return_value.send_verification_code = Mock(return_value=True)
service_mock.return_value.validate_verification_code = Mock(return_value=True)
service_mock.return_value.devices = {}
yield service_mock
@pytest.fixture(name="service_send_verification_code_failed")
def mock_controller_service_send_verification_code_failed():
"""Mock a failed service during sending verification code step."""
@ -103,7 +117,7 @@ async def test_user(hass: HomeAssistantType, service: MagicMock):
async def test_user_with_cookie(
hass: HomeAssistantType, service_with_cookie: MagicMock
hass: HomeAssistantType, service_authenticated: MagicMock
):
"""Test user config with presence of a cookie."""
# test with all provided
@ -148,7 +162,7 @@ async def test_import(hass: HomeAssistantType, service: MagicMock):
async def test_import_with_cookie(
hass: HomeAssistantType, service_with_cookie: MagicMock
hass: HomeAssistantType, service_authenticated: MagicMock
):
"""Test import step with presence of a cookie."""
# import with username and password
@ -186,7 +200,7 @@ async def test_import_with_cookie(
async def test_two_accounts_setup(
hass: HomeAssistantType, service_with_cookie: MagicMock
hass: HomeAssistantType, service_authenticated: MagicMock
):
"""Test to setup two accounts."""
MockConfigEntry(
@ -210,7 +224,7 @@ async def test_two_accounts_setup(
assert result["data"][CONF_GPS_ACCURACY_THRESHOLD] == DEFAULT_GPS_ACCURACY_THRESHOLD
async def test_abort_if_already_setup(hass: HomeAssistantType):
async def test_already_setup(hass: HomeAssistantType):
"""Test we abort if the account is already setup."""
MockConfigEntry(
domain=DOMAIN,
@ -240,7 +254,7 @@ async def test_abort_if_already_setup(hass: HomeAssistantType):
async def test_login_failed(hass: HomeAssistantType):
"""Test when we have errors during login."""
with patch(
"pyicloud.base.PyiCloudService.authenticate",
"homeassistant.components.icloud.config_flow.PyiCloudService.authenticate",
side_effect=PyiCloudFailedLoginException(),
):
result = await hass.config_entries.flow.async_init(
@ -252,6 +266,19 @@ async def test_login_failed(hass: HomeAssistantType):
assert result["errors"] == {CONF_USERNAME: "login"}
async def test_no_device(
hass: HomeAssistantType, service_authenticated_no_device: MagicMock
):
"""Test when we have no devices."""
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_USER},
data={CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD},
)
assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT
assert result["reason"] == "no_device"
async def test_trusted_device(hass: HomeAssistantType, service: MagicMock):
"""Test trusted_device step."""
result = await hass.config_entries.flow.async_init(