Add support to reauthorize expired Plex tokens (#40469)
This commit is contained in:
parent
eda68f127c
commit
3596eb39f2
5 changed files with 116 additions and 18 deletions
|
@ -14,8 +14,10 @@ from homeassistant.components.media_player.const import (
|
|||
ATTR_MEDIA_CONTENT_ID,
|
||||
ATTR_MEDIA_CONTENT_TYPE,
|
||||
)
|
||||
from homeassistant.config_entries import SOURCE_REAUTH
|
||||
from homeassistant.const import (
|
||||
ATTR_ENTITY_ID,
|
||||
CONF_SOURCE,
|
||||
CONF_URL,
|
||||
CONF_VERIFY_SSL,
|
||||
EVENT_HOMEASSISTANT_STOP,
|
||||
|
@ -75,7 +77,11 @@ async def async_setup_entry(hass, entry):
|
|||
hass.config_entries.async_update_entry(entry, options=options)
|
||||
|
||||
plex_server = PlexServer(
|
||||
hass, server_config, entry.data[CONF_SERVER_IDENTIFIER], entry.options
|
||||
hass,
|
||||
server_config,
|
||||
entry.data[CONF_SERVER_IDENTIFIER],
|
||||
entry.options,
|
||||
entry.entry_id,
|
||||
)
|
||||
try:
|
||||
await hass.async_add_executor_job(plex_server.connect)
|
||||
|
@ -95,9 +101,21 @@ async def async_setup_entry(hass, entry):
|
|||
error,
|
||||
)
|
||||
raise ConfigEntryNotReady from error
|
||||
except plexapi.exceptions.Unauthorized:
|
||||
hass.async_create_task(
|
||||
hass.config_entries.flow.async_init(
|
||||
PLEX_DOMAIN,
|
||||
context={CONF_SOURCE: SOURCE_REAUTH},
|
||||
data={**entry.data, "config_entry_id": entry.entry_id},
|
||||
)
|
||||
)
|
||||
_LOGGER.error(
|
||||
"Token not accepted, please reauthenticate Plex server '%s'",
|
||||
entry.data[CONF_SERVER],
|
||||
)
|
||||
return False
|
||||
except (
|
||||
plexapi.exceptions.BadRequest,
|
||||
plexapi.exceptions.Unauthorized,
|
||||
plexapi.exceptions.NotFound,
|
||||
) as error:
|
||||
_LOGGER.error(
|
||||
|
@ -207,7 +225,10 @@ async def async_unload_entry(hass, entry):
|
|||
async def async_options_updated(hass, entry):
|
||||
"""Triggered by config entry options updates."""
|
||||
server_id = entry.data[CONF_SERVER_IDENTIFIER]
|
||||
hass.data[PLEX_DOMAIN][SERVERS][server_id].options = entry.options
|
||||
|
||||
# Guard incomplete setup during reauth flows
|
||||
if server_id in hass.data[PLEX_DOMAIN][SERVERS]:
|
||||
hass.data[PLEX_DOMAIN][SERVERS][server_id].options = entry.options
|
||||
|
||||
|
||||
def play_on_sonos(hass, service_call):
|
||||
|
|
|
@ -16,6 +16,7 @@ from homeassistant.const import (
|
|||
CONF_CLIENT_ID,
|
||||
CONF_HOST,
|
||||
CONF_PORT,
|
||||
CONF_SOURCE,
|
||||
CONF_SSL,
|
||||
CONF_TOKEN,
|
||||
CONF_URL,
|
||||
|
@ -70,7 +71,7 @@ async def async_discover(hass):
|
|||
for server_data in gdm.entries:
|
||||
await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": config_entries.SOURCE_INTEGRATION_DISCOVERY},
|
||||
context={CONF_SOURCE: config_entries.SOURCE_INTEGRATION_DISCOVERY},
|
||||
data=server_data,
|
||||
)
|
||||
|
||||
|
@ -95,6 +96,7 @@ class PlexFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
|||
self.token = None
|
||||
self.client_id = None
|
||||
self._manual = False
|
||||
self._entry_id = None
|
||||
|
||||
async def async_step_user(
|
||||
self, user_input=None, errors=None
|
||||
|
@ -209,10 +211,6 @@ class PlexFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
|||
return await self.async_step_user(errors=errors)
|
||||
|
||||
server_id = plex_server.machine_identifier
|
||||
|
||||
await self.async_set_unique_id(server_id)
|
||||
self._abort_if_unique_id_configured()
|
||||
|
||||
url = plex_server.url_in_use
|
||||
token = server_config.get(CONF_TOKEN)
|
||||
|
||||
|
@ -226,16 +224,28 @@ class PlexFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
|||
CONF_VERIFY_SSL, DEFAULT_VERIFY_SSL
|
||||
)
|
||||
|
||||
data = {
|
||||
CONF_SERVER: plex_server.friendly_name,
|
||||
CONF_SERVER_IDENTIFIER: server_id,
|
||||
PLEX_SERVER_CONFIG: entry_config,
|
||||
}
|
||||
|
||||
await self.async_set_unique_id(server_id)
|
||||
if (
|
||||
self.context[CONF_SOURCE] # pylint: disable=no-member
|
||||
== config_entries.SOURCE_REAUTH
|
||||
):
|
||||
entry = self.hass.config_entries.async_get_entry(self._entry_id)
|
||||
self.hass.config_entries.async_update_entry(entry, data=data)
|
||||
_LOGGER.debug("Updated config entry for %s", plex_server.friendly_name)
|
||||
await self.hass.config_entries.async_reload(entry.entry_id)
|
||||
return self.async_abort(reason="reauth_successful")
|
||||
|
||||
self._abort_if_unique_id_configured()
|
||||
|
||||
_LOGGER.debug("Valid config created for %s", plex_server.friendly_name)
|
||||
|
||||
return self.async_create_entry(
|
||||
title=plex_server.friendly_name,
|
||||
data={
|
||||
CONF_SERVER: plex_server.friendly_name,
|
||||
CONF_SERVER_IDENTIFIER: server_id,
|
||||
PLEX_SERVER_CONFIG: entry_config,
|
||||
},
|
||||
)
|
||||
return self.async_create_entry(title=plex_server.friendly_name, data=data)
|
||||
|
||||
async def async_step_select_server(self, user_input=None):
|
||||
"""Use selected Plex server."""
|
||||
|
@ -316,6 +326,12 @@ class PlexFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
|||
server_config = {CONF_TOKEN: self.token}
|
||||
return await self.async_step_server_validate(server_config)
|
||||
|
||||
async def async_step_reauth(self, data):
|
||||
"""Handle a reauthorization flow request."""
|
||||
self.current_login = dict(data)
|
||||
self._entry_id = self.current_login.pop("config_entry_id")
|
||||
return await self.async_step_user()
|
||||
|
||||
|
||||
class PlexOptionsFlowHandler(config_entries.OptionsFlow):
|
||||
"""Handle Plex options."""
|
||||
|
|
|
@ -62,9 +62,12 @@ plexapi.X_PLEX_VERSION = X_PLEX_VERSION
|
|||
class PlexServer:
|
||||
"""Manages a single Plex server connection."""
|
||||
|
||||
def __init__(self, hass, server_config, known_server_id=None, options=None):
|
||||
def __init__(
|
||||
self, hass, server_config, known_server_id=None, options=None, entry_id=None
|
||||
):
|
||||
"""Initialize a Plex server instance."""
|
||||
self.hass = hass
|
||||
self.entry_id = entry_id
|
||||
self._plex_account = None
|
||||
self._plex_server = None
|
||||
self._created_clients = set()
|
||||
|
@ -270,6 +273,12 @@ class PlexServer:
|
|||
devices, sessions, plextv_clients = await self.hass.async_add_executor_job(
|
||||
self._fetch_platform_data
|
||||
)
|
||||
except plexapi.exceptions.Unauthorized:
|
||||
_LOGGER.debug(
|
||||
"Token has expired for '%s', reloading integration", self.friendly_name
|
||||
)
|
||||
await self.hass.config_entries.async_reload(self.entry_id)
|
||||
return
|
||||
except (
|
||||
plexapi.exceptions.BadRequest,
|
||||
requests.exceptions.RequestException,
|
||||
|
|
|
@ -41,8 +41,9 @@
|
|||
"all_configured": "All linked servers already configured",
|
||||
"already_configured": "This Plex server is already configured",
|
||||
"already_in_progress": "Plex is being configured",
|
||||
"reauth_successful": "Successfully reauthenticated",
|
||||
"token_request_timeout": "Timed out obtaining token",
|
||||
"unknown": "Failed for unknown reason"
|
||||
"unknown": "[%key:common::config_flow::error::unknown%]"
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
|
|
|
@ -24,6 +24,7 @@ from homeassistant.config import async_process_ha_core_config
|
|||
from homeassistant.config_entries import (
|
||||
ENTRY_STATE_LOADED,
|
||||
SOURCE_INTEGRATION_DISCOVERY,
|
||||
SOURCE_REAUTH,
|
||||
)
|
||||
from homeassistant.const import (
|
||||
CONF_HOST,
|
||||
|
@ -723,3 +724,53 @@ async def test_integration_discovery(hass):
|
|||
== mock_gdm.entries[0]["data"]["Resource-Identifier"]
|
||||
)
|
||||
assert flow["step_id"] == "user"
|
||||
|
||||
|
||||
async def test_trigger_reauth(hass, entry, mock_plex_server, mock_websocket):
|
||||
"""Test setup and reauthorization of a Plex token."""
|
||||
await async_process_ha_core_config(
|
||||
hass,
|
||||
{"internal_url": "http://example.local:8123"},
|
||||
)
|
||||
|
||||
assert entry.state == ENTRY_STATE_LOADED
|
||||
|
||||
with patch.object(
|
||||
mock_plex_server, "clients", side_effect=plexapi.exceptions.Unauthorized
|
||||
), patch("plexapi.server.PlexServer", side_effect=plexapi.exceptions.Unauthorized):
|
||||
trigger_plex_update(mock_websocket)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert len(hass.config_entries.async_entries(DOMAIN)) == 1
|
||||
assert entry.state != ENTRY_STATE_LOADED
|
||||
|
||||
flows = hass.config_entries.flow.async_progress()
|
||||
assert len(flows) == 1
|
||||
assert flows[0]["context"]["source"] == SOURCE_REAUTH
|
||||
|
||||
flow_id = flows[0]["flow_id"]
|
||||
|
||||
with patch("plexapi.myplex.MyPlexAccount", return_value=MockPlexAccount()), patch(
|
||||
"plexapi.server.PlexServer", return_value=mock_plex_server
|
||||
), patch("plexauth.PlexAuth.initiate_auth"), patch(
|
||||
"plexauth.PlexAuth.token", return_value="BRAND_NEW_TOKEN"
|
||||
):
|
||||
result = await hass.config_entries.flow.async_configure(flow_id, user_input={})
|
||||
assert result["type"] == "external"
|
||||
|
||||
result = await hass.config_entries.flow.async_configure(result["flow_id"])
|
||||
assert result["type"] == "external_done"
|
||||
|
||||
result = await hass.config_entries.flow.async_configure(result["flow_id"])
|
||||
assert result["type"] == "abort"
|
||||
assert result["reason"] == "reauth_successful"
|
||||
assert result["flow_id"] == flow_id
|
||||
|
||||
assert len(hass.config_entries.flow.async_progress()) == 0
|
||||
assert len(hass.config_entries.async_entries(DOMAIN)) == 1
|
||||
|
||||
assert entry.state == ENTRY_STATE_LOADED
|
||||
assert entry.data[CONF_SERVER] == mock_plex_server.friendlyName
|
||||
assert entry.data[CONF_SERVER_IDENTIFIER] == mock_plex_server.machineIdentifier
|
||||
assert entry.data[PLEX_SERVER_CONFIG][CONF_URL] == mock_plex_server._baseurl
|
||||
assert entry.data[PLEX_SERVER_CONFIG][CONF_TOKEN] == "BRAND_NEW_TOKEN"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue