Add reauthenticaion to mikrotik (#74454)

This commit is contained in:
Rami Mosleh 2022-10-02 06:37:24 +03:00 committed by GitHub
parent 7b8b73f826
commit 38a680c3eb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 166 additions and 6 deletions

View File

@ -2,7 +2,7 @@
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import Platform from homeassistant.const import Platform
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
from homeassistant.helpers import config_validation as cv, device_registry as dr from homeassistant.helpers import config_validation as cv, device_registry as dr
from .const import ATTR_MANUFACTURER, DOMAIN from .const import ATTR_MANUFACTURER, DOMAIN
@ -20,8 +20,8 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b
api = await hass.async_add_executor_job(get_api, dict(config_entry.data)) api = await hass.async_add_executor_job(get_api, dict(config_entry.data))
except CannotConnect as api_error: except CannotConnect as api_error:
raise ConfigEntryNotReady from api_error raise ConfigEntryNotReady from api_error
except LoginError: except LoginError as err:
return False raise ConfigEntryAuthFailed from err
coordinator = MikrotikDataUpdateCoordinator(hass, config_entry, api) coordinator = MikrotikDataUpdateCoordinator(hass, config_entry, api)
await hass.async_add_executor_job(coordinator.api.get_hub_details) await hass.async_add_executor_job(coordinator.api.get_hub_details)

View File

@ -1,6 +1,7 @@
"""Config flow for Mikrotik.""" """Config flow for Mikrotik."""
from __future__ import annotations from __future__ import annotations
from collections.abc import Mapping
from typing import Any from typing import Any
import voluptuous as vol import voluptuous as vol
@ -33,6 +34,7 @@ class MikrotikFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
"""Handle a Mikrotik config flow.""" """Handle a Mikrotik config flow."""
VERSION = 1 VERSION = 1
_reauth_entry: config_entries.ConfigEntry | None
@staticmethod @staticmethod
@callback @callback
@ -76,6 +78,49 @@ class MikrotikFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
errors=errors, errors=errors,
) )
async def async_step_reauth(self, data: Mapping[str, Any]) -> FlowResult:
"""Perform reauth upon an API authentication error."""
self._reauth_entry = self.hass.config_entries.async_get_entry(
self.context["entry_id"]
)
return await self.async_step_reauth_confirm()
async def async_step_reauth_confirm(
self, user_input: dict[str, str] | None = None
) -> FlowResult:
"""Confirm reauth dialog."""
errors = {}
assert self._reauth_entry
if user_input is not None:
user_input = {**self._reauth_entry.data, **user_input}
try:
await self.hass.async_add_executor_job(get_api, user_input)
except CannotConnect:
errors["base"] = "cannot_connect"
except LoginError:
errors[CONF_PASSWORD] = "invalid_auth"
if not errors:
self.hass.config_entries.async_update_entry(
self._reauth_entry,
data=user_input,
)
await self.hass.config_entries.async_reload(self._reauth_entry.entry_id)
return self.async_abort(reason="reauth_successful")
return self.async_show_form(
description_placeholders={
CONF_USERNAME: self._reauth_entry.data[CONF_USERNAME]
},
step_id="reauth_confirm",
data_schema=vol.Schema(
{
vol.Required(CONF_PASSWORD): str,
}
),
errors=errors,
)
class MikrotikOptionsFlowHandler(config_entries.OptionsFlow): class MikrotikOptionsFlowHandler(config_entries.OptionsFlow):
"""Handle Mikrotik options.""" """Handle Mikrotik options."""

View File

@ -13,6 +13,7 @@ from librouteros.login import plain as login_plain, token as login_token
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME, CONF_VERIFY_SSL from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME, CONF_VERIFY_SSL
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryAuthFailed
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from .const import ( from .const import (
@ -132,8 +133,10 @@ class MikrotikData:
# get new hub firmware version if updated # get new hub firmware version if updated
self.firmware = self.get_info(ATTR_FIRMWARE) self.firmware = self.get_info(ATTR_FIRMWARE)
except (CannotConnect, LoginError) as err: except CannotConnect as err:
raise UpdateFailed from err raise UpdateFailed from err
except LoginError as err:
raise ConfigEntryAuthFailed from err
if not device_list: if not device_list:
return return

View File

@ -11,6 +11,13 @@
"port": "[%key:common::config_flow::data::port%]", "port": "[%key:common::config_flow::data::port%]",
"verify_ssl": "Use ssl" "verify_ssl": "Use ssl"
} }
},
"reauth_confirm": {
"description": "The password for {username} is invalid.",
"title": "[%key:common::config_flow::title::reauth%]",
"data": {
"password": "[%key:common::config_flow::data::password%]"
}
} }
}, },
"error": { "error": {
@ -19,7 +26,8 @@
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]" "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]"
}, },
"abort": { "abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]" "already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]"
} }
}, },
"options": { "options": {

View File

@ -1,7 +1,8 @@
{ {
"config": { "config": {
"abort": { "abort": {
"already_configured": "Device is already configured" "already_configured": "Device is already configured",
"reauth_successful": "Re-authentication was successful"
}, },
"error": { "error": {
"cannot_connect": "Failed to connect", "cannot_connect": "Failed to connect",
@ -9,6 +10,13 @@
"name_exists": "Name exists" "name_exists": "Name exists"
}, },
"step": { "step": {
"reauth_confirm": {
"data": {
"password": "Password"
},
"description": "The password for {username} is invalid.",
"title": "Reauthenticate Integration"
},
"user": { "user": {
"data": { "data": {
"host": "Host", "host": "Host",

View File

@ -162,3 +162,99 @@ async def test_wrong_credentials(hass, auth_error):
CONF_USERNAME: "invalid_auth", CONF_USERNAME: "invalid_auth",
CONF_PASSWORD: "invalid_auth", CONF_PASSWORD: "invalid_auth",
} }
async def test_reauth_success(hass, api):
"""Test we can reauth."""
entry = MockConfigEntry(
domain=DOMAIN,
data=DEMO_USER_INPUT,
)
entry.add_to_hass(hass)
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={
"source": config_entries.SOURCE_REAUTH,
"entry_id": entry.entry_id,
},
data=DEMO_USER_INPUT,
)
assert result["type"] == "form"
assert result["step_id"] == "reauth_confirm"
assert result["description_placeholders"] == {CONF_USERNAME: "username"}
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
CONF_PASSWORD: "test-password",
},
)
assert result2["type"] == "abort"
assert result2["reason"] == "reauth_successful"
async def test_reauth_failed(hass, auth_error):
"""Test reauth fails due to wrong password."""
entry = MockConfigEntry(
domain=DOMAIN,
data=DEMO_USER_INPUT,
)
entry.add_to_hass(hass)
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={
"source": config_entries.SOURCE_REAUTH,
"entry_id": entry.entry_id,
},
data=DEMO_USER_INPUT,
)
assert result["type"] == "form"
assert result["step_id"] == "reauth_confirm"
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
CONF_PASSWORD: "test-wrong-password",
},
)
assert result2["type"] == "form"
assert result2["errors"] == {
CONF_PASSWORD: "invalid_auth",
}
async def test_reauth_failed_conn_error(hass, conn_error):
"""Test reauth failed due to connection error."""
entry = MockConfigEntry(
domain=DOMAIN,
data=DEMO_USER_INPUT,
)
entry.add_to_hass(hass)
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={
"source": config_entries.SOURCE_REAUTH,
"entry_id": entry.entry_id,
},
data=DEMO_USER_INPUT,
)
assert result["type"] == "form"
assert result["step_id"] == "reauth_confirm"
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
CONF_PASSWORD: "test-wrong-password",
},
)
assert result2["type"] == "form"
assert result2["errors"] == {"base": "cannot_connect"}