diff --git a/homeassistant/components/nightscout/__init__.py b/homeassistant/components/nightscout/__init__.py index 88939cbe790..1b67963bcc3 100644 --- a/homeassistant/components/nightscout/__init__.py +++ b/homeassistant/components/nightscout/__init__.py @@ -7,7 +7,7 @@ from aiohttp import ClientError from py_nightscout import Api as NightscoutAPI from homeassistant.config_entries import ConfigEntry -from homeassistant.const import CONF_URL +from homeassistant.const import CONF_API_KEY, CONF_URL from homeassistant.core import HomeAssistant from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers import device_registry as dr @@ -30,8 +30,9 @@ async def async_setup(hass: HomeAssistant, config: dict): async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): """Set up Nightscout from a config entry.""" server_url = entry.data[CONF_URL] + api_key = entry.data.get(CONF_API_KEY) session = async_get_clientsession(hass) - api = NightscoutAPI(server_url, session=session) + api = NightscoutAPI(server_url, session=session, api_secret=api_key) try: status = await api.get_server_status() except (ClientError, AsyncIOTimeoutError, OSError) as error: diff --git a/homeassistant/components/nightscout/config_flow.py b/homeassistant/components/nightscout/config_flow.py index bd33bc8dcb4..3000d652e46 100644 --- a/homeassistant/components/nightscout/config_flow.py +++ b/homeassistant/components/nightscout/config_flow.py @@ -2,27 +2,32 @@ from asyncio import TimeoutError as AsyncIOTimeoutError import logging -from aiohttp import ClientError +from aiohttp import ClientError, ClientResponseError from py_nightscout import Api as NightscoutAPI import voluptuous as vol from homeassistant import config_entries, exceptions -from homeassistant.const import CONF_URL +from homeassistant.const import CONF_API_KEY, CONF_URL from .const import DOMAIN # pylint:disable=unused-import from .utils import hash_from_url _LOGGER = logging.getLogger(__name__) -DATA_SCHEMA = vol.Schema({vol.Required(CONF_URL): str}) +DATA_SCHEMA = vol.Schema({vol.Required(CONF_URL): str, vol.Optional(CONF_API_KEY): str}) async def _validate_input(data): """Validate the user input allows us to connect.""" url = data[CONF_URL] + api_key = data.get(CONF_API_KEY) try: - api = NightscoutAPI(url) + api = NightscoutAPI(url, api_secret=api_key) status = await api.get_server_status() + if status.settings.get("authDefaultRoles") == "status-only": + await api.get_sgvs() + except ClientResponseError as error: + raise InputValidationError("invalid_auth") from error except (ClientError, AsyncIOTimeoutError, OSError) as error: raise InputValidationError("cannot_connect") from error diff --git a/homeassistant/components/nightscout/manifest.json b/homeassistant/components/nightscout/manifest.json index b3e9b3a0d55..ecc44258e90 100644 --- a/homeassistant/components/nightscout/manifest.json +++ b/homeassistant/components/nightscout/manifest.json @@ -4,7 +4,7 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/nightscout", "requirements": [ - "py-nightscout==1.2.1" + "py-nightscout==1.2.2" ], "codeowners": [ "@marciogranzotto" diff --git a/homeassistant/components/nightscout/strings.json b/homeassistant/components/nightscout/strings.json index a6e100ae8f2..2240bcec02b 100644 --- a/homeassistant/components/nightscout/strings.json +++ b/homeassistant/components/nightscout/strings.json @@ -3,12 +3,16 @@ "flow_title": "Nightscout", "step": { "user": { + "title": "Enter your Nightscout server information.", + "description": "- URL: the address of your nightscout instance. I.e.: https://myhomeassistant.duckdns.org:5423\n- API Key (Optional): Only use if your instance is protected (auth_default_roles != readable).", "data": { - "url": "URL" + "url": "[%key:common::config_flow::data::url%]", + "api_key": "[%key:common::config_flow::data::api_key%]" } } }, "error": { + "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]", "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", "unknown": "[%key:common::config_flow::error::unknown%]" }, @@ -16,4 +20,4 @@ "already_configured": "[%key:common::config_flow::abort::already_configured_device%]" } } -} \ No newline at end of file +} diff --git a/homeassistant/components/nightscout/translations/en.json b/homeassistant/components/nightscout/translations/en.json index b7947c84997..ffe5cce81a6 100644 --- a/homeassistant/components/nightscout/translations/en.json +++ b/homeassistant/components/nightscout/translations/en.json @@ -1,18 +1,22 @@ { "config": { "abort": { - "already_configured": "Device is already configured" + "already_configured": "[%key:common::config_flow::abort::already_configured_device%]" }, "error": { - "cannot_connect": "Failed to connect", - "unknown": "Unexpected error" + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", + "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]", + "unknown": "[%key:common::config_flow::error::unknown%]" }, "flow_title": "Nightscout", "step": { "user": { "data": { - "url": "URL" - } + "api_key": "[%key:common::config_flow::data::api_key%]", + "url": "[%key:common::config_flow::data::url%]" + }, + "description": "- URL: the address of your nightscout instance. I.e.: https://myhomeassistant.duckdns.org:5423\n- API Key (Optional): Only use if your instance is protected (auth_default_roles != readable).", + "title": "Enter your Nightscout server information." } } } diff --git a/requirements_all.txt b/requirements_all.txt index b0c0eaf54b9..76c59fcf791 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1183,7 +1183,7 @@ py-cpuinfo==7.0.0 py-melissa-climate==2.1.4 # homeassistant.components.nightscout -py-nightscout==1.2.1 +py-nightscout==1.2.2 # homeassistant.components.schluter py-schluter==0.1.7 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index d9df60ef4ba..9e9899e039e 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -573,7 +573,7 @@ py-canary==0.5.0 py-melissa-climate==2.1.4 # homeassistant.components.nightscout -py-nightscout==1.2.1 +py-nightscout==1.2.2 # homeassistant.components.seventeentrack py17track==2.2.2 diff --git a/tests/components/nightscout/__init__.py b/tests/components/nightscout/__init__.py index 52064d1a92b..c7f15068d1d 100644 --- a/tests/components/nightscout/__init__.py +++ b/tests/components/nightscout/__init__.py @@ -22,6 +22,11 @@ SERVER_STATUS = ServerStatus.new_from_json_dict( '{"status":"ok","name":"nightscout","version":"13.0.1","serverTime":"2020-08-05T18:14:02.032Z","serverTimeEpoch":1596651242032,"apiEnabled":true,"careportalEnabled":true,"boluscalcEnabled":true,"settings":{},"extendedSettings":{},"authorized":null}' ) ) +SERVER_STATUS_STATUS_ONLY = ServerStatus.new_from_json_dict( + json.loads( + '{"status":"ok","name":"nightscout","version":"14.0.4","serverTime":"2020-09-25T21:03:59.315Z","serverTimeEpoch":1601067839315,"apiEnabled":true,"careportalEnabled":true,"boluscalcEnabled":true,"settings":{"units":"mg/dl","timeFormat":12,"nightMode":false,"editMode":true,"showRawbg":"never","customTitle":"Nightscout","theme":"default","alarmUrgentHigh":true,"alarmUrgentHighMins":[30,60,90,120],"alarmHigh":true,"alarmHighMins":[30,60,90,120],"alarmLow":true,"alarmLowMins":[15,30,45,60],"alarmUrgentLow":true,"alarmUrgentLowMins":[15,30,45],"alarmUrgentMins":[30,60,90,120],"alarmWarnMins":[30,60,90,120],"alarmTimeagoWarn":true,"alarmTimeagoWarnMins":15,"alarmTimeagoUrgent":true,"alarmTimeagoUrgentMins":30,"alarmPumpBatteryLow":false,"language":"en","scaleY":"log","showPlugins":"dbsize delta direction upbat","showForecast":"ar2","focusHours":3,"heartbeat":60,"baseURL":"","authDefaultRoles":"status-only","thresholds":{"bgHigh":260,"bgTargetTop":180,"bgTargetBottom":80,"bgLow":55},"insecureUseHttp":true,"secureHstsHeader":false,"secureHstsHeaderIncludeSubdomains":false,"secureHstsHeaderPreload":false,"secureCsp":false,"deNormalizeDates":false,"showClockDelta":false,"showClockLastTime":false,"bolusRenderOver":1,"frameUrl1":"","frameUrl2":"","frameUrl3":"","frameUrl4":"","frameUrl5":"","frameUrl6":"","frameUrl7":"","frameUrl8":"","frameName1":"","frameName2":"","frameName3":"","frameName4":"","frameName5":"","frameName6":"","frameName7":"","frameName8":"","DEFAULT_FEATURES":["bgnow","delta","direction","timeago","devicestatus","upbat","errorcodes","profile","dbsize"],"alarmTypes":["predict"],"enable":["careportal","boluscalc","food","bwp","cage","sage","iage","iob","cob","basal","ar2","rawbg","pushover","bgi","pump","openaps","treatmentnotify","bgnow","delta","direction","timeago","devicestatus","upbat","errorcodes","profile","dbsize","ar2"]},"extendedSettings":{"devicestatus":{"advanced":true,"days":1}},"authorized":null}' + ) +) async def init_integration(hass) -> MockConfigEntry: diff --git a/tests/components/nightscout/test_config_flow.py b/tests/components/nightscout/test_config_flow.py index a5f3315fbb1..71983f1b29d 100644 --- a/tests/components/nightscout/test_config_flow.py +++ b/tests/components/nightscout/test_config_flow.py @@ -1,5 +1,5 @@ """Test the Nightscout config flow.""" -from aiohttp import ClientConnectionError +from aiohttp import ClientConnectionError, ClientResponseError from homeassistant import config_entries, data_entry_flow, setup from homeassistant.components.nightscout.const import DOMAIN @@ -8,7 +8,11 @@ from homeassistant.const import CONF_URL from tests.async_mock import patch from tests.common import MockConfigEntry -from tests.components.nightscout import GLUCOSE_READINGS, SERVER_STATUS +from tests.components.nightscout import ( + GLUCOSE_READINGS, + SERVER_STATUS, + SERVER_STATUS_STATUS_ONLY, +) CONFIG = {CONF_URL: "https://some.url:1234"} @@ -55,6 +59,28 @@ async def test_user_form_cannot_connect(hass): assert result2["errors"] == {"base": "cannot_connect"} +async def test_user_form_api_key_required(hass): + """Test we handle an unauthorized error.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + with patch( + "homeassistant.components.nightscout.NightscoutAPI.get_server_status", + return_value=SERVER_STATUS_STATUS_ONLY, + ), patch( + "homeassistant.components.nightscout.NightscoutAPI.get_sgvs", + side_effect=ClientResponseError(None, None, status=401), + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + {CONF_URL: "https://some.url:1234"}, + ) + + assert result2["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result2["errors"] == {"base": "invalid_auth"} + + async def test_user_form_unexpected_exception(hass): """Test we handle unexpected exception.""" result = await hass.config_entries.flow.async_init(