Add re-auth flow to nextcloud (#90472)

pull/90474/head
Michael 2023-03-29 21:46:08 +02:00 committed by GitHub
parent 28d045cf75
commit cf0550f5c2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 221 additions and 12 deletions

View File

@ -1,7 +1,12 @@
"""The Nextcloud integration."""
import logging
from nextcloudmonitor import NextcloudMonitor, NextcloudMonitorError
from nextcloudmonitor import (
NextcloudMonitor,
NextcloudMonitorAuthorizationError,
NextcloudMonitorConnectionError,
NextcloudMonitorRequestError,
)
import voluptuous as vol
from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
@ -14,6 +19,7 @@ from homeassistant.const import (
Platform,
)
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue
from homeassistant.helpers.typing import ConfigType
@ -82,9 +88,10 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
try:
ncm = await hass.async_add_executor_job(_connect_nc)
except NextcloudMonitorError:
_LOGGER.error("Nextcloud setup failed - Check configuration")
return False
except NextcloudMonitorAuthorizationError as ex:
raise ConfigEntryAuthFailed from ex
except (NextcloudMonitorConnectionError, NextcloudMonitorRequestError) as ex:
raise ConfigEntryNotReady from ex
coordinator = NextcloudDataUpdateCoordinator(
hass,

View File

@ -1,13 +1,20 @@
"""Config flow to configure the Nextcloud integration."""
from __future__ import annotations
from collections.abc import Mapping
import logging
from typing import Any
from nextcloudmonitor import NextcloudMonitor, NextcloudMonitorError
from nextcloudmonitor import (
NextcloudMonitor,
NextcloudMonitorAuthorizationError,
NextcloudMonitorConnectionError,
NextcloudMonitorError,
NextcloudMonitorRequestError,
)
import voluptuous as vol
from homeassistant.config_entries import ConfigFlow
from homeassistant.config_entries import ConfigEntry, ConfigFlow
from homeassistant.const import CONF_PASSWORD, CONF_URL, CONF_USERNAME, CONF_VERIFY_SSL
from homeassistant.data_entry_flow import FlowResult
@ -21,6 +28,13 @@ DATA_SCHEMA_USER = vol.Schema(
vol.Required(CONF_VERIFY_SSL, default=DEFAULT_VERIFY_SSL): bool,
}
)
DATA_SCHEMA_REAUTH = vol.Schema(
{
vol.Required(CONF_USERNAME): str,
vol.Required(CONF_PASSWORD): str,
}
)
_LOGGER = logging.getLogger(__name__)
@ -29,6 +43,8 @@ class NextcloudConfigFlow(ConfigFlow, domain=DOMAIN):
VERSION = 1
_entry: ConfigEntry | None = None
def _try_connect_nc(self, user_input: dict) -> NextcloudMonitor:
"""Try to connect to nextcloud server."""
return NextcloudMonitor(
@ -67,7 +83,9 @@ class NextcloudConfigFlow(ConfigFlow, domain=DOMAIN):
self._async_abort_entries_match({CONF_URL: user_input.get(CONF_URL)})
try:
await self.hass.async_add_executor_job(self._try_connect_nc, user_input)
except NextcloudMonitorError:
except NextcloudMonitorAuthorizationError:
errors["base"] = "invalid_auth"
except (NextcloudMonitorConnectionError, NextcloudMonitorRequestError):
errors["base"] = "connection_error"
else:
return self.async_create_entry(
@ -79,3 +97,43 @@ class NextcloudConfigFlow(ConfigFlow, domain=DOMAIN):
return self.async_show_form(
step_id="user", data_schema=data_schema, errors=errors
)
async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult:
"""Handle flow upon an API authentication error."""
self._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, Any] | None = None
) -> FlowResult:
"""Handle reauthorization flow."""
errors = {}
assert self._entry is not None
if user_input is not None:
try:
await self.hass.async_add_executor_job(
self._try_connect_nc, {**self._entry.data, **user_input}
)
except NextcloudMonitorAuthorizationError:
errors["base"] = "invalid_auth"
except (NextcloudMonitorConnectionError, NextcloudMonitorRequestError):
errors["base"] = "connection_error"
else:
self.hass.config_entries.async_update_entry(
self._entry,
data={**self._entry.data, **user_input},
)
await self.hass.config_entries.async_reload(self._entry.entry_id)
return self.async_abort(reason="reauth_successful")
data_schema = self.add_suggested_values_to_schema(
DATA_SCHEMA_REAUTH,
{CONF_USERNAME: self._entry.data[CONF_USERNAME], **(user_input or {})},
)
return self.async_show_form(
step_id="reauth_confirm",
data_schema=data_schema,
description_placeholders={"url": self._entry.data[CONF_URL]},
errors=errors,
)

View File

@ -10,14 +10,23 @@
"password": "[%key:common::config_flow::data::password%]",
"verify_ssl": "[%key:common::config_flow::data::verify_ssl%]"
}
},
"reauth_confirm": {
"description": "Update your login information for {url}.",
"data": {
"username": "[%key:common::config_flow::data::username%]",
"password": "[%key:common::config_flow::data::password%]"
}
}
},
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
"connection_error_during_import": "Connection error occured during yaml configuration import"
"connection_error_during_import": "Connection error occured during yaml configuration import",
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]"
},
"error": {
"connection_error": "[%key:common::config_flow::error::cannot_connect%]"
"connection_error": "[%key:common::config_flow::error::cannot_connect%]",
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]"
}
},
"issues": {

View File

@ -7,6 +7,14 @@
'verify_ssl': True,
})
# ---
# name: test_reauth
dict({
'password': 'other_password',
'url': 'nc_url',
'username': 'other_user',
'verify_ssl': True,
})
# ---
# name: test_user_create_entry
dict({
'password': 'nc_pass',

View File

@ -1,12 +1,17 @@
"""Tests for the Nextcloud config flow."""
from unittest.mock import Mock, patch
from nextcloudmonitor import NextcloudMonitorError
from nextcloudmonitor import (
NextcloudMonitorAuthorizationError,
NextcloudMonitorConnectionError,
NextcloudMonitorError,
NextcloudMonitorRequestError,
)
import pytest
from syrupy.assertion import SnapshotAssertion
from homeassistant.components.nextcloud import DOMAIN
from homeassistant.config_entries import SOURCE_IMPORT, SOURCE_USER
from homeassistant.config_entries import SOURCE_IMPORT, SOURCE_REAUTH, SOURCE_USER
from homeassistant.const import CONF_PASSWORD, CONF_URL, CONF_USERNAME, CONF_VERIFY_SSL
from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import FlowResultType
@ -27,6 +32,7 @@ async def test_user_create_entry(
hass: HomeAssistant, mock_nextcloud_monitor: Mock, snapshot: SnapshotAssertion
) -> None:
"""Test that the user step works."""
# start user flow
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_USER}
)
@ -34,9 +40,24 @@ async def test_user_create_entry(
assert result["step_id"] == "user"
assert result["errors"] == {}
# test NextcloudMonitorAuthorizationError
with patch(
"homeassistant.components.nextcloud.config_flow.NextcloudMonitor",
side_effect=NextcloudMonitorError,
side_effect=NextcloudMonitorAuthorizationError,
):
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
VALID_CONFIG,
)
await hass.async_block_till_done()
assert result["type"] == FlowResultType.FORM
assert result["step_id"] == "user"
assert result["errors"] == {"base": "invalid_auth"}
# test NextcloudMonitorConnectionError
with patch(
"homeassistant.components.nextcloud.config_flow.NextcloudMonitor",
side_effect=NextcloudMonitorConnectionError,
):
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
@ -47,6 +68,21 @@ async def test_user_create_entry(
assert result["step_id"] == "user"
assert result["errors"] == {"base": "connection_error"}
# test NextcloudMonitorRequestError
with patch(
"homeassistant.components.nextcloud.config_flow.NextcloudMonitor",
side_effect=NextcloudMonitorRequestError,
):
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
VALID_CONFIG,
)
await hass.async_block_till_done()
assert result["type"] == FlowResultType.FORM
assert result["step_id"] == "user"
assert result["errors"] == {"base": "connection_error"}
# test success
with patch(
"homeassistant.components.nextcloud.config_flow.NextcloudMonitor",
return_value=mock_nextcloud_monitor,
@ -154,3 +190,94 @@ async def test_import_connection_error(hass: HomeAssistant) -> None:
await hass.async_block_till_done()
assert result["type"] == FlowResultType.ABORT
assert result["reason"] == "connection_error_during_import"
async def test_reauth(
hass: HomeAssistant, mock_nextcloud_monitor: Mock, snapshot: SnapshotAssertion
) -> None:
"""Test that the re-auth flow works."""
entry = MockConfigEntry(
domain=DOMAIN,
title="nc_url",
unique_id="nc_url",
data=VALID_CONFIG,
)
entry.add_to_hass(hass)
# start reauth flow
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_REAUTH, "entry_id": entry.entry_id},
data=entry.data,
)
assert result["type"] == FlowResultType.FORM
assert result["step_id"] == "reauth_confirm"
# test NextcloudMonitorAuthorizationError
with patch(
"homeassistant.components.nextcloud.config_flow.NextcloudMonitor",
side_effect=NextcloudMonitorAuthorizationError,
):
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
CONF_USERNAME: "other_user",
CONF_PASSWORD: "other_password",
},
)
await hass.async_block_till_done()
assert result["type"] == FlowResultType.FORM
assert result["step_id"] == "reauth_confirm"
assert result["errors"] == {"base": "invalid_auth"}
# test NextcloudMonitorConnectionError
with patch(
"homeassistant.components.nextcloud.config_flow.NextcloudMonitor",
side_effect=NextcloudMonitorConnectionError,
):
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
CONF_USERNAME: "other_user",
CONF_PASSWORD: "other_password",
},
)
await hass.async_block_till_done()
assert result["type"] == FlowResultType.FORM
assert result["step_id"] == "reauth_confirm"
assert result["errors"] == {"base": "connection_error"}
# test NextcloudMonitorRequestError
with patch(
"homeassistant.components.nextcloud.config_flow.NextcloudMonitor",
side_effect=NextcloudMonitorRequestError,
):
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
CONF_USERNAME: "other_user",
CONF_PASSWORD: "other_password",
},
)
await hass.async_block_till_done()
assert result["type"] == FlowResultType.FORM
assert result["step_id"] == "reauth_confirm"
assert result["errors"] == {"base": "connection_error"}
# test success
with patch(
"homeassistant.components.nextcloud.config_flow.NextcloudMonitor",
return_value=mock_nextcloud_monitor,
):
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
CONF_USERNAME: "other_user",
CONF_PASSWORD: "other_password",
},
)
await hass.async_block_till_done()
assert result["type"] == FlowResultType.ABORT
assert result["reason"] == "reauth_successful"
assert entry.data == snapshot