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))
Exemple #3
0
    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