From 61ede66ad5e2d0d21cf9dd5895226b263b67a1d5 Mon Sep 17 00:00:00 2001 From: Michael Shepanski Date: Thu, 4 Jan 2018 21:44:35 -0500 Subject: [PATCH] Dont include token in URLs unless show_secrets set in config; All functions that return a URL such as stream urls and thumbnails still include token --- plexapi/alert.py | 2 +- plexapi/audio.py | 11 +++++------ plexapi/base.py | 7 +++---- plexapi/client.py | 9 ++++++--- plexapi/server.py | 15 +++++++++------ plexapi/utils.py | 6 ++++-- plexapi/video.py | 11 +++++------ tests/test_server.py | 6 +++--- tests/test_utils.py | 8 ++++---- 9 files changed, 40 insertions(+), 35 deletions(-) diff --git a/plexapi/alert.py b/plexapi/alert.py index 510af906..dc1c76e1 100644 --- a/plexapi/alert.py +++ b/plexapi/alert.py @@ -29,7 +29,7 @@ class AlertListener(threading.Thread): def run(self): # create the websocket connection - url = self._server.url(self.key).replace('http', 'ws') + url = self._server.url(self.key, includeToken=True).replace('http', 'ws') log.info('Starting AlertListener: %s', url) self._ws = websocket.WebSocketApp(url, on_message=self._onMessage, on_error=self._onError) diff --git a/plexapi/audio.py b/plexapi/audio.py index 49c0c7cd..f944dad0 100644 --- a/plexapi/audio.py +++ b/plexapi/audio.py @@ -45,18 +45,17 @@ class Audio(PlexPartialObject): def thumbUrl(self): """ Return url to for the thumbnail image. """ key = self.firstAttr('thumb', 'parentThumb', 'granparentThumb') - return self._server.url(key) if key else None + return self._server.url(key, includeToken=True) if key else None @property def artUrl(self): - """ Return the first first art url starting on the most specific for that item.""" + """ Return the first art url starting on the most specific for that item.""" art = self.firstAttr('art', 'grandparentArt') - return self._server.url(art) if art else None + return self._server.url(art, includeToken=True) if art else None def url(self, part): - """ Returns the full URL for something. Typically used for getting a specific image. """ - if part: - return self._server.url(part) + """ Returns the full URL for this audio item. Typically used for getting a specific track. """ + return self._server.url(part, includeToken=True) if part else None @utils.registerPlexObject diff --git a/plexapi/base.py b/plexapi/base.py index aa745714..41a87bec 100644 --- a/plexapi/base.py +++ b/plexapi/base.py @@ -479,7 +479,7 @@ class Playable(object): # sort the keys since the randomness fucks with my tests.. sorted_params = sorted(params.items(), key=lambda val: val[0]) return self._server.url('/%s/:/transcode/universal/start.m3u8?%s' % - (streamtype, urlencode(sorted_params))) + (streamtype, urlencode(sorted_params)), includeToken=True) def iterParts(self): """ Iterates over the parts of this media item. """ @@ -530,9 +530,8 @@ class Playable(object): download_url = self.getStreamURL(**kwargs) else: download_url = self._server.url('%s?download=1' % location.key) - - filepath = utils.download(download_url, filename=filename, - savepath=savepath, session=self._server._session) + filepath = utils.download(download_url, self._server._token, filename=filename, + savepath=savepath, session=self._server._session) if filepath: filepaths.append(filepath) return filepaths diff --git a/plexapi/client.py b/plexapi/client.py index 686ccf89..a584876e 100644 --- a/plexapi/client.py +++ b/plexapi/client.py @@ -65,6 +65,7 @@ class PlexClient(PlexObject): super(PlexClient, self).__init__(server, data, initpath) self._baseurl = baseurl.strip('/') if baseurl else None self._token = logfilter.add_secret(token) + self._showSecrets = CONFIG.get('log.show_secrets', '').lower() == 'true' server_session = server._session if server else None self._session = session or server_session or requests.Session() self._proxyThroughServer = False @@ -193,11 +194,13 @@ class PlexClient(PlexObject): return self._server.query(key, headers=headers) return self.query(key, headers=headers) - def url(self, key): - """ Build a URL string with proper token argument. """ + def url(self, key, includeToken=False): + """ Build a URL string with proper token argument. Token will be appended to the URL + if either includeToken is True or CONFIG.log.show_secrets is 'true'. + """ if not self._baseurl: raise BadRequest('PlexClient object missing baseurl.') - if self._token: + if self._token and (includeToken or self._showSecrets): delim = '&' if '?' in key else '?' return '%s%s%sX-Plex-Token=%s' % (self._baseurl, key, delim, self._token) return '%s%s' % (self._baseurl, key) diff --git a/plexapi/server.py b/plexapi/server.py index 6c27b6e3..849b4c69 100644 --- a/plexapi/server.py +++ b/plexapi/server.py @@ -94,6 +94,7 @@ class PlexServer(PlexObject): def __init__(self, baseurl=None, token=None, session=None, timeout=None): self._baseurl = baseurl or CONFIG.get('auth.server_baseurl', 'http://localhost:32400') self._token = logfilter.add_secret(token or CONFIG.get('auth.server_token')) + self._showSecrets = CONFIG.get('log.show_secrets', '').lower() == 'true' self._session = session or requests.Session() self._library = None # cached library self._settings = None # cached settings @@ -265,7 +266,7 @@ class PlexServer(PlexObject): unpack (bool): Unpack the zip file. """ url = self.url('/diagnostics/databases') - filepath = utils.download(url, None, savepath, self._session, unpack=unpack) + filepath = utils.download(url, self._token, None, savepath, self._session, unpack=unpack) return filepath def downloadLogs(self, savepath=None, unpack=False): @@ -276,7 +277,7 @@ class PlexServer(PlexObject): unpack (bool): Unpack the zip file. """ url = self.url('/diagnostics/logs') - filepath = utils.download(url, None, savepath, self._session, unpack=unpack) + filepath = utils.download(url, self._token, None, savepath, self._session, unpack=unpack) return filepath def check_for_update(self, force=True, download=False): @@ -410,11 +411,13 @@ class PlexServer(PlexObject): if media: transcode_url = '/photo/:/transcode?height=%s&width=%s&opacity=%s&saturation=%s&url=%s' % ( height, width, opacity, saturation, media) - return self.url(transcode_url) + return self.url(transcode_url, includeToken=True) - def url(self, key): - """ Build a URL string with proper token argument. """ - if self._token: + def url(self, key, includeToken=None): + """ Build a URL string with proper token argument. Token will be appended to the URL + if either includeToken is True or CONFIG.log.show_secrets is 'true'. + """ + if self._token and (includeToken or self._showSecrets): delim = '&' if '?' in key else '?' return '%s%s%sX-Plex-Token=%s' % (self._baseurl, key, delim, self._token) return '%s%s' % (self._baseurl, key) diff --git a/plexapi/utils.py b/plexapi/utils.py index f7add415..b523eaeb 100644 --- a/plexapi/utils.py +++ b/plexapi/utils.py @@ -223,13 +223,14 @@ def downloadSessionImages(server, filename=None, height=150, width=150, return info -def download(url, filename=None, savepath=None, session=None, chunksize=4024, +def download(url, token, filename=None, savepath=None, session=None, chunksize=4024, unpack=False, mocked=False, showstatus=False): """ Helper to download a thumb, videofile or other media item. Returns the local path to the downloaded file. Parameters: url (str): URL where the content be reached. + token (str): Plex auth token to include in headers. filename (str): Filename of the downloaded file, default None. savepath (str): Defaults to current working dir. chunksize (int): What chunksize read/write at the time. @@ -245,7 +246,8 @@ def download(url, filename=None, savepath=None, session=None, chunksize=4024, from plexapi import log # fetch the data to be saved session = session or requests.Session() - response = session.get(url, stream=True) + headers = {'X-Plex-Token': token} + response = session.get(url, headers=headers, stream=True) # make sure the savepath directory exists savepath = savepath or os.getcwd() compat.makedirs(savepath, exist_ok=True) diff --git a/plexapi/video.py b/plexapi/video.py index c62cda0a..fba13a9e 100644 --- a/plexapi/video.py +++ b/plexapi/video.py @@ -53,18 +53,17 @@ class Video(PlexPartialObject): the most specific thumbnail for that item. """ thumb = self.firstAttr('thumb', 'parentThumb', 'granparentThumb') - return self._server.url(thumb) if thumb else None + return self._server.url(thumb, includeToken=True) if thumb else None @property def artUrl(self): """ Return the first first art url starting on the most specific for that item.""" art = self.firstAttr('art', 'grandparentArt') - return self._server.url(art) if art else None + return self._server.url(art, includeToken=True) if art else None def url(self, part): """ Returns the full url for something. Typically used for getting a specific image. """ - if part: - return self._server.url(part) + return self._server.url(part, includeToken=True) if part else None def markWatched(self): """ Mark video as watched. """ @@ -193,10 +192,10 @@ class Movie(Video, Playable): url = self.getStreamURL(**kwargs) else: self._server.url('%s?download=1' % location.key) - filepath = utils.download(url, filename=name, savepath=savepath, session=self._server._session) + filepath = utils.download(url, self._server._token, filename=name, + savepath=savepath, session=self._server._session) if filepath: filepaths.append(filepath) - return filepaths diff --git a/tests/test_server.py b/tests/test_server.py index 5103e0c8..d28fe162 100644 --- a/tests/test_server.py +++ b/tests/test_server.py @@ -58,9 +58,9 @@ def test_server_transcodeImage(tmpdir, plex, show): width, height = 500, 500 imgurl = plex.transcodeImage(show.banner, height, width) gray = imgurl = plex.transcodeImage(show.banner, height, width, saturation=0) - resized_img = download(imgurl, savepath=str(tmpdir), filename='resize_image') - original_img = download(show._server.url(show.banner), savepath=str(tmpdir), filename='original_img') - grayscale_img = download(gray, savepath=str(tmpdir), filename='grayscale_img') + resized_img = download(imgurl, plex._token, savepath=str(tmpdir), filename='resize_image') + original_img = download(show._server.url(show.banner), plex._token, savepath=str(tmpdir), filename='original_img') + grayscale_img = download(gray, plex._token, savepath=str(tmpdir), filename='grayscale_img') with Image.open(resized_img) as image: assert width, height == image.size with Image.open(original_img) as image: diff --git a/tests/test_utils.py b/tests/test_utils.py index 3c0f18b6..e5d4ce19 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -60,10 +60,10 @@ def test_utils_cast(): bool_str = utils.cast(bool, 'kek') -def test_utils_download(episode): +def test_utils_download(plex, episode): url = episode.getStreamURL() locations = episode.locations[0] session = episode._server._session - assert utils.download(url, filename=locations, mocked=True) - assert utils.download(url, filename=locations, session=session, mocked=True) - assert utils.download(episode.thumbUrl, filename=episode.title, mocked=True) + assert utils.download(url, plex._token, filename=locations, mocked=True) + assert utils.download(url, plex._token, filename=locations, session=session, mocked=True) + assert utils.download(episode.thumbUrl, plex._token, filename=episode.title, mocked=True)