def __get_api_url(self, operation, hash_value, variables=None):
        """ Generates a GraphQL url

        :param str operation:   The operation to use
        :param str hash_value:  The hash of the Query
        :param dict variables:  Any variables to pass

        :return: A GraphQL string
        :rtype: str

        """

        extensions = {
            "persistedQuery": {
                "version": 1,
                "sha256Hash": hash_value
            }
        }
        extensions = HtmlEntityHelper.url_encode(
            JsonHelper.dump(extensions, pretty_print=False))

        final_vars = {"order_by": "NAME", "per_page": 1000}
        if variables:
            final_vars = variables
        final_vars = HtmlEntityHelper.url_encode(
            JsonHelper.dump(final_vars, pretty_print=False))

        url = "https://graphql.tv4play.se/graphql?" \
              "operationName={}&" \
              "variables={}&" \
              "extensions={}".format(operation, final_vars, extensions)
        return url
    def __get_api_persisted_url(self, operation, hash_value,
                                variables):  # NOSONAR
        """ Generates a GraphQL url

        :param str operation:   The operation to use
        :param str hash_value:  The hash of the Query
        :param dict variables:  Any variables to pass

        :return: A GraphQL string
        :rtype: str

        """

        extensions = {
            "persistedQuery": {
                "version": 1,
                "sha256Hash": hash_value
            }
        }
        extensions = HtmlEntityHelper.url_encode(
            JsonHelper.dump(extensions, pretty_print=False))

        variables = HtmlEntityHelper.url_encode(
            JsonHelper.dump(variables, pretty_print=False))

        url = "https://graph.kijk.nl/graphql?" \
              "operationName={}&" \
              "variables={}&" \
              "extensions={}".format(operation, variables, extensions)
        return url
    def authenticate(self, username, password):
        # Step 1: First initiate an authentication request
        auth_request = self.__get_authentication_request(username)
        auth_data = JsonHelper.dump(auth_request)
        auth_headers = {
            "X-Amz-Target": "AWSCognitoIdentityProviderService.InitiateAuth",
            "Accept-Encoding": "identity",
            "Content-Type": "application/x-amz-json-1.1"
        }
        auth_response = UriHandler.open(self.url,
                                        proxy=self.__proxy,
                                        params=auth_data,
                                        additional_headers=auth_headers,
                                        force_text=True)
        auth_response_json = JsonHelper(auth_response)
        challenge_parameters = auth_response_json.get_value(
            "ChallengeParameters")
        if self.__logger:
            self.__logger.trace(challenge_parameters)

        challenge_name = auth_response_json.get_value("ChallengeName")
        if not challenge_name == "PASSWORD_VERIFIER":
            message = auth_response_json.get_value("message")
            if self.__logger:
                self.__logger.error(
                    "Cannot start authentication challenge: %s", message
                    or None)
            return None

        # Step 2: Respond to the Challenge with a valid ChallengeResponse
        challenge_request = self.__get_challenge_response_request(
            challenge_parameters, password)
        challenge_data = JsonHelper.dump(challenge_request)
        challenge_headers = {
            "X-Amz-Target":
            "AWSCognitoIdentityProviderService.RespondToAuthChallenge",
            "Content-Type": "application/x-amz-json-1.1"
        }
        auth_response = UriHandler.open(self.url,
                                        proxy=self.__proxy,
                                        params=challenge_data,
                                        additional_headers=challenge_headers,
                                        force_text=True)

        auth_response_json = JsonHelper(auth_response)
        if "message" in auth_response_json.json:
            self.__logger.error("Error logging in: %s",
                                auth_response_json.get_value("message"))
            return None, None

        id_token = auth_response_json.get_value("AuthenticationResult",
                                                "IdToken")
        refresh_token = auth_response_json.get_value("AuthenticationResult",
                                                     "RefreshToken")
        return id_token, refresh_token
Example #4
0
    def renew_token(self, refresh_token):
        """
        Sets a new access token on the User using the refresh token. The basic expire time of the
        refresh token is 30 days:

        http://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-using-tokens-with-identity-providers.html

        :param str refresh_token:   Token to use for refreshing the authorization token.

        """

        refresh_request = {
            "AuthParameters": {
                "REFRESH_TOKEN": refresh_token
            },
            "ClientId": self.client_id,
            "AuthFlow": "REFRESH_TOKEN"
        }
        refresh_headers = {
            "X-Amz-Target": "AWSCognitoIdentityProviderService.InitiateAuth",
            "Content-Type": "application/x-amz-json-1.1"
        }
        refresh_request_data = JsonHelper.dump(refresh_request)
        refresh_response = UriHandler.open(self.url, proxy=self.__proxy,
                                           params=refresh_request_data,
                                           additional_headers=refresh_headers)
        refresh_json = JsonHelper(refresh_response)
        id_token = refresh_json.get_value("AuthenticationResult", "IdToken")
        return id_token
    def __init__(self, channel, settings_store, logger=None):
        """ Creates a Cloaker object that helps with cloaking objects

        :param ChannelInfo channel:             The ChannelInfo of the channel for which we need
                                                cloak information.
        :param SettingsStore settings_store:    The settings store to use for retrieving the
                                                settings data.
        :param any logger:                      A Logger object for logging purposes.

        """

        self.__logger = logger
        self.__channel = channel
        self.__channelId = channel.guid
        self.__settingsStore = settings_store

        if self.__logger:
            self.__logger.debug("Setting up a Cloaker based on '%s'",
                                self.__settingsStore)

        # Create a new file if none existed
        self.__cloaked = self.__settingsStore.get_setting("cloaked",
                                                          channel=channel,
                                                          default=None)
        if self.__cloaked is None:
            self.__cloaked = {}
            self.__store(False)

        if self.__logger:
            self.__logger.trace(
                "Found cloaked data:\n%s",
                JsonHelper.dump(self.__cloaked, pretty_print=True))
    def __send_git_hub_gist(self, name, code):
        """ Send a file to a Github gist.

        :param str|unicode name:            Name of the logfile paste/gist.
        :param str code:                    The content to post.

        :return: the ID of the gist
        :rtype: int

        """

        params = {
            "description": name,
            "public": False,
            "files": {
                name: {
                    "content": code
                }
            }
        }
        headers = {"Content-Type": "application/json"}
        post_data = JsonHelper.dump(params, pretty_print=False)
        data = UriHandler.open("https://api.github.com/gists",
                               params=post_data.encode(),
                               proxy=self.__proxy,
                               additional_headers=headers)
        if not data:
            raise IOError("Error posting Gist to GitHub")

        json_data = JsonHelper(data)
        url = json_data.get_value("html_url")
        if self.__logger:
            self.__logger.info("Gist: %s", url)

        # minify with google
        # POST https://www.googleapis.com/urlshortener/v1/url
        # Content-Type: application/json
        shortener = {"longUrl": url}
        google = "https://www.googleapis.com/urlshortener/v1/url?key=%s" % (
            self.__apiKey, )
        google_data = UriHandler.open(
            google,
            params=JsonHelper.dump(shortener, False),
            proxy=self.__proxy,
            additional_headers={"Content-Type": "application/json"})

        return JsonHelper(google_data).get_value("id")
Example #7
0
    def __rebuild_index(self):
        """ Rebuilds the channel index that contains all channels and performs all necessary steps:

        1. Find all channel add-on paths and determine the version of the channel add-on
        2. For all channel sets in the add-on:
            a. See if it is a new channel set (pyo and pyc check)
            b. If so, initialise the channel set and then perform the first time actions on
               the included channels.
            c. Add all channels within the channel set to the channelIndex

        Remark: this method only generates the index of the channels, it does not import at all!

        :return: The current channel index.
        :rtype: dict

        """

        if self.__reindexed:
            Logger.error("Channel index was already re-indexed this run. Not doing it again.")
            return self.__channelIndex

        Logger.info("Rebuilding the channel index.")
        index = {
            self.__CHANNEL_INDEX_ADD_ONS_KEY: [],
            self.__CHANNEL_INDEX_CHANNEL_KEY: {}
        }

        # iterate all Retrospect Channel Packages
        channel_pack_base, channel_pack_folders = self.__get_channel_pack_folders()
        for channel_pack_name in channel_pack_folders:
            index[self.__CHANNEL_INDEX_ADD_ONS_KEY].append(channel_pack_name)

            channel_package_path = os.path.join(channel_pack_base, channel_pack_name)
            channel_package_id, channel_add_on_version = self.__validate_and_get_add_on_version(channel_package_path)
            if channel_package_id is None:
                continue

            channel_sets = os.listdir(channel_package_path)
            for channel_set in channel_sets:
                if not os.path.isdir(os.path.join(channel_package_path, channel_set)):
                    continue

                channel_set_id = "chn_%s" % (channel_set,)
                Logger.debug("Found channel set '%s'", channel_set_id)
                index[self.__CHANNEL_INDEX_CHANNEL_KEY][channel_set_id] = {
                    self.__CHANNEL_INDEX_CHANNEL_VERSION_KEY: str(channel_add_on_version),
                    self.__CHANNEL_INDEX_CHANNEL_INFO_KEY: os.path.join(channel_package_path, channel_set, "%s.json" % (channel_set_id,))
                }

        with io.open(self.__CHANNEL_INDEX, 'wt+', encoding='utf-8') as f:
            f.write(JsonHelper.dump(index))

        # now we marked that we already re-indexed.
        self.__reindexed = True
        self.__channelIndex = index
        Logger.info("Rebuilding channel index completed with %d channelSets and %d add-ons: %s.",
                    len(index[self.__CHANNEL_INDEX_CHANNEL_KEY]),
                    len(index[self.__CHANNEL_INDEX_ADD_ONS_KEY]),
                    index)

        return index
    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
    def send_files(self, name, file_paths):
        """ Sends multiple files.

        :param str|unicode name:                Name for the gist/paste.
        :param list[str|unicode] file_paths:    List of file paths.

        :return: The result of the upload.
        :rtype: any

        """

        if self.__mode != "gist":
            raise ValueError("Invalid mode for multiple files")

        params = {
            "description": name,
            "public": False,
            "files": {
                # name: {
                #     "content": code
                # }
            }
        }

        for file_path in file_paths:
            if not os.path.isfile(file_path):
                continue
            code = self.__read_file_bytes(file_path)
            file_name = os.path.split(file_path)
            params["files"][file_name[-1]] = {"content": code}

        headers = {"Content-Type": "application/json"}
        post_data = JsonHelper.dump(params, pretty_print=False)
        data = UriHandler.open("https://api.github.com/gists",
                               params=post_data,
                               proxy=self.__proxy,
                               additional_headers=headers)
        if not data:
            raise IOError("Error posting Gist to GitHub")

        json_data = JsonHelper(data)
        url = json_data.get_value("html_url")
        if self.__logger:
            self.__logger.info("Gist: %s", url)

        # minify with google
        # POST https://www.googleapis.com/urlshortener/v1/url
        # Content-Type: application/json
        shortener = {"longUrl": url}
        google = "https://www.googleapis.com/urlshortener/v1/url?key=%s" % (
            self.__apiKey, )
        google_data = UriHandler.open(
            google,
            params=JsonHelper.dump(shortener, False),
            proxy=self.__proxy,
            additional_headers={"Content-Type": "application/json"})

        google_url = JsonHelper(google_data).get_value("id")
        if self.__logger:
            self.__logger.info("Goo.gl: %s", google_url)
        return google_url