8000 Fix Plex when using local tokenless authentication by jjlawren · Pull Request #37096 · home-assistant/core · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Fix Plex when using local tokenless authentication #37096

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jun 26, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files. 8000
Loading
Diff view
Diff view
20 changes: 14 additions & 6 deletions homeassistant/components/plex/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,12 +161,20 @@ async def async_play_on_sonos_service(service_call):
}
)

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):
< 8000 /td> 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

Expand Down
52 changes: 39 additions & 13 deletions homeassistant/components/plex/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ def __init__(self, hass, server_config, known_server_id=None, options=None):
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,
Expand All @@ -94,18 +95,35 @@ def __init__(self, hass, server_config, known_server_id=None, options=None):
@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
Expand All @@ -119,7 +137,7 @@ def connect(self):
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
]

Expand All @@ -145,14 +163,18 @@ def _connect_with_url():
)

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:
Expand All @@ -168,8 +190,12 @@ def _update_plexdirect_hostname():
_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:
Expand Down
14 changes: 8 additions & 6 deletions tests/components/plex/test_config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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)
Expand Down
79 changes: 64 additions & 15 deletions tests/components/plex/test_init.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
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

Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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()
Expand All @@ -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"
Expand Down Expand Up @@ -293,10 +290,33 @@ def __init__(self):

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", return_value=MockPlexAccount()):
), 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.asy F438 nc_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()):
assert await hass.config_entries.async_setup(old_entry.entry_id)
await hass.async_block_till_done()

Expand All @@ -307,3 +327,32 @@ def __init__(self):
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()
31 changes: 13 additions & 18 deletions tests/components/plex/test_media_players.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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()

Expand All @@ -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
Expand All @@ -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
8 changes: 2 additions & 6 deletions tests/components/plex/test_playback.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,19 +28,15 @@ 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()

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
Expand Down
Loading
0