Merge pull request #3832 from vincentDcmps/master

Automatically choose subsonic authentication algorithm based on server version
This commit is contained in:
Benedikt 2021-01-19 19:00:10 +01:00 committed by GitHub
commit 6680f692f6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 84 additions and 18 deletions

View file

@ -29,26 +29,46 @@ import string
import requests
from binascii import hexlify
from beets import config
from beets.plugins import BeetsPlugin
__author__ = 'https://github.com/maffo999'
AUTH_TOKEN_VERSION = (1, 12)
class SubsonicUpdate(BeetsPlugin):
def __init__(self):
super(SubsonicUpdate, self).__init__()
# Set default configuration values
config['subsonic'].add({
'user': 'admin',
'pass': 'admin',
'url': 'http://localhost:4040',
})
config['subsonic']['pass'].redact = True
self._version = None
self._auth = None
self.register_listener('import', self.start_scan)
@property
def version(self):
if (self._version is None):
self._version = self.__get_version()
return self._version
@property
def auth(self):
if (self._auth is None):
if(self.version is not None):
if self.version > AUTH_TOKEN_VERSION:
self._auth = "token"
else:
self._auth = "password"
self._log.info(
u"using '{}' authentication method".format(self._auth))
return self._auth
@staticmethod
def __create_token():
"""Create salt and token from given password.
@ -67,10 +87,10 @@ class SubsonicUpdate(BeetsPlugin):
return salt, token
@staticmethod
def __format_url():
"""Get the Subsonic URL to trigger a scan. Uses either the url
config option or the deprecated host, port, and context_path config
options together.
def __format_url(endpoint):
"""Get the Subsonic URL to trigger the given endpoint.
Uses either the url config option or the deprecated host, port,
and context_path config options together.
:return: Endpoint for updating Subsonic
"""
@ -88,22 +108,55 @@ class SubsonicUpdate(BeetsPlugin):
context_path = ''
url = "http://{}:{}{}".format(host, port, context_path)
return url + '/rest/startScan'
def start_scan(self):
user = config['subsonic']['user'].as_str()
url = self.__format_url()
salt, token = self.__create_token()
return url + '/rest/{}'.format(endpoint)
def __get_version(self):
url = self.__format_url("ping.view")
payload = {
'u': user,
't': token,
's': salt,
'v': '1.15.0', # Subsonic 6.1 and newer.
'c': 'beets',
'f': 'json'
}
try:
response = requests.get(url, params=payload)
if response.status_code == 200:
json = response.json()
version = json['subsonic-response']['version']
self._log.info(
u'subsonic version:{0} '.format(version))
return tuple(int(s) for s in version.split('.'))
else:
self._log.error(u'Error: {0}', json)
return None
except Exception as error:
self._log.error(u'Error: {0}'.format(error))
return None
def start_scan(self):
user = config['subsonic']['user'].as_str()
url = self.__format_url("startScan.view")
if self.auth == 'token':
salt, token = self.__create_token()
payload = {
'u': user,
't': token,
's': salt,
'v': self.version, # Subsonic 6.1 and newer.
'c': 'beets',
'f': 'json'
}
elif self.auth == 'password':
password = config['subsonic']['pass'].as_str()
encpass = hexlify(password.encode()).decode()
payload = {
'u': user,
'p': 'enc:{}'.format(encpass),
'v': self.version,
'c': 'beets',
'f': 'json'
}
else:
return
try:
response = requests.get(url, params=payload)
json = response.json()

View file

@ -15,6 +15,7 @@ New features:
* :doc:`/plugins/chroma`: Update file metadata after generating fingerprints through the `submit` command.
* :doc:`/plugins/lastgenre`: Added more heavy metal genres: https://en.wikipedia.org/wiki/Heavy_metal_genres to genres.txt and genres-tree.yaml
* :doc:`/plugins/subsonicplaylist`: import playlist from a subsonic server.
* :doc:`/plugins/subsonicupdate`: manage tocken and password authentifications method by checking server version.
* A new :ref:`reflink` config option instructs the importer to create fast,
copy-on-write file clones on filesystems that support them. Thanks to
:user:`rubdos`.

View file

@ -39,9 +39,21 @@ class SubsonicPluginTest(_common.TestCase, TestHelper):
config["subsonic"]["user"] = "admin"
config["subsonic"]["pass"] = "admin"
config["subsonic"]["url"] = "http://localhost:4040"
responses.add(
responses.GET,
'http://localhost:4040/rest/ping.view',
status=200,
body=self.PING_BODY
)
self.subsonicupdate = subsonicupdate.SubsonicUpdate()
PING_BODY = '''
{
"subsonic-response": {
"status": "failed",
"version": "1.15.0"
}
}
'''
SUCCESS_BODY = '''
{
"subsonic-response": {