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
Exemple #4
0
    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