diff --git a/homeassistant/components/simplisafe/config_flow.py b/homeassistant/components/simplisafe/config_flow.py index 0b92871ccb2..7ae363c3be3 100644 --- a/homeassistant/components/simplisafe/config_flow.py +++ b/homeassistant/components/simplisafe/config_flow.py @@ -81,17 +81,34 @@ class SimpliSafeFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): description_placeholders={CONF_URL: self._oauth_values.auth_url}, ) + auth_code = user_input[CONF_AUTH_CODE] + + if auth_code.startswith("="): + # Sometimes, users may include the "=" from the URL query param; in that + # case, strip it off and proceed: + LOGGER.debug('Stripping "=" from the start of the authorization code') + auth_code = auth_code[1:] + + if len(auth_code) != 45: + # SimpliSafe authorization codes are 45 characters in length; if the user + # provides something different, stop them here: + return self.async_show_form( + step_id="user", + data_schema=STEP_USER_SCHEMA, + errors={CONF_AUTH_CODE: "invalid_auth_code_length"}, + description_placeholders={CONF_URL: self._oauth_values.auth_url}, + ) + errors = {} session = aiohttp_client.async_get_clientsession(self.hass) - try: simplisafe = await API.async_from_auth( - user_input[CONF_AUTH_CODE], + auth_code, self._oauth_values.code_verifier, session=session, ) except InvalidCredentialsError: - errors = {"base": "invalid_auth"} + errors = {CONF_AUTH_CODE: "invalid_auth"} except SimplipyError as err: LOGGER.error("Unknown error while logging into SimpliSafe: %s", err) errors = {"base": "unknown"} diff --git a/homeassistant/components/simplisafe/strings.json b/homeassistant/components/simplisafe/strings.json index 16ae7111abf..618c21566f7 100644 --- a/homeassistant/components/simplisafe/strings.json +++ b/homeassistant/components/simplisafe/strings.json @@ -2,7 +2,7 @@ "config": { "step": { "user": { - "description": "SimpliSafe authenticates users via its web app. Due to technical limitations, there is a manual step at the end of this process; please ensure that you read the [documentation](http://home-assistant.io/integrations/simplisafe#getting-an-authorization-code) before starting.\n\nWhen you are ready, click [here]({url}) to open the SimpliSafe web app and input your credentials. When the process is complete, return here and input the authorization code from the SimpliSafe web app URL.", + "description": "SimpliSafe authenticates users via its web app. Due to technical limitations, there is a manual step at the end of this process; please ensure that you read the [documentation](http://home-assistant.io/integrations/simplisafe#getting-an-authorization-code) before starting.\n\nWhen you are ready, click [here]({url}) to open the SimpliSafe web app and input your credentials. If you've already logged into SimpliSafe in your browser, you may want to open a new tab, then copy/paste the above URL into that tab.\n\nWhen the process is complete, return here and input the authorization code from the `com.simplisafe.mobile` URL.", "data": { "auth_code": "Authorization Code" } @@ -11,6 +11,7 @@ "error": { "identifier_exists": "Account already registered", "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]", + "invalid_auth_code_length": "SimpliSafe authorization codes are 45 characters in length", "unknown": "[%key:common::config_flow::error::unknown%]" }, "abort": { diff --git a/homeassistant/components/simplisafe/translations/en.json b/homeassistant/components/simplisafe/translations/en.json index 70b0cc15383..245bb18351e 100644 --- a/homeassistant/components/simplisafe/translations/en.json +++ b/homeassistant/components/simplisafe/translations/en.json @@ -2,39 +2,21 @@ "config": { "abort": { "already_configured": "This SimpliSafe account is already in use.", - "email_2fa_timed_out": "Timed out while waiting for email-based two-factor authentication.", "reauth_successful": "Re-authentication was successful", "wrong_account": "The user credentials provided do not match this SimpliSafe account." }, "error": { "identifier_exists": "Account already registered", "invalid_auth": "Invalid authentication", + "invalid_auth_code_length": "SimpliSafe authorization codes are 45 characters in length", "unknown": "Unexpected error" }, - "progress": { - "email_2fa": "Check your email for a verification link from Simplisafe." - }, "step": { - "reauth_confirm": { - "data": { - "password": "Password" - }, - "description": "Please re-enter the password for {username}.", - "title": "Reauthenticate Integration" - }, - "sms_2fa": { - "data": { - "code": "Code" - }, - "description": "Input the two-factor authentication code sent to you via SMS." - }, "user": { "data": { - "auth_code": "Authorization Code", - "password": "Password", - "username": "Username" + "auth_code": "Authorization Code" }, - "description": "SimpliSafe authenticates users via its web app. Due to technical limitations, there is a manual step at the end of this process; please ensure that you read the [documentation](http://home-assistant.io/integrations/simplisafe#getting-an-authorization-code) before starting.\n\nWhen you are ready, click [here]({url}) to open the SimpliSafe web app and input your credentials. When the process is complete, return here and input the authorization code from the SimpliSafe web app URL." + "description": "SimpliSafe authenticates users via its web app. Due to technical limitations, there is a manual step at the end of this process; please ensure that you read the [documentation](http://home-assistant.io/integrations/simplisafe#getting-an-authorization-code) before starting.\n\nWhen you are ready, click [here]({url}) to open the SimpliSafe web app and input your credentials. If you've already logged into SimpliSafe in your browser, you may want to open a new tab, then copy/paste the above URL into that tab.\n\nWhen the process is complete, return here and input the authorization code from the `com.simplisafe.mobile` URL." } } }, diff --git a/tests/components/simplisafe/test_config_flow.py b/tests/components/simplisafe/test_config_flow.py index 6e6f99ad4bb..cf92ed94d41 100644 --- a/tests/components/simplisafe/test_config_flow.py +++ b/tests/components/simplisafe/test_config_flow.py @@ -1,4 +1,5 @@ """Define tests for the SimpliSafe config flow.""" +import logging from unittest.mock import patch import pytest @@ -10,6 +11,8 @@ from homeassistant.components.simplisafe.config_flow import CONF_AUTH_CODE from homeassistant.config_entries import SOURCE_REAUTH, SOURCE_USER from homeassistant.const import CONF_CODE, CONF_TOKEN, CONF_USERNAME +VALID_AUTH_CODE = "code12345123451234512345123451234512345123451" + async def test_duplicate_error(config_entry, hass, setup_simplisafe): """Test that errors are shown when duplicates are added.""" @@ -23,12 +26,27 @@ async def test_duplicate_error(config_entry, hass, setup_simplisafe): assert result["type"] == data_entry_flow.RESULT_TYPE_FORM result = await hass.config_entries.flow.async_configure( - result["flow_id"], user_input={CONF_AUTH_CODE: "code123"} + result["flow_id"], user_input={CONF_AUTH_CODE: VALID_AUTH_CODE} ) assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT assert result["reason"] == "already_configured" +async def test_invalid_auth_code_length(hass): + """Test that an invalid auth code length show the correct error.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_USER} + ) + assert result["step_id"] == "user" + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input={CONF_AUTH_CODE: "too_short_code"} + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["errors"] == {CONF_AUTH_CODE: "invalid_auth_code_length"} + + async def test_invalid_credentials(hass): """Test that invalid credentials show the correct error.""" with patch( @@ -42,10 +60,11 @@ async def test_invalid_credentials(hass): assert result["type"] == data_entry_flow.RESULT_TYPE_FORM result = await hass.config_entries.flow.async_configure( - result["flow_id"], user_input={CONF_AUTH_CODE: "code123"} + result["flow_id"], + user_input={CONF_AUTH_CODE: VALID_AUTH_CODE}, ) assert result["type"] == data_entry_flow.RESULT_TYPE_FORM - assert result["errors"] == {"base": "invalid_auth"} + assert result["errors"] == {CONF_AUTH_CODE: "invalid_auth"} async def test_options_flow(config_entry, hass): @@ -80,7 +99,7 @@ async def test_step_reauth(config_entry, hass, setup_simplisafe): "homeassistant.components.simplisafe.async_setup_entry", return_value=True ), patch("homeassistant.config_entries.ConfigEntries.async_reload"): result = await hass.config_entries.flow.async_configure( - result["flow_id"], user_input={CONF_AUTH_CODE: "code123"} + result["flow_id"], user_input={CONF_AUTH_CODE: VALID_AUTH_CODE} ) assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT assert result["reason"] == "reauth_successful" @@ -104,14 +123,29 @@ async def test_step_reauth_wrong_account(config_entry, hass, setup_simplisafe): "homeassistant.components.simplisafe.async_setup_entry", return_value=True ), patch("homeassistant.config_entries.ConfigEntries.async_reload"): result = await hass.config_entries.flow.async_configure( - result["flow_id"], user_input={CONF_AUTH_CODE: "code123"} + result["flow_id"], user_input={CONF_AUTH_CODE: VALID_AUTH_CODE} ) assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT assert result["reason"] == "wrong_account" -async def test_step_user(hass, setup_simplisafe): - """Test the user step.""" +@pytest.mark.parametrize( + "auth_code,log_statement", + [ + ( + VALID_AUTH_CODE, + None, + ), + ( + f"={VALID_AUTH_CODE}", + 'Stripping "=" from the start of the authorization code', + ), + ], +) +async def test_step_user(auth_code, caplog, hass, log_statement, setup_simplisafe): + """Test successfully completion of the user step.""" + caplog.set_level = logging.DEBUG + result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_USER} ) @@ -121,10 +155,13 @@ async def test_step_user(hass, setup_simplisafe): "homeassistant.components.simplisafe.async_setup_entry", return_value=True ), patch("homeassistant.config_entries.ConfigEntries.async_reload"): result = await hass.config_entries.flow.async_configure( - result["flow_id"], user_input={CONF_AUTH_CODE: "code123"} + result["flow_id"], user_input={CONF_AUTH_CODE: auth_code} ) assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + if log_statement: + assert any(m for m in caplog.messages if log_statement in m) + assert len(hass.config_entries.async_entries()) == 1 [config_entry] = hass.config_entries.async_entries(DOMAIN) assert config_entry.data == {CONF_USERNAME: "12345", CONF_TOKEN: "token123"} @@ -143,7 +180,7 @@ async def test_unknown_error(hass, setup_simplisafe): assert result["type"] == data_entry_flow.RESULT_TYPE_FORM result = await hass.config_entries.flow.async_configure( - result["flow_id"], user_input={CONF_AUTH_CODE: "code123"} + result["flow_id"], user_input={CONF_AUTH_CODE: VALID_AUTH_CODE} ) assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["errors"] == {"base": "unknown"}