Skip to content

Commit

Permalink
Refactor header construction WRT authorization
Browse files Browse the repository at this point in the history
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
  • Loading branch information
s-t-e-v-e-n-k committed Aug 25, 2023
1 parent 60018e4 commit 3fae2cc
Show file tree
Hide file tree
Showing 3 changed files with 28 additions and 55 deletions.
21 changes: 2 additions & 19 deletions jellyfin_apiclient_python/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand Down Expand Up @@ -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 {}
Expand Down
4 changes: 2 additions & 2 deletions jellyfin_apiclient_python/connection_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

from .credentials import Credentials
from .api import API
from .http import HTTP
import traceback

#################################################################################################
Expand All @@ -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):

Expand Down
58 changes: 24 additions & 34 deletions jellyfin_apiclient_python/http.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'])
Expand All @@ -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):

Expand Down

0 comments on commit 3fae2cc

Please sign in to comment.