Fix Plex when using local tokenless authentication (#37096)

This commit is contained in:
jjlawren 2020-06-25 20:10:40 -04:00 committed by GitHub
parent f42eb0d5ca
commit fd1a8dd96c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 157 additions and 84 deletions

View File

@ -161,12 +161,20 @@ async def async_setup_entry(hass, entry):
}
)
hass.services.async_register(
PLEX_DOMAIN,
SERVICE_PLAY_ON_SONOS,
async_play_on_sonos_service,
schema=play_on_sonos_schema,
)
def get_plex_account(plex_server):
try:
return plex_server.account
except plexapi.exceptions.Unauthorized:
return None
plex_account = await hass.async_add_executor_job(get_plex_account, plex_server)
if plex_account:
hass.services.async_register(
PLEX_DOMAIN,
SERVICE_PLAY_ON_SONOS,
async_play_on_sonos_service,
schema=play_on_sonos_schema,
)
return True

View File

@ -76,6 +76,7 @@ class PlexServer:
self._plextv_clients = None
self._plextv_client_timestamp = 0
self._plextv_device_cache = {}
self._use_plex_tv = self._token is not None
self._version = None
self.async_update_platforms = Debouncer(
hass,
@ -94,18 +95,35 @@ class PlexServer:
@property
def account(self):
"""Return a MyPlexAccount instance."""
if not self._plex_account:
self._plex_account = plexapi.myplex.MyPlexAccount(token=self._token)
if not self._plex_account and self._use_plex_tv:
try:
self._plex_account = plexapi.myplex.MyPlexAccount(token=self._token)
except Unauthorized:
self._use_plex_tv = False
_LOGGER.error("Not authorized to access plex.tv with provided token")
raise
return self._plex_account
@property
def plextv_resources(self):
"""Return all resources linked to Plex account."""
if self.account is None:
return []
return self.account.resources()
def plextv_clients(self):
"""Return available clients linked to Plex account."""
if self.account is None:
return []
now = time.time()
if now - self._plextv_client_timestamp > PLEXTV_THROTTLE:
self._plextv_client_timestamp = now
resources = self.account.resources()
self._plextv_clients = [
x for x in resources if "player" in x.provides and x.presence
x
for x in self.plextv_resources
if "player" in x.provides and x.presence
]
_LOGGER.debug(
"Current available clients from plex.tv: %s", self._plextv_clients
@ -119,7 +137,7 @@ class PlexServer:
def _connect_with_token():
available_servers = [
(x.name, x.clientIdentifier)
for x in self.account.resources()
for x in self.plextv_resources
if "server" in x.provides
]
@ -145,14 +163,18 @@ class PlexServer:
)
def _update_plexdirect_hostname():
matching_server = [
matching_servers = [
x.name
for x in self.account.resources()
for x in self.plextv_resources
if x.clientIdentifier == self._server_id
][0]
self._plex_server = self.account.resource(matching_server).connect(
timeout=10
)
]
if matching_servers:
self._plex_server = self.account.resource(matching_servers[0]).connect(
timeout=10
)
return True
_LOGGER.error("Attempt to update plex.direct hostname failed")
return False
if self._url:
try:
@ -168,8 +190,12 @@ class PlexServer:
_LOGGER.warning(
"Plex SSL certificate's hostname changed, updating."
)
_update_plexdirect_hostname()
config_entry_update_needed = True
if _update_plexdirect_hostname():
config_entry_update_needed = True
else:
raise Unauthorized(
"New certificate cannot be validated with provided token"
)
else:
raise
else:

View File

@ -367,8 +367,8 @@ async def test_option_flow(hass):
)
with patch("plexapi.server.PlexServer", return_value=mock_plex_server), patch(
"homeassistant.components.plex.PlexWebsocket.listen"
) as mock_listen:
"plexapi.myplex.MyPlexAccount", return_value=MockPlexAccount()
), patch("homeassistant.components.plex.PlexWebsocket.listen") as mock_listen:
entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
@ -417,8 +417,8 @@ async def test_missing_option_flow(hass):
)
with patch("plexapi.server.PlexServer", return_value=mock_plex_server), patch(
"homeassistant.components.plex.PlexWebsocket.listen"
) as mock_listen:
"plexapi.myplex.MyPlexAccount", return_value=MockPlexAccount()
), patch("homeassistant.components.plex.PlexWebsocket.listen") as mock_listen:
entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
@ -471,8 +471,8 @@ async def test_option_flow_new_users_available(hass, caplog):
mock_plex_server = MockPlexServer(config_entry=entry)
with patch("plexapi.server.PlexServer", return_value=mock_plex_server), patch(
"homeassistant.components.plex.PlexWebsocket.listen"
):
"plexapi.myplex.MyPlexAccount", return_value=MockPlexAccount()
), patch("homeassistant.components.plex.PlexWebsocket.listen"):
entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
@ -741,6 +741,8 @@ async def test_setup_with_limited_credentials(hass):
), patch.object(
mock_plex_server, "systemAccounts", side_effect=plexapi.exceptions.Unauthorized
) as mock_accounts, patch(
"plexapi.myplex.MyPlexAccount", return_value=MockPlexAccount()
), patch(
"homeassistant.components.plex.PlexWebsocket.listen"
) as mock_listen:
entry.add_to_hass(hass)

View File

@ -13,7 +13,7 @@ from homeassistant.config_entries import (
ENTRY_STATE_SETUP_ERROR,
ENTRY_STATE_SETUP_RETRY,
)
from homeassistant.const import CONF_URL, CONF_VERIFY_SSL
from homeassistant.const import CONF_TOKEN, CONF_URL, CONF_VERIFY_SSL
from homeassistant.helpers.dispatcher import async_dispatcher_send
import homeassistant.util.dt as dt_util
@ -115,8 +115,8 @@ async def test_set_config_entry_unique_id(hass):
)
with patch("plexapi.server.PlexServer", return_value=mock_plex_server), patch(
"homeassistant.components.plex.PlexWebsocket.listen"
) as mock_listen:
"plexapi.myplex.MyPlexAccount", return_value=MockPlexAccount()
), patch("homeassistant.components.plex.PlexWebsocket.listen") as mock_listen:
entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
@ -181,8 +181,8 @@ async def test_setup_with_insecure_config_entry(hass):
)
with patch("plexapi.server.PlexServer", return_value=mock_plex_server), patch(
"homeassistant.components.plex.PlexWebsocket.listen"
) as mock_listen:
"plexapi.myplex.MyPlexAccount", return_value=MockPlexAccount()
), patch("homeassistant.components.plex.PlexWebsocket.listen") as mock_listen:
entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
@ -210,8 +210,8 @@ async def test_unload_config_entry(hass):
assert entry is config_entries[0]
with patch("plexapi.server.PlexServer", return_value=mock_plex_server), patch(
"homeassistant.components.plex.PlexWebsocket.listen"
) as mock_listen:
"plexapi.myplex.MyPlexAccount", return_value=MockPlexAccount()
), patch("homeassistant.components.plex.PlexWebsocket.listen") as mock_listen:
assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
assert mock_listen.called
@ -243,8 +243,8 @@ async def test_setup_with_photo_session(hass):
)
with patch("plexapi.server.PlexServer", return_value=mock_plex_server), patch(
"homeassistant.components.plex.PlexWebsocket.listen"
):
"plexapi.myplex.MyPlexAccount", return_value=MockPlexAccount()
), patch("homeassistant.components.plex.PlexWebsocket.listen"):
entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
@ -254,11 +254,8 @@ async def test_setup_with_photo_session(hass):
server_id = mock_plex_server.machineIdentifier
with patch("plexapi.myplex.MyPlexAccount", return_value=MockPlexAccount()):
async_dispatcher_send(
hass, const.PLEX_UPDATE_PLATFORMS_SIGNAL.format(server_id)
)
await hass.async_block_till_done()
async_dispatcher_send(hass, const.PLEX_UPDATE_PLATFORMS_SIGNAL.format(server_id))
await hass.async_block_till_done()
media_player = hass.states.get("media_player.plex_product_title")
assert media_player.state == "idle"
@ -293,10 +290,33 @@ async def test_setup_when_certificate_changed(hass):
new_entry = MockConfigEntry(domain=const.DOMAIN, data=DEFAULT_DATA)
# Test with account failure
with patch(
"plexapi.server.PlexServer", side_effect=WrongCertHostnameException
), patch(
"plexapi.myplex.MyPlexAccount", side_effect=plexapi.exceptions.Unauthorized
):
old_entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(old_entry.entry_id) is False
await hass.async_block_till_done()
assert old_entry.state == ENTRY_STATE_SETUP_ERROR
await hass.config_entries.async_unload(old_entry.entry_id)
# Test with no servers found
with patch(
"plexapi.server.PlexServer", side_effect=WrongCertHostnameException
), patch("plexapi.myplex.MyPlexAccount", return_value=MockPlexAccount(servers=0)):
assert await hass.config_entries.async_setup(old_entry.entry_id) is False
await hass.async_block_till_done()
assert old_entry.state == ENTRY_STATE_SETUP_ERROR
await hass.config_entries.async_unload(old_entry.entry_id)
# Test with success
with patch(
"plexapi.server.PlexServer", side_effect=WrongCertHostnameException
), patch("plexapi.myplex.MyPlexAccount", return_value=MockPlexAccount()):
old_entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(old_entry.entry_id)
await hass.async_block_till_done()
@ -307,3 +327,32 @@ async def test_setup_when_certificate_changed(hass):
old_entry.data[const.PLEX_SERVER_CONFIG][CONF_URL]
== new_entry.data[const.PLEX_SERVER_CONFIG][CONF_URL]
)
async def test_tokenless_server(hass):
"""Test setup with a server with token auth disabled."""
mock_plex_server = MockPlexServer()
TOKENLESS_DATA = copy.deepcopy(DEFAULT_DATA)
TOKENLESS_DATA[const.PLEX_SERVER_CONFIG].pop(CONF_TOKEN, None)
entry = MockConfigEntry(
domain=const.DOMAIN,
data=TOKENLESS_DATA,
options=DEFAULT_OPTIONS,
unique_id=DEFAULT_DATA["server_id"],
)
with patch("plexapi.server.PlexServer", return_value=mock_plex_server), patch(
"plexapi.myplex.MyPlexAccount", side_effect=plexapi.exceptions.Unauthorized
), patch("homeassistant.components.plex.PlexWebsocket.listen"):
entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
assert entry.state == ENTRY_STATE_LOADED
server_id = mock_plex_server.machineIdentifier
async_dispatcher_send(hass, const.PLEX_UPDATE_PLATFORMS_SIGNAL.format(server_id))
await hass.async_block_till_done()

View File

@ -23,8 +23,8 @@ async def test_plex_tv_clients(hass):
mock_plex_account = MockPlexAccount()
with patch("plexapi.server.PlexServer", return_value=mock_plex_server), patch(
"homeassistant.components.plex.PlexWebsocket.listen"
):
"plexapi.myplex.MyPlexAccount", return_value=mock_plex_account
), patch("homeassistant.components.plex.PlexWebsocket.listen"):
entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
@ -37,9 +37,7 @@ async def test_plex_tv_clients(hass):
for x in mock_plex_account.resources()
if x.name.startswith("plex.tv Resource Player")
)
with patch(
"plexapi.myplex.MyPlexAccount", return_value=mock_plex_account
), patch.object(resource, "connect", side_effect=NotFound):
with patch.object(resource, "connect", side_effect=NotFound):
await plex_server._async_update_platforms()
await hass.async_block_till_done()
@ -49,16 +47,15 @@ async def test_plex_tv_clients(hass):
await hass.config_entries.async_unload(entry.entry_id)
with patch("plexapi.server.PlexServer", return_value=mock_plex_server), patch(
"homeassistant.components.plex.PlexWebsocket.listen"
):
"plexapi.myplex.MyPlexAccount", return_value=mock_plex_account
), patch("homeassistant.components.plex.PlexWebsocket.listen"):
assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
plex_server = hass.data[DOMAIN][SERVERS][server_id]
with patch("plexapi.myplex.MyPlexAccount", return_value=mock_plex_account):
await plex_server._async_update_platforms()
await hass.async_block_till_done()
await plex_server._async_update_platforms()
await hass.async_block_till_done()
media_players_after = len(hass.states.async_entity_ids("media_player"))
assert media_players_after == media_players_before + 1
@ -70,22 +67,20 @@ async def test_plex_tv_clients(hass):
mock_plex_server.clear_sessions()
with patch("plexapi.server.PlexServer", return_value=mock_plex_server), patch(
"homeassistant.components.plex.PlexWebsocket.listen"
):
"plexapi.myplex.MyPlexAccount", return_value=mock_plex_account
), patch("homeassistant.components.plex.PlexWebsocket.listen"):
assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
plex_server = hass.data[DOMAIN][SERVERS][server_id]
with patch("plexapi.myplex.MyPlexAccount", return_value=mock_plex_account):
await plex_server._async_update_platforms()
await hass.async_block_till_done()
await plex_server._async_update_platforms()
await hass.async_block_till_done()
assert len(hass.states.async_entity_ids("media_player")) == 1
# Ensure cache gets called
with patch("plexapi.myplex.MyPlexAccount", return_value=mock_plex_account):
await plex_server._async_update_platforms()
await hass.async_block_till_done()
await plex_server._async_update_platforms()
await hass.async_block_till_done()
assert len(hass.states.async_entity_ids("media_player")) == 1

View File

@ -28,8 +28,8 @@ async def test_sonos_playback(hass):
mock_plex_server = MockPlexServer(config_entry=entry)
with patch("plexapi.server.PlexServer", return_value=mock_plex_server), patch(
"homeassistant.components.plex.PlexWebsocket.listen"
):
"plexapi.myplex.MyPlexAccount", return_value=MockPlexAccount()
), patch("homeassistant.components.plex.PlexWebsocket.listen"):
entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
@ -37,10 +37,6 @@ async def test_sonos_playback(hass):
server_id = mock_plex_server.machineIdentifier
loaded_server = hass.data[DOMAIN][SERVERS][server_id]
with patch("plexapi.myplex.MyPlexAccount", return_value=MockPlexAccount()):
# Access and cache PlexAccount
assert loaded_server.account
# Test Sonos integration lookup failure
with patch.object(
hass.components.sonos, "get_coordinator_id", side_effect=HomeAssistantError

View File

@ -55,17 +55,16 @@ async def test_new_users_available(hass):
mock_plex_server = MockPlexServer(config_entry=entry)
with patch("plexapi.server.PlexServer", return_value=mock_plex_server), patch(
"homeassistant.components.plex.PlexWebsocket.listen"
):
"plexapi.myplex.MyPlexAccount", return_value=MockPlexAccount()
), patch("homeassistant.components.plex.PlexWebsocket.listen"):
entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
server_id = mock_plex_server.machineIdentifier
with patch("plexapi.myplex.MyPlexAccount", return_value=MockPlexAccount()):
async_dispatcher_send(hass, PLEX_UPDATE_PLATFORMS_SIGNAL.format(server_id))
await hass.async_block_till_done()
async_dispatcher_send(hass, PLEX_UPDATE_PLATFORMS_SIGNAL.format(server_id))
await hass.async_block_till_done()
monitored_users = hass.data[DOMAIN][SERVERS][server_id].option_monitored_users
@ -95,17 +94,16 @@ async def test_new_ignored_users_available(hass, caplog):
mock_plex_server = MockPlexServer(config_entry=entry)
with patch("plexapi.server.PlexServer", return_value=mock_plex_server), patch(
"homeassistant.components.plex.PlexWebsocket.listen"
):
"plexapi.myplex.MyPlexAccount", return_value=MockPlexAccount()
), patch("homeassistant.components.plex.PlexWebsocket.listen"):
entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
server_id = mock_plex_server.machineIdentifier
with patch("plexapi.myplex.MyPlexAccount", return_value=MockPlexAccount()):
async_dispatcher_send(hass, PLEX_UPDATE_PLATFORMS_SIGNAL.format(server_id))
await hass.async_block_till_done()
async_dispatcher_send(hass, PLEX_UPDATE_PLATFORMS_SIGNAL.format(server_id))
await hass.async_block_till_done()
monitored_users = hass.data[DOMAIN][SERVERS][server_id].option_monitored_users
@ -248,17 +246,16 @@ async def test_ignore_plex_web_client(hass):
mock_plex_server = MockPlexServer(config_entry=entry)
with patch("plexapi.server.PlexServer", return_value=mock_plex_server), patch(
"homeassistant.components.plex.PlexWebsocket.listen"
):
"plexapi.myplex.MyPlexAccount", return_value=MockPlexAccount(players=0)
), patch("homeassistant.components.plex.PlexWebsocket.listen"):
entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
server_id = mock_plex_server.machineIdentifier
with patch("plexapi.myplex.MyPlexAccount", return_value=MockPlexAccount(players=0)):
async_dispatcher_send(hass, PLEX_UPDATE_PLATFORMS_SIGNAL.format(server_id))
await hass.async_block_till_done()
async_dispatcher_send(hass, PLEX_UPDATE_PLATFORMS_SIGNAL.format(server_id))
await hass.async_block_till_done()
sensor = hass.states.get("sensor.plex_plex_server_1")
assert sensor.state == str(len(mock_plex_server.accounts))
@ -281,8 +278,8 @@ async def test_media_lookups(hass):
mock_plex_server = MockPlexServer(config_entry=entry)
with patch("plexapi.server.PlexServer", return_value=mock_plex_server), patch(
"homeassistant.components.plex.PlexWebsocket.listen"
):
"plexapi.myplex.MyPlexAccount", return_value=MockPlexAccount()
), patch("homeassistant.components.plex.PlexWebsocket.listen"):
entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
@ -291,9 +288,9 @@ async def test_media_lookups(hass):
loaded_server = hass.data[DOMAIN][SERVERS][server_id]
# Plex Key searches
with patch("plexapi.myplex.MyPlexAccount", return_value=MockPlexAccount()):
async_dispatcher_send(hass, PLEX_UPDATE_PLATFORMS_SIGNAL.format(server_id))
await hass.async_block_till_done()
async_dispatcher_send(hass, PLEX_UPDATE_PLATFORMS_SIGNAL.format(server_id))
await hass.async_block_till_done()
media_player_id = hass.states.async_entity_ids("media_player")[0]
with patch("homeassistant.components.plex.PlexServer.create_playqueue"):
assert await hass.services.async_call(