Opower MFA fixes (#99317)

opower mfa fixes
This commit is contained in:
tronikos 2023-08-30 21:36:07 -07:00 committed by Bram Kragten
parent cb33d82c24
commit 794071449a
7 changed files with 25 additions and 22 deletions

View File

@ -5,7 +5,13 @@ from collections.abc import Mapping
import logging import logging
from typing import Any from typing import Any
from opower import CannotConnect, InvalidAuth, Opower, get_supported_utility_names from opower import (
CannotConnect,
InvalidAuth,
Opower,
get_supported_utility_names,
select_utility,
)
import voluptuous as vol import voluptuous as vol
from homeassistant import config_entries from homeassistant import config_entries
@ -20,9 +26,7 @@ _LOGGER = logging.getLogger(__name__)
STEP_USER_DATA_SCHEMA = vol.Schema( STEP_USER_DATA_SCHEMA = vol.Schema(
{ {
vol.Required(CONF_UTILITY): vol.In( vol.Required(CONF_UTILITY): vol.In(get_supported_utility_names()),
get_supported_utility_names(supports_mfa=True)
),
vol.Required(CONF_USERNAME): str, vol.Required(CONF_USERNAME): str,
vol.Required(CONF_PASSWORD): str, vol.Required(CONF_PASSWORD): str,
} }
@ -38,7 +42,7 @@ async def _validate_login(
login_data[CONF_UTILITY], login_data[CONF_UTILITY],
login_data[CONF_USERNAME], login_data[CONF_USERNAME],
login_data[CONF_PASSWORD], login_data[CONF_PASSWORD],
login_data.get(CONF_TOTP_SECRET, None), login_data.get(CONF_TOTP_SECRET),
) )
errors: dict[str, str] = {} errors: dict[str, str] = {}
try: try:
@ -50,12 +54,6 @@ async def _validate_login(
return errors return errors
@callback
def _supports_mfa(utility: str) -> bool:
"""Return whether the utility supports MFA."""
return utility not in get_supported_utility_names(supports_mfa=False)
class OpowerConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): class OpowerConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
"""Handle a config flow for Opower.""" """Handle a config flow for Opower."""
@ -78,7 +76,7 @@ class OpowerConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
CONF_USERNAME: user_input[CONF_USERNAME], CONF_USERNAME: user_input[CONF_USERNAME],
} }
) )
if _supports_mfa(user_input[CONF_UTILITY]): if select_utility(user_input[CONF_UTILITY]).accepts_mfa():
self.utility_info = user_input self.utility_info = user_input
return await self.async_step_mfa() return await self.async_step_mfa()
@ -154,7 +152,7 @@ class OpowerConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
vol.Required(CONF_USERNAME): self.reauth_entry.data[CONF_USERNAME], vol.Required(CONF_USERNAME): self.reauth_entry.data[CONF_USERNAME],
vol.Required(CONF_PASSWORD): str, vol.Required(CONF_PASSWORD): str,
} }
if _supports_mfa(self.reauth_entry.data[CONF_UTILITY]): if select_utility(self.reauth_entry.data[CONF_UTILITY]).accepts_mfa():
schema[vol.Optional(CONF_TOTP_SECRET)] = str schema[vol.Optional(CONF_TOTP_SECRET)] = str
return self.async_show_form( return self.async_show_form(
step_id="reauth_confirm", step_id="reauth_confirm",

View File

@ -55,7 +55,7 @@ class OpowerCoordinator(DataUpdateCoordinator[dict[str, Forecast]]):
entry_data[CONF_UTILITY], entry_data[CONF_UTILITY],
entry_data[CONF_USERNAME], entry_data[CONF_USERNAME],
entry_data[CONF_PASSWORD], entry_data[CONF_PASSWORD],
entry_data.get(CONF_TOTP_SECRET, None), entry_data.get(CONF_TOTP_SECRET),
) )
async def _async_update_data( async def _async_update_data(

View File

@ -7,5 +7,5 @@
"documentation": "https://www.home-assistant.io/integrations/opower", "documentation": "https://www.home-assistant.io/integrations/opower",
"iot_class": "cloud_polling", "iot_class": "cloud_polling",
"loggers": ["opower"], "loggers": ["opower"],
"requirements": ["opower==0.0.32"] "requirements": ["opower==0.0.33"]
} }

View File

@ -5,8 +5,13 @@
"data": { "data": {
"utility": "Utility name", "utility": "Utility name",
"username": "[%key:common::config_flow::data::username%]", "username": "[%key:common::config_flow::data::username%]",
"password": "[%key:common::config_flow::data::password%]", "password": "[%key:common::config_flow::data::password%]"
"totp_secret": "TOTP Secret (only for some utilities, see documentation)" }
},
"mfa": {
"description": "The TOTP secret below is not one of the 6 digit time-based numeric codes. It is a string of around 16 characters containing the shared secret that enables your authenticator app to generate the correct time-based code at the appropriate time. See the documentation.",
"data": {
"totp_secret": "TOTP Secret"
} }
}, },
"reauth_confirm": { "reauth_confirm": {
@ -14,7 +19,7 @@
"data": { "data": {
"username": "[%key:common::config_flow::data::username%]", "username": "[%key:common::config_flow::data::username%]",
"password": "[%key:common::config_flow::data::password%]", "password": "[%key:common::config_flow::data::password%]",
"totp_secret": "TOTP Secret (only for some utilities, see documentation)" "totp_secret": "TOTP Secret"
} }
} }
}, },

View File

@ -1374,7 +1374,7 @@ openwrt-luci-rpc==1.1.16
openwrt-ubus-rpc==0.0.2 openwrt-ubus-rpc==0.0.2
# homeassistant.components.opower # homeassistant.components.opower
opower==0.0.32 opower==0.0.33
# homeassistant.components.oralb # homeassistant.components.oralb
oralb-ble==0.17.6 oralb-ble==0.17.6

View File

@ -1040,7 +1040,7 @@ openerz-api==0.2.0
openhomedevice==2.2.0 openhomedevice==2.2.0
# homeassistant.components.opower # homeassistant.components.opower
opower==0.0.32 opower==0.0.33
# homeassistant.components.oralb # homeassistant.components.oralb
oralb-ble==0.17.6 oralb-ble==0.17.6

View File

@ -300,7 +300,7 @@ async def test_form_valid_reauth(
assert result["reason"] == "reauth_successful" assert result["reason"] == "reauth_successful"
await hass.async_block_till_done() await hass.async_block_till_done()
assert hass.config_entries.async_entries(DOMAIN)[0].data == { assert mock_config_entry.data == {
"utility": "Pacific Gas and Electric Company (PG&E)", "utility": "Pacific Gas and Electric Company (PG&E)",
"username": "test-username", "username": "test-username",
"password": "test-password2", "password": "test-password2",
@ -350,7 +350,7 @@ async def test_form_valid_reauth_with_mfa(
assert result["reason"] == "reauth_successful" assert result["reason"] == "reauth_successful"
await hass.async_block_till_done() await hass.async_block_till_done()
assert hass.config_entries.async_entries(DOMAIN)[0].data == { assert mock_config_entry.data == {
"utility": "Consolidated Edison (ConEd)", "utility": "Consolidated Edison (ConEd)",
"username": "test-username", "username": "test-username",
"password": "test-password2", "password": "test-password2",