Handle authentication errors better
This commit is contained in:
parent
57cf842ca7
commit
5a19cf02a9
@ -96,6 +96,10 @@ msgctxt "#30701"
|
|||||||
msgid "To watch a video, you need to enter your credentials. Do you want to enter them now?"
|
msgid "To watch a video, you need to enter your credentials. Do you want to enter them now?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgctxt "#30702"
|
||||||
|
msgid "An error occurred while authenticating: {error}."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgctxt "#30709"
|
msgctxt "#30709"
|
||||||
msgid "Geo-blocked video"
|
msgid "Geo-blocked video"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
@ -97,6 +97,10 @@ msgctxt "#30701"
|
|||||||
msgid "To watch a video, you need to enter your credentials. Do you want to enter them now?"
|
msgid "To watch a video, you need to enter your credentials. Do you want to enter them now?"
|
||||||
msgstr "Om een video te bekijken moet je je inloggegevens ingeven. Wil je dit nu doen?"
|
msgstr "Om een video te bekijken moet je je inloggegevens ingeven. Wil je dit nu doen?"
|
||||||
|
|
||||||
|
msgctxt "#30702"
|
||||||
|
msgid "An error occurred while authenticating: {error}."
|
||||||
|
msgstr "Er is een fout opgetreden tijdens het aanmelden: {error}."
|
||||||
|
|
||||||
msgctxt "#30709"
|
msgctxt "#30709"
|
||||||
msgid "Geo-blocked video"
|
msgid "Geo-blocked video"
|
||||||
msgstr "Video is geografisch geblokkeerd"
|
msgstr "Video is geografisch geblokkeerd"
|
||||||
|
@ -9,7 +9,6 @@ from resources.lib import kodiutils
|
|||||||
from resources.lib.kodiutils import TitleItem
|
from resources.lib.kodiutils import TitleItem
|
||||||
from resources.lib.modules.menu import Menu
|
from resources.lib.modules.menu import Menu
|
||||||
from resources.lib.viervijfzes import CHANNELS
|
from resources.lib.viervijfzes import CHANNELS
|
||||||
from resources.lib.viervijfzes.auth import AuthApi
|
|
||||||
from resources.lib.viervijfzes.content import ContentApi, UnavailableException
|
from resources.lib.viervijfzes.content import ContentApi, UnavailableException
|
||||||
|
|
||||||
_LOGGER = logging.getLogger('catalog')
|
_LOGGER = logging.getLogger('catalog')
|
||||||
@ -20,8 +19,7 @@ class Catalog:
|
|||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
""" Initialise object """
|
""" Initialise object """
|
||||||
self._auth = AuthApi(kodiutils.get_setting('username'), kodiutils.get_setting('password'), kodiutils.get_tokens_path())
|
self._api = ContentApi()
|
||||||
self._api = ContentApi(self._auth.get_token())
|
|
||||||
self._menu = Menu()
|
self._menu = Menu()
|
||||||
|
|
||||||
def show_catalog(self):
|
def show_catalog(self):
|
||||||
|
@ -7,6 +7,7 @@ import logging
|
|||||||
|
|
||||||
from resources.lib import kodiutils
|
from resources.lib import kodiutils
|
||||||
from resources.lib.viervijfzes.auth import AuthApi
|
from resources.lib.viervijfzes.auth import AuthApi
|
||||||
|
from resources.lib.viervijfzes.auth_awsidp import InvalidLoginException, AuthenticationException
|
||||||
from resources.lib.viervijfzes.content import ContentApi, UnavailableException, GeoblockedException
|
from resources.lib.viervijfzes.content import ContentApi, UnavailableException, GeoblockedException
|
||||||
|
|
||||||
_LOGGER = logging.getLogger('player')
|
_LOGGER = logging.getLogger('player')
|
||||||
@ -38,15 +39,22 @@ class Player:
|
|||||||
try:
|
try:
|
||||||
# Check if we have credentials
|
# Check if we have credentials
|
||||||
if not kodiutils.get_setting('username') or not kodiutils.get_setting('password'):
|
if not kodiutils.get_setting('username') or not kodiutils.get_setting('password'):
|
||||||
confirm = kodiutils.yesno_dialog(message=kodiutils.localize(30701)) # To watch a video, you need to enter your credentials. Do you want to enter them now?
|
confirm = kodiutils.yesno_dialog(
|
||||||
|
message=kodiutils.localize(30701)) # To watch a video, you need to enter your credentials. Do you want to enter them now?
|
||||||
if confirm:
|
if confirm:
|
||||||
kodiutils.open_settings()
|
kodiutils.open_settings()
|
||||||
kodiutils.end_of_directory()
|
kodiutils.end_of_directory()
|
||||||
return
|
return
|
||||||
|
|
||||||
# Fetch an auth token now
|
# Fetch an auth token now
|
||||||
|
try:
|
||||||
auth = AuthApi(kodiutils.get_setting('username'), kodiutils.get_setting('password'), kodiutils.get_tokens_path())
|
auth = AuthApi(kodiutils.get_setting('username'), kodiutils.get_setting('password'), kodiutils.get_tokens_path())
|
||||||
token = auth.get_token()
|
token = auth.get_token()
|
||||||
|
except (InvalidLoginException, AuthenticationException) as ex:
|
||||||
|
_LOGGER.error(ex)
|
||||||
|
kodiutils.ok_dialog(message=kodiutils.localize(30702, error=ex.message))
|
||||||
|
kodiutils.end_of_directory()
|
||||||
|
return
|
||||||
|
|
||||||
# Get stream information
|
# Get stream information
|
||||||
resolved_stream = ContentApi(token).get_stream(channel, item)
|
resolved_stream = ContentApi(token).get_stream(channel, item)
|
||||||
|
@ -8,7 +8,7 @@ import logging
|
|||||||
import os
|
import os
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from resources.lib.viervijfzes.auth_awsidp import AwsIdp
|
from resources.lib.viervijfzes.auth_awsidp import AwsIdp, InvalidLoginException, AuthenticationException
|
||||||
|
|
||||||
_LOGGER = logging.getLogger('auth-api')
|
_LOGGER = logging.getLogger('auth-api')
|
||||||
|
|
||||||
@ -25,15 +25,15 @@ class AuthApi:
|
|||||||
""" Initialise object """
|
""" Initialise object """
|
||||||
self._username = username
|
self._username = username
|
||||||
self._password = password
|
self._password = password
|
||||||
self._cache = cache_dir
|
self._cache_dir = cache_dir
|
||||||
self._id_token = None
|
self._id_token = None
|
||||||
self._expiry = 0
|
self._expiry = 0
|
||||||
self._refresh_token = None
|
self._refresh_token = None
|
||||||
|
|
||||||
if self._cache:
|
if self._cache_dir:
|
||||||
# Load tokens from cache
|
# Load tokens from cache
|
||||||
try:
|
try:
|
||||||
with open(self._cache + self.TOKEN_FILE, 'rb') as f:
|
with open(self._cache_dir + self.TOKEN_FILE, 'rb') as f:
|
||||||
data_json = json.loads(f.read())
|
data_json = json.loads(f.read())
|
||||||
self._id_token = data_json.get('id_token')
|
self._id_token = data_json.get('id_token')
|
||||||
self._refresh_token = data_json.get('refresh_token')
|
self._refresh_token = data_json.get('refresh_token')
|
||||||
@ -53,25 +53,33 @@ class AuthApi:
|
|||||||
if self._refresh_token:
|
if self._refresh_token:
|
||||||
# We have a valid refresh token, use that to refresh our id token
|
# We have a valid refresh token, use that to refresh our id token
|
||||||
# The refresh token is valid for 30 days. If this refresh fails, we just continue by logging in again.
|
# The refresh token is valid for 30 days. If this refresh fails, we just continue by logging in again.
|
||||||
|
_LOGGER.debug('Getting an id token by refreshing')
|
||||||
|
try:
|
||||||
self._id_token = self._refresh(self._refresh_token)
|
self._id_token = self._refresh(self._refresh_token)
|
||||||
if self._id_token:
|
|
||||||
self._expiry = now + 3600
|
self._expiry = now + 3600
|
||||||
_LOGGER.debug('Got an id token by refreshing: %s', self._id_token)
|
_LOGGER.debug('Got an id token by refreshing: %s', self._id_token)
|
||||||
|
except (InvalidLoginException, AuthenticationException) as e:
|
||||||
|
_LOGGER.error('Error logging in: %s', e.message)
|
||||||
|
self._id_token = None
|
||||||
|
self._refresh_token = None
|
||||||
|
self._expiry = 0
|
||||||
|
# We continue by logging in with username and password
|
||||||
|
|
||||||
if not self._id_token:
|
if not self._id_token:
|
||||||
# We have no tokens, or they are all invalid, do a login
|
# We have no tokens, or they are all invalid, do a login
|
||||||
|
_LOGGER.debug('Getting an id token by logging in')
|
||||||
id_token, refresh_token = self._authenticate(self._username, self._password)
|
id_token, refresh_token = self._authenticate(self._username, self._password)
|
||||||
self._id_token = id_token
|
self._id_token = id_token
|
||||||
self._refresh_token = refresh_token
|
self._refresh_token = refresh_token
|
||||||
self._expiry = now + 3600
|
self._expiry = now + 3600
|
||||||
_LOGGER.debug('Got an id token by logging in: %s', self._id_token)
|
_LOGGER.debug('Got an id token by logging in: %s', self._id_token)
|
||||||
|
|
||||||
if self._cache:
|
if self._cache_dir:
|
||||||
if not os.path.isdir(self._cache):
|
if not os.path.isdir(self._cache_dir):
|
||||||
os.mkdir(self._cache)
|
os.mkdir(self._cache_dir)
|
||||||
|
|
||||||
# Store new tokens in cache
|
# Store new tokens in cache
|
||||||
with open(self._cache + self.TOKEN_FILE, 'wb') as f:
|
with open(self._cache_dir + self.TOKEN_FILE, 'wb') as f:
|
||||||
data = json.dumps(dict(
|
data = json.dumps(dict(
|
||||||
id_token=self._id_token,
|
id_token=self._id_token,
|
||||||
refresh_token=self._refresh_token,
|
refresh_token=self._refresh_token,
|
||||||
@ -83,11 +91,11 @@ class AuthApi:
|
|||||||
|
|
||||||
def clear_cache(self):
|
def clear_cache(self):
|
||||||
""" Remove the cached tokens. """
|
""" Remove the cached tokens. """
|
||||||
if not self._cache:
|
if not self._cache_dir:
|
||||||
return
|
return
|
||||||
|
|
||||||
# Remove cache
|
# Remove cache
|
||||||
os.remove(self._cache + self.TOKEN_FILE)
|
os.remove(self._cache_dir + self.TOKEN_FILE)
|
||||||
|
|
||||||
# Clear tokens in memory
|
# Clear tokens in memory
|
||||||
self._id_token = None
|
self._id_token = None
|
||||||
|
@ -20,6 +20,16 @@ import six
|
|||||||
_LOGGER = logging.getLogger('auth-awsidp')
|
_LOGGER = logging.getLogger('auth-awsidp')
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidLoginException(Exception):
|
||||||
|
""" The login credentials are invalid """
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class AuthenticationException(Exception):
|
||||||
|
""" Something went wrong while logging in """
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class AwsIdp:
|
class AwsIdp:
|
||||||
""" AWS Identity Provider """
|
""" AWS Identity Provider """
|
||||||
|
|
||||||
@ -86,9 +96,7 @@ class AwsIdp:
|
|||||||
|
|
||||||
challenge_name = auth_response_json.get("ChallengeName")
|
challenge_name = auth_response_json.get("ChallengeName")
|
||||||
if not challenge_name == "PASSWORD_VERIFIER":
|
if not challenge_name == "PASSWORD_VERIFIER":
|
||||||
message = auth_response_json.get("message")
|
raise AuthenticationException(auth_response_json.get("message"))
|
||||||
_LOGGER.error("Cannot start authentication challenge: %s", message or None)
|
|
||||||
return None
|
|
||||||
|
|
||||||
# Step 2: Respond to the Challenge with a valid ChallengeResponse
|
# Step 2: Respond to the Challenge with a valid ChallengeResponse
|
||||||
challenge_request = self.__get_challenge_response_request(challenge_parameters, password)
|
challenge_request = self.__get_challenge_response_request(challenge_parameters, password)
|
||||||
@ -102,8 +110,7 @@ class AwsIdp:
|
|||||||
_LOGGER.debug("Got response: %s", auth_response_json)
|
_LOGGER.debug("Got response: %s", auth_response_json)
|
||||||
|
|
||||||
if "message" in auth_response_json:
|
if "message" in auth_response_json:
|
||||||
_LOGGER.error("Error logging in: %s", auth_response_json.get("message"))
|
raise InvalidLoginException(auth_response_json.get("message"))
|
||||||
return None, None
|
|
||||||
|
|
||||||
id_token = auth_response_json.get("AuthenticationResult", {}).get("IdToken")
|
id_token = auth_response_json.get("AuthenticationResult", {}).get("IdToken")
|
||||||
refresh_token = auth_response_json.get("AuthenticationResult", {}).get("RefreshToken")
|
refresh_token = auth_response_json.get("AuthenticationResult", {}).get("RefreshToken")
|
||||||
@ -134,8 +141,7 @@ class AwsIdp:
|
|||||||
refresh_json = json.loads(refresh_response.content)
|
refresh_json = json.loads(refresh_response.content)
|
||||||
|
|
||||||
if "message" in refresh_json:
|
if "message" in refresh_json:
|
||||||
_LOGGER.error("Error refreshing: %s", refresh_json.get("message"))
|
raise AuthenticationException(refresh_json.get("message"))
|
||||||
return None
|
|
||||||
|
|
||||||
id_token = refresh_json.get("AuthenticationResult", {}).get("IdToken")
|
id_token = refresh_json.get("AuthenticationResult", {}).get("IdToken")
|
||||||
return id_token
|
return id_token
|
||||||
|
@ -8,8 +8,8 @@ import logging
|
|||||||
import re
|
import re
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
import requests
|
|
||||||
from six.moves.html_parser import HTMLParser
|
from six.moves.html_parser import HTMLParser
|
||||||
|
import requests
|
||||||
|
|
||||||
from resources.lib.viervijfzes import CHANNELS
|
from resources.lib.viervijfzes import CHANNELS
|
||||||
|
|
||||||
|
@ -18,19 +18,16 @@ _LOGGER = logging.getLogger('test-api')
|
|||||||
class TestApi(unittest.TestCase):
|
class TestApi(unittest.TestCase):
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super(TestApi, self).__init__(*args, **kwargs)
|
super(TestApi, self).__init__(*args, **kwargs)
|
||||||
|
self._api = ContentApi()
|
||||||
|
|
||||||
def test_programs(self):
|
def test_programs(self):
|
||||||
api = ContentApi()
|
|
||||||
|
|
||||||
for channel in ['vier', 'vijf', 'zes']:
|
for channel in ['vier', 'vijf', 'zes']:
|
||||||
channels = api.get_programs(channel)
|
channels = self._api.get_programs(channel)
|
||||||
self.assertIsInstance(channels, list)
|
self.assertIsInstance(channels, list)
|
||||||
|
|
||||||
def test_episodes(self):
|
def test_episodes(self):
|
||||||
api = ContentApi()
|
|
||||||
|
|
||||||
for channel, program in [('vier', 'auwch'), ('vijf', 'zo-man-zo-vrouw')]:
|
for channel, program in [('vier', 'auwch'), ('vijf', 'zo-man-zo-vrouw')]:
|
||||||
program = api.get_program(channel, program)
|
program = self._api.get_program(channel, program)
|
||||||
self.assertIsInstance(program, Program)
|
self.assertIsInstance(program, Program)
|
||||||
self.assertIsInstance(program.seasons, dict)
|
self.assertIsInstance(program.seasons, dict)
|
||||||
# self.assertIsInstance(program.seasons[0], Season)
|
# self.assertIsInstance(program.seasons[0], Season)
|
||||||
@ -39,13 +36,12 @@ class TestApi(unittest.TestCase):
|
|||||||
_LOGGER.info('Got program: %s', program)
|
_LOGGER.info('Got program: %s', program)
|
||||||
|
|
||||||
def test_get_stream(self):
|
def test_get_stream(self):
|
||||||
auth = AuthApi(kodiutils.get_setting('username'), kodiutils.get_setting('password'), kodiutils.get_tokens_path())
|
program = self._api.get_program('vier', 'auwch')
|
||||||
token = auth.get_token()
|
|
||||||
|
|
||||||
api = ContentApi(token)
|
|
||||||
program = api.get_program('vier', 'auwch')
|
|
||||||
episode = program.episodes[0]
|
episode = program.episodes[0]
|
||||||
video = api.get_stream(episode.channel, episode.uuid)
|
|
||||||
|
auth = AuthApi(kodiutils.get_setting('username'), kodiutils.get_setting('password'), kodiutils.get_tokens_path())
|
||||||
|
api_authed = ContentApi(auth.get_token())
|
||||||
|
video = api_authed.get_stream(episode.channel, episode.uuid)
|
||||||
self.assertTrue(video)
|
self.assertTrue(video)
|
||||||
|
|
||||||
_LOGGER.info('Got video URL: %s', video)
|
_LOGGER.info('Got video URL: %s', video)
|
||||||
|
@ -20,47 +20,43 @@ _LOGGER = logging.getLogger('test-epg')
|
|||||||
class TestEpg(unittest.TestCase):
|
class TestEpg(unittest.TestCase):
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super(TestEpg, self).__init__(*args, **kwargs)
|
super(TestEpg, self).__init__(*args, **kwargs)
|
||||||
self._auth = AuthApi(kodiutils.get_setting('username'), kodiutils.get_setting('password'), kodiutils.get_tokens_path())
|
self._epg = EpgApi()
|
||||||
|
|
||||||
def test_vier_today(self):
|
def test_vier_today(self):
|
||||||
epg = EpgApi()
|
programs = self._epg.get_epg('vier', date.today().strftime('%Y-%m-%d'))
|
||||||
programs = epg.get_epg('vier', date.today().strftime('%Y-%m-%d'))
|
|
||||||
self.assertIsInstance(programs, list)
|
self.assertIsInstance(programs, list)
|
||||||
self.assertIsInstance(programs[0], EpgProgram)
|
self.assertIsInstance(programs[0], EpgProgram)
|
||||||
|
|
||||||
def test_vijf_today(self):
|
def test_vijf_today(self):
|
||||||
epg = EpgApi()
|
programs = self._epg.get_epg('vijf', date.today().strftime('%Y-%m-%d'))
|
||||||
programs = epg.get_epg('vijf', date.today().strftime('%Y-%m-%d'))
|
|
||||||
self.assertIsInstance(programs, list)
|
self.assertIsInstance(programs, list)
|
||||||
self.assertIsInstance(programs[0], EpgProgram)
|
self.assertIsInstance(programs[0], EpgProgram)
|
||||||
|
|
||||||
def test_zes_today(self):
|
def test_zes_today(self):
|
||||||
epg = EpgApi()
|
programs = self._epg.get_epg('zes', date.today().strftime('%Y-%m-%d'))
|
||||||
programs = epg.get_epg('zes', date.today().strftime('%Y-%m-%d'))
|
|
||||||
self.assertIsInstance(programs, list)
|
self.assertIsInstance(programs, list)
|
||||||
self.assertIsInstance(programs[0], EpgProgram)
|
self.assertIsInstance(programs[0], EpgProgram)
|
||||||
|
|
||||||
def test_unknown_today(self):
|
def test_unknown_today(self):
|
||||||
epg = EpgApi()
|
|
||||||
with self.assertRaises(Exception):
|
with self.assertRaises(Exception):
|
||||||
epg.get_epg('vtm', date.today().strftime('%Y-%m-%d'))
|
self._epg.get_epg('vtm', date.today().strftime('%Y-%m-%d'))
|
||||||
|
|
||||||
def test_vier_out_of_range(self):
|
def test_vier_out_of_range(self):
|
||||||
epg = EpgApi()
|
programs = self._epg.get_epg('vier', '2020-01-01')
|
||||||
programs = epg.get_epg('vier', '2020-01-01')
|
|
||||||
self.assertEqual(programs, [])
|
self.assertEqual(programs, [])
|
||||||
|
|
||||||
def test_play_video_from_epg(self):
|
def test_play_video_from_epg(self):
|
||||||
epg = EpgApi()
|
epg_programs = self._epg.get_epg('vier', date.today().strftime('%Y-%m-%d'))
|
||||||
epg_programs = epg.get_epg('vier', date.today().strftime('%Y-%m-%d'))
|
|
||||||
epg_program = [program for program in epg_programs if program.video_url][0]
|
epg_program = [program for program in epg_programs if program.video_url][0]
|
||||||
|
|
||||||
# Lookup the Episode data since we don't have an UUID
|
# Lookup the Episode data since we don't have an UUID
|
||||||
api = ContentApi(self._auth.get_token())
|
api = ContentApi()
|
||||||
episode = api.get_episode(epg_program.channel, epg_program.video_url)
|
episode = api.get_episode(epg_program.channel, epg_program.video_url)
|
||||||
self.assertIsInstance(episode, Episode)
|
self.assertIsInstance(episode, Episode)
|
||||||
|
|
||||||
# Get stream based on the Episode's UUID
|
# Get stream based on the Episode's UUID
|
||||||
|
auth = AuthApi(kodiutils.get_setting('username'), kodiutils.get_setting('password'), kodiutils.get_tokens_path())
|
||||||
|
api = ContentApi(auth.get_token())
|
||||||
video = api.get_stream(episode.channel, episode.uuid)
|
video = api.get_stream(episode.channel, episode.uuid)
|
||||||
self.assertTrue(video)
|
self.assertTrue(video)
|
||||||
|
|
||||||
|
@ -17,21 +17,19 @@ _LOGGER = logging.getLogger('test-search')
|
|||||||
class TestSearch(unittest.TestCase):
|
class TestSearch(unittest.TestCase):
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super(TestSearch, self).__init__(*args, **kwargs)
|
super(TestSearch, self).__init__(*args, **kwargs)
|
||||||
|
self._search = SearchApi()
|
||||||
|
|
||||||
def test_search(self):
|
def test_search(self):
|
||||||
search = SearchApi()
|
programs = self._search.search('de mol')
|
||||||
programs = search.search('de mol')
|
|
||||||
self.assertIsInstance(programs, list)
|
self.assertIsInstance(programs, list)
|
||||||
self.assertIsInstance(programs[0], Program)
|
self.assertIsInstance(programs[0], Program)
|
||||||
|
|
||||||
def test_search_empty(self):
|
def test_search_empty(self):
|
||||||
search = SearchApi()
|
programs = self._search.search('')
|
||||||
programs = search.search('')
|
|
||||||
self.assertIsInstance(programs, list)
|
self.assertIsInstance(programs, list)
|
||||||
|
|
||||||
def test_search_space(self):
|
def test_search_space(self):
|
||||||
search = SearchApi()
|
programs = self._search.search(' ')
|
||||||
programs = search.search(' ')
|
|
||||||
self.assertIsInstance(programs, list)
|
self.assertIsInstance(programs, list)
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user