From 9b670919ccb97367f186aadce0beb251207548c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20Arnauts?= Date: Thu, 7 Jan 2021 19:08:37 +0100 Subject: [PATCH] Auth improvements (#253) --- resources/lib/addon.py | 4 +- resources/lib/modules/menu.py | 2 +- resources/lib/modules/player.py | 2 +- resources/lib/modules/proxy.py | 4 +- resources/lib/service.py | 4 +- resources/lib/vtmgo/vtmgo.py | 2 +- resources/lib/vtmgo/vtmgoauth.py | 66 +++++++++++++++++------------- resources/lib/vtmgo/vtmgostream.py | 2 +- tests/test_auth.py | 2 +- 9 files changed, 48 insertions(+), 40 deletions(-) diff --git a/resources/lib/addon.py b/resources/lib/addon.py index 2721b4f6..f085314c 100644 --- a/resources/lib/addon.py +++ b/resources/lib/addon.py @@ -11,10 +11,9 @@ from resources.lib import kodilogging, kodiutils from resources.lib.vtmgo.exceptions import InvalidLoginException, LoginErrorException -kodilogging.config() routing = routing.Plugin() # pylint: disable=invalid-name -_LOGGER = logging.getLogger('plugin') +_LOGGER = logging.getLogger(__name__) @routing.route('/') @@ -315,4 +314,5 @@ def iptv_epg(): def run(params): """ Run the routing plugin """ + kodilogging.config() routing.run(params) diff --git a/resources/lib/modules/menu.py b/resources/lib/modules/menu.py index 1b1ac015..ebef7032 100644 --- a/resources/lib/modules/menu.py +++ b/resources/lib/modules/menu.py @@ -29,7 +29,7 @@ def show_mainmenu(self): """ Show the main menu """ listing = [] - account = self._auth.login() + account = self._auth.get_tokens() listing.append(kodiutils.TitleItem( title=kodiutils.localize(30007), # TV Channels diff --git a/resources/lib/modules/player.py b/resources/lib/modules/player.py index 6306a718..042f7cf7 100644 --- a/resources/lib/modules/player.py +++ b/resources/lib/modules/player.py @@ -7,7 +7,7 @@ from resources.lib import kodiutils from resources.lib.kodiplayer import KodiPlayer -from resources.lib.vtmgo.exceptions import UnavailableException, StreamGeoblockedException, StreamUnavailableException +from resources.lib.vtmgo.exceptions import StreamGeoblockedException, StreamUnavailableException, UnavailableException from resources.lib.vtmgo.vtmgo import VtmGo from resources.lib.vtmgo.vtmgoauth import VtmGoAuth from resources.lib.vtmgo.vtmgostream import VtmGoStream diff --git a/resources/lib/modules/proxy.py b/resources/lib/modules/proxy.py index f0df8063..a5126da7 100644 --- a/resources/lib/modules/proxy.py +++ b/resources/lib/modules/proxy.py @@ -28,9 +28,9 @@ from SocketServer import TCPServer try: # Python 3 - from urllib.parse import urlparse, parse_qs + from urllib.parse import parse_qs, urlparse except ImportError: # Python 2 - from urlparse import urlparse, parse_qs + from urlparse import parse_qs, urlparse _LOGGER = logging.getLogger(__name__) diff --git a/resources/lib/service.py b/resources/lib/service.py index cc7733e1..5ddc2104 100644 --- a/resources/lib/service.py +++ b/resources/lib/service.py @@ -12,8 +12,7 @@ from resources.lib.modules.proxy import Proxy from resources.lib.vtmgo.exceptions import NoLoginException -kodilogging.config() -_LOGGER = logging.getLogger('service') +_LOGGER = logging.getLogger(__name__) class BackgroundService(Monitor): @@ -188,4 +187,5 @@ def __get_subtitle_paths(): def run(): """ Run the BackgroundService """ + kodilogging.config() BackgroundService().run() diff --git a/resources/lib/vtmgo/vtmgo.py b/resources/lib/vtmgo/vtmgo.py index ea1ea8d6..cc03d473 100644 --- a/resources/lib/vtmgo/vtmgo.py +++ b/resources/lib/vtmgo/vtmgo.py @@ -36,7 +36,7 @@ class VtmGo: def __init__(self, auth): """ Initialise object """ self._auth = auth - self._tokens = self._auth.login() + self._tokens = self._auth.get_tokens() def _mode(self): """ Return the mode that should be used for API calls """ diff --git a/resources/lib/vtmgo/vtmgoauth.py b/resources/lib/vtmgo/vtmgoauth.py index f03a3a0a..9f26bdaa 100644 --- a/resources/lib/vtmgo/vtmgoauth.py +++ b/resources/lib/vtmgo/vtmgoauth.py @@ -10,7 +10,8 @@ from hashlib import md5 from uuid import uuid4 -from resources.lib import kodiutils +from requests import HTTPError + from resources.lib.vtmgo import API_ENDPOINT, Profile, util from resources.lib.vtmgo.exceptions import InvalidLoginException, LoginErrorException, NoLoginException @@ -89,14 +90,18 @@ def __init__(self, username, password, loginprovider, profile, token_path): except (IndexError, AttributeError): self._account.product = None - def check_credentials_change(self): - """ Check if credentials have changed """ + def _check_credentials_change(self): + """ Check if credentials have changed. + + :return: The hash of the current credentials. + :rtype: str + """ old_hash = self._account.hash new_hash = md5((self._username + ':' + self._password + ':' + self._loginprovider).encode('utf-8')).hexdigest() + if new_hash != old_hash: - _LOGGER.debug('Credentials have changed, clearing tokens.') - self._account.hash = new_hash - self.logout() + return new_hash + return None def get_profiles(self, products='VTM_GO,VTM_GO_KIDS'): """ Returns the available profiles """ @@ -127,39 +132,52 @@ def login(self, force=False): :rtype: AccountStorage """ # Check if credentials have changed - self.check_credentials_change() + new_hash = self._check_credentials_change() + if new_hash: + _LOGGER.debug('Credentials have changed, forcing a new login.') + self._account.hash = new_hash + force = True # Use cached token if it is still valid if force or not self._account.is_valid_token(): # Do actual login self._web_login() - return self._account - def logout(self): """ Clear the session tokens. """ self._account.jwt_token = None self._save_cache() + def get_tokens(self): + """ Return the tokens. + + :return: + :rtype: AccountStorage + """ + return self._account + def _web_login(self): """ Executes a login and returns the JSON Web Token. :rtype str """ - # Yes, we have accepted the cookies + util.SESSION.cookies.clear() util.SESSION.cookies.set('authId', str(uuid4())) # Start login flow - response = util.http_get('https://vtm.be/vtmgo/aanmelden?redirectUrl=https://vtm.be/vtmgo') - response.raise_for_status() + util.http_get('https://vtm.be/vtmgo/aanmelden?redirectUrl=https://vtm.be/vtmgo') # Send login credentials - response = util.http_post('https://login2.vtm.be/login?client_id=vtm-go-web', form={ - 'userName': kodiutils.get_setting('username'), - 'password': kodiutils.get_setting('password'), - 'jsEnabled': 'true', - }) - response.raise_for_status() + try: + response = util.http_post('https://login2.vtm.be/login?client_id=vtm-go-web', form={ + 'userName': self._username, + 'password': self._password, + 'jsEnabled': 'true', + }) + except HTTPError as exc: + if exc.response.status_code == 400: + raise InvalidLoginException() + raise if 'errorBlock-OIDC-004' in response.text: # E-mailadres is niet gekend. raise InvalidLoginException() @@ -172,7 +190,6 @@ def _web_login(self): # Follow login response = util.http_get('https://login2.vtm.be/authorize/continue?client_id=vtm-go-web') - response.raise_for_status() # Extract state and code matches_state = re.search(r'name="state" value="([^"]+)', response.text) @@ -188,11 +205,10 @@ def _web_login(self): raise LoginErrorException(code=101) # Could not extract authentication code # Okay, final stage. We now need to POST our state and code to get a valid JWT. - response = util.http_post('https://vtm.be/vtmgo/login-callback', form={ + util.http_post('https://vtm.be/vtmgo/login-callback', form={ 'state': state, 'code': code, }) - response.raise_for_status() # Get JWT from cookies self._account.jwt_token = util.SESSION.cookies.get('lfvp_auth') @@ -216,11 +232,3 @@ def _save_cache(self): with open(os.path.join(self._token_path, self.TOKEN_FILE), 'w') as fdesc: json.dump(self._account.__dict__, fdesc, indent=2) - - def clear_token(self): - """ Remove the cached JWT. """ - _LOGGER.debug('Clearing token cache') - path = os.path.join(self._token_path, self.TOKEN_FILE) - if kodiutils.exists(path): - kodiutils.delete(path) - self._account = AccountStorage() diff --git a/resources/lib/vtmgo/vtmgostream.py b/resources/lib/vtmgo/vtmgostream.py index 265521dc..db633f9d 100644 --- a/resources/lib/vtmgo/vtmgostream.py +++ b/resources/lib/vtmgo/vtmgostream.py @@ -28,7 +28,7 @@ def __init__(self): 'VTM', kodiutils.get_setting('profile'), kodiutils.get_tokens_path()) - self._tokens = self._auth.login() + self._tokens = self._auth.get_tokens() def _mode(self): """ Return the mode that should be used for API calls """ diff --git a/tests/test_auth.py b/tests/test_auth.py index 32c298d5..adcad6ff 100644 --- a/tests/test_auth.py +++ b/tests/test_auth.py @@ -24,7 +24,7 @@ def setUpClass(cls): kodiutils.get_tokens_path()) def test_login(self): - token = self._vtmgoauth.login() + token = self._vtmgoauth.get_tokens() self.assertTrue(token) def test_get_profiles(self):