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 execute(self): try: # Import vault here, as it is only used here or in a channel # that supports it from resources.lib.vault import Vault if self.vault_action == action.RESET_VAULT: Vault.reset() return v = Vault() if self.vault_action == action.SET_ENCRYPTION_PIN: v.change_pin() elif self.vault_action == action.SET_ENCRYPTED_VALUE: v.set_setting(self.params[keyword.SETTING_ID], self.params.get(keyword.SETTING_NAME, ""), self.params.get(keyword.SETTING_ACTION_ID, None)) finally: if keyword.SETTING_TAB_FOCUS in self.params: AddonSettings.show_settings( self.params[keyword.SETTING_TAB_FOCUS], self.params.get(keyword.SETTING_FOCUS, None))
def __init__(self, addon_name, params, handle=0): # NOSONAR complexity """ Initialises the plugin with given arguments. :param str addon_name: The add-on name. :param str params: The input parameters from the query string. :param int handle: The Kodi directory handle. """ Logger.info("******** Starting %s add-on version %s/repo *********", Config.appName, Config.version) # noinspection PyTypeChecker super(Plugin, self).__init__(addon_name, handle, params) Logger.debug(self) # Container Properties self.propertyRetrospect = "Retrospect" self.propertyRetrospectChannel = "RetrospectChannel" self.propertyRetrospectChannelSetting = "RetrospectChannelSettings" self.propertyRetrospectFolder = "RetrospectFolder" self.propertyRetrospectVideo = "RetrospectVideo" self.propertyRetrospectCloaked = "RetrospectCloaked" self.propertyRetrospectCategory = "RetrospectCategory" self.propertyRetrospectFavorite = "RetrospectFavorite" self.propertyRetrospectAdaptive = "RetrospectAdaptive" # channel objects self.channelObject = None self.channelFile = "" self.channelCode = None self.methodContainer = dict( ) # : storage for the inspect.getmembers(channel) method. Improves performance # are we in session? session_active = SessionHelper.is_session_active(Logger.instance()) # fetch some environment settings env_ctrl = envcontroller.EnvController(Logger.instance()) if not session_active: # do add-on start stuff Logger.info("Add-On start detected. Performing startup actions.") # print the folder structure env_ctrl.print_retrospect_settings_and_folders( Config, AddonSettings) # show notification XbmcWrapper.show_notification(None, LanguageHelper.get_localized_string( LanguageHelper.StartingAddonId) % (Config.appName, ), fallback=False, logger=Logger) # check for updates. Using local import for performance from resources.lib.updater import Updater up = Updater(Config.updateUrl, Config.version, UriHandler.instance(), Logger.instance(), AddonSettings.get_release_track()) if up.is_new_version_available(): Logger.info("Found new version online: %s vs %s", up.currentVersion, up.onlineVersion) notification = LanguageHelper.get_localized_string( LanguageHelper.NewVersion2Id) notification = notification % (Config.appName, up.onlineVersion) XbmcWrapper.show_notification(None, lines=notification, display_time=20000) # check for cache folder env_ctrl.cache_check() # do some cache cleanup env_ctrl.cache_clean_up(Config.cacheDir, Config.cacheValidTime) # empty picklestore self._pickler.purge_store(Config.addonId) # create a session SessionHelper.create_session(Logger.instance()) #=============================================================================== # Start the plugin version of progwindow #=============================================================================== if len(self.params) == 0: # Show initial start if not in a session # now show the list if AddonSettings.show_categories(): self.show_categories() else: self.show_channel_list() #=============================================================================== # Start the plugin verion of the episode window #=============================================================================== else: # Determine what stage we are in. Check that there are more than 2 Parameters if len(self.params) > 1 and self.keywordChannel in self.params: # retrieve channel characteristics self.channelFile = os.path.splitext( self.params[self.keywordChannel])[0] self.channelCode = self.params[self.keywordChannelCode] Logger.debug( "Found Channel data in URL: channel='%s', code='%s'", self.channelFile, self.channelCode) # import the channel channel_register = ChannelIndex.get_register() channel = channel_register.get_channel(self.channelFile, self.channelCode) if channel is not None: self.channelObject = channel else: Logger.critical( "None or more than one channels were found, unable to continue." ) return # init the channel as plugin self.channelObject.init_channel() Logger.info("Loaded: %s", self.channelObject.channelName) elif self.keywordCategory in self.params \ or self.keywordAction in self.params and ( self.params[self.keywordAction] == self.actionAllFavourites or self.params[self.keywordAction] == self.actionRemoveFavourite): # no channel needed for these favourites actions. pass # =============================================================================== # Vault Actions # =============================================================================== elif self.keywordAction in self.params and \ self.params[self.keywordAction] in \ ( self.actionSetEncryptedValue, self.actionSetEncryptionPin, self.actionResetVault ): try: # Import vault here, as it is only used here or in a channel # that supports it from resources.lib.vault import Vault action = self.params[self.keywordAction] if action == self.actionResetVault: Vault.reset() return v = Vault() if action == self.actionSetEncryptionPin: v.change_pin() elif action == self.actionSetEncryptedValue: v.set_setting( self.params[self.keywordSettingId], self.params.get(self.keywordSettingName, ""), self.params.get(self.keywordSettingActionId, None)) finally: if self.keywordSettingTabFocus in self.params: AddonSettings.show_settings( self.params[self.keywordSettingTabFocus], self.params.get(self.keywordSettingSettingFocus, None)) return elif self.keywordAction in self.params and \ self.actionPostLog in self.params[self.keywordAction]: self.__send_log() return elif self.keywordAction in self.params and \ self.actionProxy in self.params[self.keywordAction]: # do this here to not close the busy dialog on the SetProxy when # a confirm box is shown title = LanguageHelper.get_localized_string( LanguageHelper.ProxyChangeConfirmTitle) content = LanguageHelper.get_localized_string( LanguageHelper.ProxyChangeConfirm) if not XbmcWrapper.show_yes_no(title, content): Logger.warning( "Stopping proxy update due to user intervention") return language = self.params.get(self.keywordLanguage, None) proxy_id = self.params.get(self.keywordProxy, None) local_ip = self.params.get(self.keywordLocalIP, None) self.__set_proxy(language, proxy_id, local_ip) return else: Logger.critical("Error determining Plugin action") return #=============================================================================== # See what needs to be done. #=============================================================================== if self.keywordAction not in self.params: Logger.critical( "Action parameters missing from request. Parameters=%s", self.params) return elif self.params[self.keywordAction] == self.actionListCategory: self.show_channel_list(self.params[self.keywordCategory]) elif self.params[ self.keywordAction] == self.actionConfigureChannel: self.__configure_channel(self.channelObject) elif self.params[self.keywordAction] == self.actionFavourites: # we should show the favourites self.show_favourites(self.channelObject) elif self.params[self.keywordAction] == self.actionAllFavourites: self.show_favourites(None) elif self.params[self.keywordAction] == self.actionListFolder: # channelName and URL is present, Parse the folder self.process_folder_list() elif self.params[self.keywordAction] == self.actionPlayVideo: self.play_video_item() elif not self.params[self.keywordAction] == "": self.on_action_from_context_menu( self.params[self.keywordAction]) else: Logger.warning( "Number of parameters (%s) or parameter (%s) values not implemented", len(self.params), self.params) self.__fetch_textures() return
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 """ api_key = "3_qhEcPa5JGFROVwu5SWKqJ4mVOIkwlFNMSKwzPDAh8QZOtHqu6L4nD5Q7lk0eXOOG" # Do we still have a valid short living token (1 hour)? If so, we have an active session. short_login_cookie = UriHandler.get_cookie("X-VRT-Token", ".vrt.be") if short_login_cookie is not None: # The old X-VRT-Token expired after 1 year. We don't want that old cookie short_login_cookie_can_live_too_long = \ DateHelper.get_date_from_posix(short_login_cookie.expires) > datetime.datetime.now() + datetime.timedelta(hours=4) if not short_login_cookie_can_live_too_long: Logger.debug("Using existing VRT.be session.") return True # Do we still have a valid long living token? If so, try to extend the session. We need the # original UIDSignature value for that. The 'vrtlogin-rt' and all other related cookies # are valid for a same period (1 year). long_login_cookie = UriHandler.get_cookie("vrtlogin-rt", ".vrt.be") if long_login_cookie is not None: # if we stored a valid user signature, we can use it, together with the 'gmid' and # 'ucid' cookies to extend the session and get new token data data = UriHandler.open("https://token.vrt.be/refreshtoken", proxy=self.proxy, no_cache=True) if "vrtnutoken" in data: Logger.debug("Refreshed the VRT.be session.") return True Logger.warning("Failed to extend the VRT.be session.") username = self._get_setting("username") if not username: Logger.warning("No username configured for VRT.nu") return None v = Vault() password = v.get_channel_setting(self.guid, "password") if not password: Logger.warning("Found empty password for VRT user") # Get a 'gmid' and 'ucid' cookie by logging in. Valid for 10 years Logger.debug("Using: %s / %s", username, "*" * len(password)) url = "https://accounts.vrt.be/accounts.login" data = { "loginID": username, "password": password, "sessionExpiration": "-1", "targetEnv": "jssdk", "include": "profile,data,emails,subscriptions,preferences,", "includeUserInfo": "true", "loginMode": "standard", "lang": "nl-inf", "APIKey": api_key, "source": "showScreenSet", "sdk": "js_latest", "authMode": "cookie", "format": "json" } logon_data = UriHandler.open(url, data=data, proxy=self.proxy, no_cache=True) user_id, signature, signature_time_stamp = self.__extract_session_data(logon_data) if user_id is None or signature is None or signature_time_stamp is None: return False # We need to initialize the token retrieval which will redirect to the actual token UriHandler.open("https://token.vrt.be/vrtnuinitlogin?provider=site&destination=https://www.vrt.be/vrtnu/", proxy=self.proxy, no_cache=True) # Now get the actual VRT tokens (X-VRT-Token....). Valid for 1 hour. So we call the actual # perform_login url which will redirect and get cookies. csrf = UriHandler.get_cookie("OIDCXSRF", "login.vrt.be") if csrf is None: csrf = UriHandler.get_cookie("XSRF-TOKEN", "login.vrt.be") token_data = { "UID": user_id, "UIDSignature": signature, "signatureTimestamp": signature_time_stamp, "client_id": "vrtnu-site", "submit": "submit", "_csrf": csrf.value } UriHandler.open("https://login.vrt.be/perform_login", proxy=self.proxy, data=token_data, no_cache=True) return True
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