def log_on(self): """ Logs on to a website, using an url. First checks if the channel requires log on. If so and it's not already logged on, it should handle the log on. That part should be implemented by the specific channel. More arguments can be passed on, but must be handled by custom code. After a successful log on the self.loggedOn property is set to True and True is returned. :return: indication if the login was successful. :rtype: bool """ if self.__idToken: return True # check if there is a refresh token # refresh token: viervijfzes_refresh_token refresh_token = AddonSettings.get_setting("viervijfzes_refresh_token") client = AwsIdp("eu-west-1_dViSsKM5Y", "6s1h851s8uplco5h6mqh1jac8m", proxy=self.proxy, logger=Logger.instance()) if refresh_token: id_token = client.renew_token(refresh_token) if id_token: self.__idToken = id_token return True else: Logger.info("Extending token for VierVijfZes failed.") # username: viervijfzes_username username = AddonSettings.get_setting("viervijfzes_username") # password: viervijfzes_password v = Vault() password = v.get_setting("viervijfzes_password") if not username or not password: XbmcWrapper.show_dialog( title=None, lines=LanguageHelper.get_localized_string( LanguageHelper.MissingCredentials), ) return False id_token, refresh_token = client.authenticate(username, password) if not id_token or not refresh_token: Logger.error("Error getting a new token. Wrong password?") return False self.__idToken = id_token AddonSettings.set_setting("viervijfzes_refresh_token", refresh_token) return True
def __get_application_key(self): """ Gets the decrypted application key that is used for all the encryption. :return: The decrypted application key that is used for all the encryption. :rtype: bytes """ application_key_encrypted = AddonSettings.get_setting( Vault.__APPLICATION_KEY_SETTING, store=LOCAL) # The key was never in the local store the value was None. It was "" if it was reset. if application_key_encrypted is None: application_key_encrypted = AddonSettings.get_setting( Vault.__APPLICATION_KEY_SETTING, store=KODI) if not application_key_encrypted: return None Logger.info("Moved ApplicationKey to local storage") AddonSettings.set_setting(Vault.__APPLICATION_KEY_SETTING, application_key_encrypted, store=LOCAL) # Still no application key? Then there was no key! if application_key_encrypted == "" or application_key_encrypted is None: return None vault_incorrect_pin = LanguageHelper.get_localized_string( LanguageHelper.VaultIncorrectPin) pin = XbmcWrapper.show_key_board( heading=LanguageHelper.get_localized_string( LanguageHelper.VaultInputPin), hidden=True) if not pin: XbmcWrapper.show_notification("", vault_incorrect_pin, XbmcWrapper.Error) raise RuntimeError("Incorrect Retrospect PIN specified") pin_key = self.__get_pbk(pin) application_key = self.__decrypt(application_key_encrypted, pin_key) if not application_key.startswith(Vault.__APPLICATION_KEY_SETTING): Logger.critical("Invalid Retrospect PIN") XbmcWrapper.show_notification("", vault_incorrect_pin, XbmcWrapper.Error) raise RuntimeError("Incorrect Retrospect PIN specified") application_key_value = application_key[ len(Vault.__APPLICATION_KEY_SETTING) + 1:] Logger.info("Successfully decrypted the ApplicationKey.") if PY2: return application_key_value # We return bytes on Python 3 return application_key_value.encode()
def get_setting(self, setting_id): """ Retrieves an encrypted setting from the Kodi Add-on Settings. :param str setting_id: the ID for the setting to retrieve. :return: the decrypted value for the setting. :rtype: str """ Logger.info("Decrypting value for setting '%s'", setting_id) encrypted_value = AddonSettings.get_setting(setting_id) if not encrypted_value: Logger.warning("Found empty string as encrypted data") return encrypted_value try: decrypted_value = self.__decrypt(encrypted_value, Vault.__Key) if not decrypted_value.startswith(setting_id): Logger.error("Invalid decrypted value for setting '%s'", setting_id) return None decrypted_value = decrypted_value[len(setting_id) + 1:] Logger.info("Successfully decrypted value for setting '%s'", setting_id) except UnicodeDecodeError: Logger.error( "Invalid Unicode data returned from decryption. Must be wrong data" ) return None return decrypted_value
def active_authentication(self): """ Check if the user with the given name is currently authenticated. :returns: a AuthenticationResult with the account data. :rtype: AuthenticationResult """ login_token = AddonSettings.get_setting(self.__setting_signature, store=LOCAL) login_cookie = UriHandler.get_cookie("gmid", domain=".sso.rtl.nl") if login_token and \ login_cookie is not None and \ not login_cookie.is_expired(): # only retrieve the account information using the cookie and the token account_info_url = "https://sso.rtl.nl/accounts.getAccountInfo?{}" \ "&login_token={}".format(self.__common_params, login_token) account_info = UriHandler.open(account_info_url, no_cache=True) # See if it was successful auth_info = self.__extract_session_data(account_info) auth_info.existing_login = True return auth_info return AuthenticationResult(None)
def log_on(self, username=None, password=None): """ Logs on to a website, using an url. :param username: If provided overrides the Kodi stored username :param password: If provided overrides the Kodi stored username :return: indication if the login was successful. :rtype: bool First checks if the channel requires log on. If so and it's not already logged on, it should handle the log on. That part should be implemented by the specific channel. More arguments can be passed on, but must be handled by custom code. After a successful log on the self.loggedOn property is set to True and True is returned. """ username = username or AddonSettings.get_setting("dplayse_username") if self.__is_already_logged_on(username): return True # Local import to not slow down any other stuff import os import binascii try: # If running on Leia import pyaes except: # If running on Pre-Leia from resources.lib import pyaes import random now = int(time.time()) b64_now = binascii.b2a_base64(str(now).encode()).decode().strip() user_agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 " \ "(KHTML, like Gecko) Chrome/81.0.4044.129 Safari/537.36" device_id = AddonSettings.get_client_id().replace("-", "") window_id = "{}|{}".format( binascii.hexlify(os.urandom(16)).decode(), binascii.hexlify(os.urandom(16)).decode()) fe = [ "DNT:unknown", "L:en-US", "D:24", "PR:1", "S:1920,975", "AS:1920,935", "TO:-120", "SS:true", "LS:true", "IDB:true", "B:false", "ODB:true", "CPUC:unknown", "PK:Win32", "CFP:990181251", "FR:false", "FOS:false", "FB:false", "JSF:Arial", "P:Chrome PDF Plugin", "T:0,false,false", "H:4", "SWF:false" ] fs_murmur_hash = '48bf49e1796939175b0406859d00baec' data = [ { "key": "api_type", "value": "js" }, { "key": "p", "value": 1 }, # constant { "key": "f", "value": device_id }, # browser instance ID { "key": "n", "value": b64_now }, # base64 encoding of time.now() { "key": "wh", "value": window_id }, # WindowHandle ID { "key": "fe", "value": fe }, # browser properties { "key": "ife_hash", "value": fs_murmur_hash }, # hash of browser properties { "key": "cs", "value": 1 }, # canvas supported 0/1 { "key": "jsbd", "value": "{\"HL\":41,\"NCE\":true,\"DMTO\":1,\"DOTO\":1}" } ] data_value = JsonHelper.dump(data) stamp = now - (now % (60 * 60 * 6)) key_password = "******".format(user_agent, stamp) salt_bytes = os.urandom(8) key_iv = self.__evp_kdf(key_password.encode(), salt_bytes, key_size=8, iv_size=4, iterations=1, hash_algorithm="md5") key = key_iv["key"] iv = key_iv["iv"] encrypter = pyaes.Encrypter(pyaes.AESModeOfOperationCBC(key, iv)) encrypted = encrypter.feed(data_value) # Again, make a final call to flush any remaining bytes and strip padding encrypted += encrypter.feed() salt_hex = binascii.hexlify(salt_bytes) iv_hex = binascii.hexlify(iv) encrypted_b64 = binascii.b2a_base64(encrypted) bda = { "ct": encrypted_b64.decode(), "iv": iv_hex.decode(), "s": salt_hex.decode() } bda_str = JsonHelper.dump(bda) bda_base64 = binascii.b2a_base64(bda_str.encode()) req_dict = { "bda": bda_base64.decode(), "public_key": "FE296399-FDEA-2EA2-8CD5-50F6E3157ECA", "site": "https://client-api.arkoselabs.com", "userbrowser": user_agent, "simulate_rate_limit": "0", "simulated": "0", "rnd": "{}".format(random.random()) } req_data = "" for k, v in req_dict.items(): req_data = "{}{}={}&".format(req_data, k, HtmlEntityHelper.url_encode(v)) req_data = req_data.rstrip("&") arkose_data = UriHandler.open( "https://client-api.arkoselabs.com/fc/gt2/public_key/FE296399-FDEA-2EA2-8CD5-50F6E3157ECA", proxy=self.proxy, data=req_data, additional_headers={"user-agent": user_agent}, no_cache=True) arkose_json = JsonHelper(arkose_data) arkose_token = arkose_json.get_value("token") if "rid=" not in arkose_token: Logger.error("Error logging in. Invalid Arkose token.") return False Logger.debug("Succesfully required a login token from Arkose.") UriHandler.open( "https://disco-api.dplay.se/token?realm=dplayse&deviceId={}&shortlived=true" .format(device_id), proxy=self.proxy, no_cache=True) if username is None or password is None: from resources.lib.vault import Vault v = Vault() password = v.get_setting("dplayse_password") dplay_username = username dplay_password = password creds = { "credentials": { "username": dplay_username, "password": dplay_password } } headers = { "x-disco-arkose-token": arkose_token, "Origin": "https://auth.dplay.se", "x-disco-client": "WEB:10:AUTH_DPLAY_V1:2.4.1", # is not specified a captcha is required # "Sec-Fetch-Site": "same-site", # "Sec-Fetch-Mode": "cors", # "Sec-Fetch-Dest": "empty", "Referer": "https://auth.dplay.se/login", "User-Agent": user_agent } result = UriHandler.open("https://disco-api.dplay.se/login", proxy=self.proxy, json=creds, additional_headers=headers) if UriHandler.instance().status.code > 200: Logger.error("Failed to log in: %s", result) return False Logger.debug("Succesfully logged in") return True