Add support to reauthorize expired Plex tokens (#40469)

This commit is contained in:
jjlawren 2020-09-28 09:14:54 -05:00 committed by GitHub
parent eda68f127c
commit 3596eb39f2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 116 additions and 18 deletions

View file

@ -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,6 +225,9 @@ 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]
# 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

View file

@ -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
)
_LOGGER.debug("Valid config created for %s", plex_server.friendly_name)
return self.async_create_entry(
title=plex_server.friendly_name,
data={
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=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."""

View file

@ -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,

View file

@ -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": {

View file

@ -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"