From 3fae2cceeff4fbfb8f838ef4aa0d5badd686c2b2 Mon Sep 17 00:00:00 2001 From: Steve Kowalik Date: Fri, 25 Aug 2023 13:08:11 +0930 Subject: [PATCH] Refactor header construction WRT authorization According to [1], "X-Emby-Authorization" and "X-MediaBrowser-Token" headers are deprecated. Sadly, this is how this library authenticates with a server. Switch to using the Authorization header, but also remove some duplication with header construction so it is only done in the HTTP code. Handle requests that do not include an AccessToken, along with correcting how the connection manager creates its own API object. 1: https://gist.github.com/nielsvanvelzen/ea047d9028f676185832e51ffaf12a6f --- jellyfin_apiclient_python/api.py | 21 +------ .../connection_manager.py | 4 +- jellyfin_apiclient_python/http.py | 58 ++++++++----------- 3 files changed, 28 insertions(+), 55 deletions(-) diff --git a/jellyfin_apiclient_python/api.py b/jellyfin_apiclient_python/api.py index 4690fca..73856df 100644 --- a/jellyfin_apiclient_python/api.py +++ b/jellyfin_apiclient_python/api.py @@ -508,21 +508,7 @@ def get_audio_stream(self, dest_file, item_id, play_id, container, max_streaming }) def get_default_headers(self): - auth = "MediaBrowser " - auth += "Client=%s, " % self.config.data['app.name'] - auth += "Device=%s, " % self.config.data['app.device_name'] - auth += "DeviceId=%s, " % self.config.data['app.device_id'] - auth += "Version=%s" % self.config.data['app.version'] - - return { - "Accept": "application/json", - "Content-type": "application/x-www-form-urlencoded; charset=UTF-8", - "X-Application": "%s/%s" % (self.config.data['app.name'], self.config.data['app.version']), - "Accept-Charset": "UTF-8,*", - "Accept-encoding": "gzip", - "User-Agent": self.config.data['http.user_agent'] or "%s/%s" % (self.config.data['app.name'], self.config.data['app.version']), - "x-emby-authorization": auth - } + return self.client._get_default_headers(content_type="application/x-www-form-urlencoded; charset=UTF-8") def send_request(self, url, path, method="get", timeout=None, headers=None, data=None, session=None): request_method = getattr(session or requests, method.lower()) @@ -572,11 +558,8 @@ def login(self, server_url, username, password=""): return {} def validate_authentication_token(self, server): - authTokenHeader = { - 'X-MediaBrowser-Token': server['AccessToken'] - } headers = self.get_default_headers() - headers.update(authTokenHeader) + headers["Authorization"] += f", Token=\"{server['AccessToken']}\"" response = self.send_request(server['address'], "system/info", headers=headers) return response.json() if response.status_code == 200 else {} diff --git a/jellyfin_apiclient_python/connection_manager.py b/jellyfin_apiclient_python/connection_manager.py index 1c1a3a2..3566b16 100644 --- a/jellyfin_apiclient_python/connection_manager.py +++ b/jellyfin_apiclient_python/connection_manager.py @@ -12,6 +12,7 @@ from .credentials import Credentials from .api import API +from .http import HTTP import traceback ################################################################################################# @@ -38,8 +39,7 @@ def __init__(self, client): self.client = client self.config = client.config self.credentials = Credentials() - - self.API = API(client) + self.API = API(HTTP(client)) def clear_data(self): diff --git a/jellyfin_apiclient_python/http.py b/jellyfin_apiclient_python/http.py index 3160e9a..73a4b33 100644 --- a/jellyfin_apiclient_python/http.py +++ b/jellyfin_apiclient_python/http.py @@ -199,7 +199,7 @@ def _request(self, data): if 'url' not in data: data['url'] = "%s/%s" % (self.config.data.get("auth.server", ""), data.pop('handler', "")) - self._get_header(data) + data['headers'] = self._get_default_headers() data['timeout'] = data.get('timeout') or self.config.data['http.timeout'] data['verify'] = data.get('verify') or self.config.data.get('auth.ssl', False) data['url'] = self._replace_user_info(data['url']) @@ -219,39 +219,29 @@ def _process_params(self, params): if isinstance(value, str): params[key] = self._replace_user_info(value) - def _get_header(self, data): - - data['headers'] = data.setdefault('headers', {}) - - if not data['headers']: - data['headers'].update({ - 'Content-type': "application/json", - 'Accept-Charset': "UTF-8,*", - 'Accept-encoding': "gzip", - 'User-Agent': self.config.data['http.user_agent'] or "%s/%s" % (self.config.data.get('app.name', 'Jellyfin for Kodi'), self.config.data.get('app.version', "0.0.0")) - }) - - if 'x-emby-authorization' not in data['headers']: - self._authorization(data) - - return data - - def _authorization(self, data): - - auth = "MediaBrowser " - auth += "Client=%s, " % self.config.data.get('app.name', "Jellyfin for Kodi") - auth += "Device=%s, " % self.config.data.get('app.device_name', 'Unknown Device') - auth += "DeviceId=%s, " % self.config.data.get('app.device_id', 'Unknown Device id') - auth += "Version=%s" % self.config.data.get('app.version', '0.0.0') - - data['headers'].update({'x-emby-authorization': auth}) - - if self.config.data.get('auth.token') and self.config.data.get('auth.user_id'): - - auth += ', UserId=%s' % self.config.data.get('auth.user_id') - data['headers'].update({'x-emby-authorization': auth, 'X-MediaBrowser-Token': self.config.data.get('auth.token')}) - - return data + def _get_authenication_header(self): + params = { + "Client": self.config.data['app.name'], + "Device": self.config.data['app.device_name'], + "DeviceId": self.config.data['app.device_id'], + "Version": self.config.data['app.version'] + } + if "auth.token" in self.config.data: + params["Token"] = self.config.data['auth.token'] + param_line = ", ".join(f'{k}="{v}"' for k, v in params.items()) + return f"MediaBrowser {param_line}" + + def _get_default_headers(self, content_type="application/json"): + app_name = f"{self.config.data.get('app.name', 'Jellyfin for Kodi')}/{self.config.data.get('app.version', '0.0.0')}" + return { + "Accept": "application/json", + "Content-type": content_type, + "X-Application": app_name, + "Accept-Charset": "UTF-8,*", + "Accept-encoding": "gzip", + "User-Agent": self.config.data['http.user_agent'] or app_name, + "Authorization": self._get_authenication_header() + } def _requests(self, session, action, **kwargs):