From 0d5ad875d48bdd544017bc13a75778ea0bd701ba Mon Sep 17 00:00:00 2001 From: vincent Date: Sun, 10 Jan 2021 12:19:43 +0100 Subject: [PATCH 01/11] add .view to endpoint --- beetsplug/subsonicupdate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beetsplug/subsonicupdate.py b/beetsplug/subsonicupdate.py index 004439bac..3876b0abe 100644 --- a/beetsplug/subsonicupdate.py +++ b/beetsplug/subsonicupdate.py @@ -88,7 +88,7 @@ class SubsonicUpdate(BeetsPlugin): context_path = '' url = "http://{}:{}{}".format(host, port, context_path) - return url + '/rest/startScan' + return url + '/rest/startScan.view' def start_scan(self): user = config['subsonic']['user'].as_str() From c1cc91c5e9045a44021b19b69bc277af50f07dc7 Mon Sep 17 00:00:00 2001 From: vincent Date: Sun, 10 Jan 2021 12:20:31 +0100 Subject: [PATCH 02/11] add old password option --- beetsplug/subsonicupdate.py | 35 +++++++++++++++++++++++------------ 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/beetsplug/subsonicupdate.py b/beetsplug/subsonicupdate.py index 3876b0abe..38ab2b0ba 100644 --- a/beetsplug/subsonicupdate.py +++ b/beetsplug/subsonicupdate.py @@ -20,6 +20,7 @@ a "subsonic" section like the following: url: https://mydomain.com:443/subsonic user: username pass: password + auth: enc or token """ from __future__ import division, absolute_import, print_function @@ -29,6 +30,7 @@ import string import requests +from binascii import hexlify from beets import config from beets.plugins import BeetsPlugin @@ -38,12 +40,12 @@ __author__ = 'https://github.com/maffo999' 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', + 'auth': 'token', }) config['subsonic']['pass'].redact = True @@ -93,21 +95,30 @@ class SubsonicUpdate(BeetsPlugin): def start_scan(self): user = config['subsonic']['user'].as_str() url = self.__format_url() - salt, token = self.__create_token() - - payload = { - 'u': user, - 't': token, - 's': salt, - 'v': '1.15.0', # Subsonic 6.1 and newer. - 'c': 'beets', - 'f': 'json' - } + if config['subsonic']['user'] == 'token': + salt, token = self.__create_token() + payload = { + 'u': user, + 't': token, + 's': salt, + 'v': '1.15.0', # Subsonic 6.1 and newer. + 'c': 'beets', + 'f': 'json' + } + else: + password = config['subsonic']['pass'].as_str() + encpass = hexlify(password.encode()).decode() + payload = { + 'u': user, + 'p': 'enc:{}'.format(encpass), + 'v': '1.15.0', + 'c': 'beets', + 'f': 'json' + } try: response = requests.get(url, params=payload) json = response.json() - if response.status_code == 200 and \ json['subsonic-response']['status'] == "ok": count = json['subsonic-response']['scanStatus']['count'] From 93bb114175c1bb7c049e8cf1bb011cf300af7a50 Mon Sep 17 00:00:00 2001 From: vincent Date: Sun, 10 Jan 2021 12:47:19 +0100 Subject: [PATCH 03/11] update doc --- beetsplug/subsonicupdate.py | 5 +++-- docs/plugins/subsonicupdate.rst | 1 + 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/beetsplug/subsonicupdate.py b/beetsplug/subsonicupdate.py index 38ab2b0ba..c284daa62 100644 --- a/beetsplug/subsonicupdate.py +++ b/beetsplug/subsonicupdate.py @@ -20,7 +20,7 @@ a "subsonic" section like the following: url: https://mydomain.com:443/subsonic user: username pass: password - auth: enc or token + auth: enc or plain """ from __future__ import division, absolute_import, print_function @@ -95,7 +95,7 @@ class SubsonicUpdate(BeetsPlugin): def start_scan(self): user = config['subsonic']['user'].as_str() url = self.__format_url() - if config['subsonic']['user'] == 'token': + if config['subsonic']['auth'] == 'token': salt, token = self.__create_token() payload = { 'u': user, @@ -119,6 +119,7 @@ class SubsonicUpdate(BeetsPlugin): try: response = requests.get(url, params=payload) json = response.json() + if response.status_code == 200 and \ json['subsonic-response']['status'] == "ok": count = json['subsonic-response']['scanStatus']['count'] diff --git a/docs/plugins/subsonicupdate.rst b/docs/plugins/subsonicupdate.rst index 710d21f2c..a8e1e537f 100644 --- a/docs/plugins/subsonicupdate.rst +++ b/docs/plugins/subsonicupdate.rst @@ -30,5 +30,6 @@ The available options under the ``subsonic:`` section are: - **url**: The Subsonic server resource. Default: ``http://localhost:4040`` - **user**: The Subsonic user. Default: ``admin`` +- **auth**: authentification method token or plain password - **pass**: The Subsonic user password. (This may either be a clear-text password or hex-encoded with the prefix ``enc:``.) Default: ``admin`` From 54748ad4c9b35ba0cc1c1bac684eb52f9d75a230 Mon Sep 17 00:00:00 2001 From: vincent Date: Mon, 11 Jan 2021 17:56:09 +0100 Subject: [PATCH 04/11] change auth option enc by password --- beetsplug/subsonicupdate.py | 2 +- docs/plugins/subsonicupdate.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/beetsplug/subsonicupdate.py b/beetsplug/subsonicupdate.py index c284daa62..2f9dfac9f 100644 --- a/beetsplug/subsonicupdate.py +++ b/beetsplug/subsonicupdate.py @@ -20,7 +20,7 @@ a "subsonic" section like the following: url: https://mydomain.com:443/subsonic user: username pass: password - auth: enc or plain + auth: token or password """ from __future__ import division, absolute_import, print_function diff --git a/docs/plugins/subsonicupdate.rst b/docs/plugins/subsonicupdate.rst index a8e1e537f..7a90d06a2 100644 --- a/docs/plugins/subsonicupdate.rst +++ b/docs/plugins/subsonicupdate.rst @@ -30,6 +30,6 @@ The available options under the ``subsonic:`` section are: - **url**: The Subsonic server resource. Default: ``http://localhost:4040`` - **user**: The Subsonic user. Default: ``admin`` -- **auth**: authentification method token or plain password +- **auth**: Authentication method. Default: ``token``. Use ``password`` for APIs <= 1.12.0. - **pass**: The Subsonic user password. (This may either be a clear-text password or hex-encoded with the prefix ``enc:``.) Default: ``admin`` From 375c158fc7f62d0f59665f6220c162d0d567f26d Mon Sep 17 00:00:00 2001 From: vincent Date: Mon, 11 Jan 2021 18:29:27 +0100 Subject: [PATCH 05/11] implement version check --- beetsplug/subsonicupdate.py | 50 +++++++++++++++++++++++++-------- docs/plugins/subsonicupdate.rst | 1 - 2 files changed, 38 insertions(+), 13 deletions(-) diff --git a/beetsplug/subsonicupdate.py b/beetsplug/subsonicupdate.py index 2f9dfac9f..4ff31f2fb 100644 --- a/beetsplug/subsonicupdate.py +++ b/beetsplug/subsonicupdate.py @@ -20,7 +20,6 @@ a "subsonic" section like the following: url: https://mydomain.com:443/subsonic user: username pass: password - auth: token or password """ from __future__ import division, absolute_import, print_function @@ -35,6 +34,7 @@ from beets import config from beets.plugins import BeetsPlugin __author__ = 'https://github.com/maffo999' +authtokenversion = '1.12' class SubsonicUpdate(BeetsPlugin): @@ -45,9 +45,16 @@ class SubsonicUpdate(BeetsPlugin): 'user': 'admin', 'pass': 'admin', 'url': 'http://localhost:4040', - 'auth': 'token', }) - + self.version = self.get_version() + if self.version > authtokenversion: + self._log.info( + u'use token authent method') + self.auth = "token" + else: + self.auth = "password" + self._log.info( + u'use password authent method') config['subsonic']['pass'].redact = True self.register_listener('import', self.start_scan) @@ -69,10 +76,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 a endpoint put in paramater. + Uses either the url config option or the deprecated host, port, + and context_path config options together. :return: Endpoint for updating Subsonic """ @@ -90,18 +97,37 @@ class SubsonicUpdate(BeetsPlugin): context_path = '' url = "http://{}:{}{}".format(host, port, context_path) - return url + '/rest/startScan.view' + return url + '/rest/{}'.format(endpoint) + + def get_version(self): + url = self.__format_url("ping.view") + payload = { + '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 version + else: + self._log.error(u'Error: {0}', json) + except Exception as error: + self._log.error(u'Error: {0}'.format(error)) def start_scan(self): user = config['subsonic']['user'].as_str() - url = self.__format_url() - if config['subsonic']['auth'] == 'token': + url = self.__format_url("startScan.view") + if self.auth == 'token': salt, token = self.__create_token() payload = { 'u': user, 't': token, 's': salt, - 'v': '1.15.0', # Subsonic 6.1 and newer. + 'v': self.version, # Subsonic 6.1 and newer. 'c': 'beets', 'f': 'json' } @@ -111,7 +137,7 @@ class SubsonicUpdate(BeetsPlugin): payload = { 'u': user, 'p': 'enc:{}'.format(encpass), - 'v': '1.15.0', + 'v': self.version, 'c': 'beets', 'f': 'json' } diff --git a/docs/plugins/subsonicupdate.rst b/docs/plugins/subsonicupdate.rst index 7a90d06a2..710d21f2c 100644 --- a/docs/plugins/subsonicupdate.rst +++ b/docs/plugins/subsonicupdate.rst @@ -30,6 +30,5 @@ The available options under the ``subsonic:`` section are: - **url**: The Subsonic server resource. Default: ``http://localhost:4040`` - **user**: The Subsonic user. Default: ``admin`` -- **auth**: Authentication method. Default: ``token``. Use ``password`` for APIs <= 1.12.0. - **pass**: The Subsonic user password. (This may either be a clear-text password or hex-encoded with the prefix ``enc:``.) Default: ``admin`` From 811089af7298b4bc7cc608ec14c1985f28e172d6 Mon Sep 17 00:00:00 2001 From: vincent Date: Mon, 11 Jan 2021 18:58:49 +0100 Subject: [PATCH 06/11] update test --- test/test_subsonicupdate.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/test/test_subsonicupdate.py b/test/test_subsonicupdate.py index c47208e65..0a4fdd512 100644 --- a/test/test_subsonicupdate.py +++ b/test/test_subsonicupdate.py @@ -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": "failled", + "version": "1.15.0" + } +} +''' SUCCESS_BODY = ''' { "subsonic-response": { From b37c55bc0b5dbdf6462b1556675d87d657a76238 Mon Sep 17 00:00:00 2001 From: Vincent Ducamps Date: Mon, 11 Jan 2021 21:15:36 +0100 Subject: [PATCH 07/11] pass get_version to private Co-authored-by: Jef LeCompte --- beetsplug/subsonicupdate.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/beetsplug/subsonicupdate.py b/beetsplug/subsonicupdate.py index 4ff31f2fb..cd1905c02 100644 --- a/beetsplug/subsonicupdate.py +++ b/beetsplug/subsonicupdate.py @@ -46,7 +46,7 @@ class SubsonicUpdate(BeetsPlugin): 'pass': 'admin', 'url': 'http://localhost:4040', }) - self.version = self.get_version() + self.version = self.__get_version() if self.version > authtokenversion: self._log.info( u'use token authent method') @@ -99,7 +99,7 @@ class SubsonicUpdate(BeetsPlugin): return url + '/rest/{}'.format(endpoint) - def get_version(self): + def __get_version(self): url = self.__format_url("ping.view") payload = { 'c': 'beets', From 03a665861dba80cf6c7f85d1070365a28543e667 Mon Sep 17 00:00:00 2001 From: Vincent Ducamps Date: Mon, 11 Jan 2021 22:30:14 +0100 Subject: [PATCH 08/11] reformating subsonicupdate.py Co-authored-by: Jef LeCompte --- beetsplug/subsonicupdate.py | 12 +++++------- test/test_subsonicupdate.py | 2 +- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/beetsplug/subsonicupdate.py b/beetsplug/subsonicupdate.py index cd1905c02..91f5bb997 100644 --- a/beetsplug/subsonicupdate.py +++ b/beetsplug/subsonicupdate.py @@ -34,7 +34,7 @@ from beets import config from beets.plugins import BeetsPlugin __author__ = 'https://github.com/maffo999' -authtokenversion = '1.12' +AUTH_TOKEN_VERSION = '1.12' class SubsonicUpdate(BeetsPlugin): @@ -47,14 +47,12 @@ class SubsonicUpdate(BeetsPlugin): 'url': 'http://localhost:4040', }) self.version = self.__get_version() - if self.version > authtokenversion: - self._log.info( - u'use token authent method') + if self.version > AUTH_TOKEN_VERSION: self.auth = "token" else: self.auth = "password" - self._log.info( - u'use password authent method') + self._log.info( + u"using '{}' authentication method".format(self.auth)) config['subsonic']['pass'].redact = True self.register_listener('import', self.start_scan) @@ -77,7 +75,7 @@ class SubsonicUpdate(BeetsPlugin): @staticmethod def __format_url(endpoint): - """Get the Subsonic URL to trigger a endpoint put in paramater. + """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. diff --git a/test/test_subsonicupdate.py b/test/test_subsonicupdate.py index 0a4fdd512..dd254d593 100644 --- a/test/test_subsonicupdate.py +++ b/test/test_subsonicupdate.py @@ -49,7 +49,7 @@ class SubsonicPluginTest(_common.TestCase, TestHelper): PING_BODY = ''' { "subsonic-response": { - "status": "failled", + "status": "failed", "version": "1.15.0" } } From 9b496e502704ff8f406b04dda7c974da0330231f Mon Sep 17 00:00:00 2001 From: vincent Date: Wed, 13 Jan 2021 17:50:37 +0100 Subject: [PATCH 09/11] change comparaison version method --- beetsplug/subsonicupdate.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/beetsplug/subsonicupdate.py b/beetsplug/subsonicupdate.py index 91f5bb997..46338f7e3 100644 --- a/beetsplug/subsonicupdate.py +++ b/beetsplug/subsonicupdate.py @@ -34,7 +34,7 @@ from beets import config from beets.plugins import BeetsPlugin __author__ = 'https://github.com/maffo999' -AUTH_TOKEN_VERSION = '1.12' +AUTH_TOKEN_VERSION = (1, 12) class SubsonicUpdate(BeetsPlugin): @@ -110,7 +110,7 @@ class SubsonicUpdate(BeetsPlugin): version = json['subsonic-response']['version'] self._log.info( u'subsonic version:{0} '.format(version)) - return version + return tuple(int(s) for s in version.split('.')) else: self._log.error(u'Error: {0}', json) except Exception as error: From 8af088c785def2398c0f96f69a1bdd947e70e918 Mon Sep 17 00:00:00 2001 From: vincent Date: Mon, 18 Jan 2021 18:18:57 +0100 Subject: [PATCH 10/11] manage case of supysonic off --- beetsplug/subsonicupdate.py | 35 ++++++++++++++++++++++++++--------- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/beetsplug/subsonicupdate.py b/beetsplug/subsonicupdate.py index 46338f7e3..8f5d2cb4b 100644 --- a/beetsplug/subsonicupdate.py +++ b/beetsplug/subsonicupdate.py @@ -46,16 +46,29 @@ class SubsonicUpdate(BeetsPlugin): 'pass': 'admin', 'url': 'http://localhost:4040', }) - self.version = self.__get_version() - if self.version > AUTH_TOKEN_VERSION: - self.auth = "token" - else: - self.auth = "password" - self._log.info( - u"using '{}' authentication method".format(self.auth)) 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. @@ -113,12 +126,15 @@ class SubsonicUpdate(BeetsPlugin): 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 = { @@ -129,7 +145,7 @@ class SubsonicUpdate(BeetsPlugin): 'c': 'beets', 'f': 'json' } - else: + elif self.auth == 'password': password = config['subsonic']['pass'].as_str() encpass = hexlify(password.encode()).decode() payload = { @@ -139,7 +155,8 @@ class SubsonicUpdate(BeetsPlugin): 'c': 'beets', 'f': 'json' } - + else: + return try: response = requests.get(url, params=payload) json = response.json() From 2479edbe22a2a3664ce168c002b72fe4cbc48f7a Mon Sep 17 00:00:00 2001 From: vincent Date: Tue, 19 Jan 2021 18:50:11 +0100 Subject: [PATCH 11/11] update changelog --- docs/changelog.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/changelog.rst b/docs/changelog.rst index 0fb503f80..ab7b589b5 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -14,6 +14,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`.