def _mastertoken_checks(self):
     """Perform checks to the MasterToken and executes a new key handshake when necessary"""
     is_handshake_required = False
     if self.crypto.mastertoken:
         if self.crypto.is_current_mastertoken_expired():
             common.debug(
                 'Stored MSL MasterToken is expired, a new key handshake will be performed'
             )
             is_handshake_required = True
         else:
             # Check if the current ESN is same of ESN bound to MasterToken
             if G.get_esn() != self.crypto.bound_esn:
                 common.debug(
                     'Stored MSL MasterToken is bound to a different ESN, '
                     'a new key handshake will be performed')
                 is_handshake_required = True
     else:
         common.debug(
             'MSL MasterToken is not available, a new key handshake will be performed'
         )
         is_handshake_required = True
     if is_handshake_required:
         if self.perform_key_handshake():
             msl_data = json.loads(common.load_file_def(MSL_DATA_FILENAME))
             self.crypto.load_msl_data(msl_data)
             self.crypto.load_crypto_session(msl_data)
Exemple #2
0
    def release_license(self, data=None):  # pylint: disable=unused-argument
        """Release the server license"""
        try:
            # When you try to play a video while another one is currently in playing,
            # a new license to be released will be queued, so the oldest license must be released
            url = self.licenses_release_url.pop()
            sid = self.licenses_session_id.pop()
            xid = self.licenses_xid.pop()

            common.debug('Requesting releasing license')
            params = [{
                'url': url,
                'params': {
                    'drmSessionId': sid,
                    'xid': xid
                },
                'echo': 'drmSessionId'
            }]

            response = self.msl_requests.chunked_request(
                ENDPOINTS['license'],
                self.msl_requests.build_request_data('/bundle', params),
                G.get_esn())
            common.debug('License release response: {}', response)
        except IndexError:
            # Example the supplemental media type have no license
            common.debug('No license to release')
Exemple #3
0
 def bind_events(self):
     """Bind events"""
     # I don't know the real purpose of its use, it seems to be requested after the license and before starting
     # playback, and only the first time after a switch,
     # in the response you can also understand if the msl switch has worked
     common.debug('Requesting bind events')
     response = self.msl_requests.chunked_request(
         ENDPOINTS['events'],
         self.msl_requests.build_request_data('/bind', {}),
         G.get_esn(),
         disable_msl_switch=False)
     common.debug('Bind events response: {}', response)
Exemple #4
0
 def _show_only_forced_subtitle(self):
     # Forced stream not found, then fix Kodi bug if user chose to apply the workaround
     # Kodi bug???:
     # If the kodi player is set with "forced only" subtitle setting, Kodi use this behavior:
     # 1) try to select forced subtitle that matches audio language
     # 2) if missing the forced subtitle in language, then
     #    Kodi try to select: The first "forced" subtitle or the first "regular" subtitle
     #    that can respect the chosen language or not, depends on the available streams
     # So can cause a wrong subtitle language or in a permanent display of subtitles!
     # This does not reflect the setting chosen in the Kodi player and is very annoying!
     # There is no other solution than to disable the subtitles manually.
     if self.legacy_kodi_version:
         # --- ONLY FOR KODI VERSION 18 ---
         # NOTE: With Kodi 18 it is not possible to read the properties of the streams
         # so the only possible way is to read the data from the manifest file
         audio_language = common.get_kodi_audio_language()
         cache_identifier = G.get_esn() + '_' + self.videoid.value
         manifest_data = G.CACHE.get(CACHE_MANIFESTS, cache_identifier)
         common.fix_locale_languages(manifest_data['timedtexttracks'])
         if not any(
                 text_track.get('isForcedNarrative', False) is True
                 and text_track['language'] == audio_language
                 for text_track in manifest_data['timedtexttracks']):
             self.sc_settings.update({'subtitleenabled': False})
     else:
         # --- ONLY FOR KODI VERSION 19 ---
         audio_language = common.get_kodi_audio_language(
             iso_format=xbmc.ISO_639_2, use_fallback=False)
         if audio_language == 'mediadefault':
             # Find the language of the default audio track
             audio_list = self.player_state.get(STREAMS['audio']['list'])
             for audio_track in audio_list:
                 if audio_track['isdefault']:
                     audio_language = audio_track['language']
                     break
         player_stream = self.player_state.get(
             STREAMS['subtitle']['current'])
         if player_stream is None:
             return
         if audio_language == 'original':
             # Do nothing
             is_language_appropriate = True
         else:
             is_language_appropriate = player_stream[
                 'language'] == audio_language
         # Check if the current stream is forced and with an appropriate subtitle language
         if not player_stream['isforced'] or not is_language_appropriate:
             self.sc_settings.update({'subtitleenabled': False})
    def perform_key_handshake(self, data=None):  # pylint: disable=unused-argument
        """Perform a key handshake and initialize crypto keys"""
        esn = G.get_esn()
        if not esn:
            common.warn('Cannot perform key handshake, missing ESN')
            return False

        common.info('Performing key handshake with ESN: {}',
                    common.censure(esn) if G.ADDON.getSetting('esn') else esn)
        response = _process_json_response(
            self._post(ENDPOINTS['manifest'], self.handshake_request(esn)))
        header_data = self.decrypt_header_data(response['headerdata'], False)
        self.crypto.parse_key_response(header_data, esn, True)

        # Delete all the user id tokens (are correlated to the previous mastertoken)
        self.crypto.clear_user_id_tokens()
        common.debug('Key handshake successful')
        return True
Exemple #6
0
    def load_manifest(self, viewable_id):
        """
        Loads the manifests for the given viewable_id and returns a mpd-XML-Manifest

        :param viewable_id: The id of of the viewable
        :return: MPD XML Manifest or False if no success
        """
        try:
            manifest = self._load_manifest(viewable_id, G.get_esn())
        except MSLError as exc:
            if 'Email or password is incorrect' in G.py2_decode(str(exc)):
                # Known cases when MSL error "Email or password is incorrect." can happen:
                # - If user change the password when the nf session was still active
                # - Netflix has reset the password for suspicious activity when the nf session was still active
                # Then clear the credentials and also user tokens.
                common.purge_credentials()
                self.msl_requests.crypto.clear_user_id_tokens()
            raise
        return self.__tranform_to_dash(manifest)
 def _get_owner_user_id_token(self):
     """A way to get the user token id of owner profile"""
     # In order to get a user id token of another (non-owner) profile you must make a request with SWITCH_PROFILE
     # authentication scheme (a custom authentication for netflix), and this request can be directly included
     # in the MSL manifest request.
     # But in order to execute this switch profile, you need to have the user id token of the main (owner) profile.
     # The only way (found to now) to get it immediately, is send a logblob event request, and save the
     # user id token obtained in the response.
     common.debug('Requesting logblog')
     params = {'reqAttempt': 1, 'reqPriority': 0, 'reqName': EVENT_BIND}
     url = ENDPOINTS['logblobs'] + '?' + urlencode(params).replace(
         '%2F', '/')
     response = self.chunked_request(url,
                                     self.build_request_data(
                                         '/logblob',
                                         generate_logblobs_params()),
                                     G.get_esn(),
                                     force_auth_credential=True)
     common.debug('Response of logblob request: {}', response)
Exemple #8
0
 def _process_event_request(self, event):
     """Do the event post request"""
     event.status = Event.STATUS_REQUESTED
     # Request attempts can be made up to a maximum of 3 times per event
     while event.is_attempts_granted():
         common.info('EVENT [{}] - Executing request (attempt {})', event,
                     event.req_attempt)
         params = {
             'reqAttempt': event.req_attempt,
             'reqPriority': 20 if event.event_type == EVENT_START else 0,
             'reqName': 'events/{}'.format(event)
         }
         url = ENDPOINTS['events'] + '?' + urlencode(params).replace(
             '%2F', '/')
         try:
             response = self.chunked_request(url,
                                             event.request_data,
                                             G.get_esn(),
                                             disable_msl_switch=False)
             event.set_response(response)
             break
         except Exception as exc:  # pylint: disable=broad-except
             common.error('EVENT [{}] - The request has failed: {}', event,
                          exc)
     if event.event_type == EVENT_STOP:
         self.clear_queue()
         if event.event_data['allow_request_update_loco']:
             # Calls to nfsession
             common.make_http_call('update_loco_context',
                                   {'context_name': 'continueWatching'})
             common.make_http_call('update_videoid_bookmark',
                                   {'video_id': event.get_video_id()})
     # Below commented lines: let future requests continue to be sent, unstable connections like wi-fi cause problems
     # if not event.is_response_success():
     # The event request is unsuccessful then there is some problem,
     # no longer make any future requests from this event id
     #     return False
     return True
Exemple #9
0
    def get_license(self, challenge, sid):
        """
        Requests and returns a license for the given challenge and sid

        :param challenge: The base64 encoded challenge
        :param sid: The sid paired to the challenge
        :return: Base64 representation of the license key or False unsuccessful
        """
        common.debug('Requesting license')

        timestamp = int(time.time() * 10000)
        xid = str(timestamp + 1610)
        params = [{
            'drmSessionId': sid,
            'clientTime': int(timestamp / 10000),
            'challengeBase64': challenge,
            'xid': xid
        }]
        self.manifest_challenge = challenge
        endpoint_url = ENDPOINTS[
            'license'] + '?reqAttempt=1&reqPriority=0&reqName=prefetch/license'
        response = self.msl_requests.chunked_request(
            endpoint_url,
            self.msl_requests.build_request_data(self.last_license_url, params,
                                                 'drmSessionId'), G.get_esn())
        # This xid must be used also for each future Event request, until playback stops
        G.LOCAL_DB.set_value('xid', xid, TABLE_SESSION)

        self.licenses_xid.insert(0, xid)
        self.licenses_session_id.insert(0, sid)
        self.licenses_release_url.insert(
            0, response[0]['links']['releaseLicense']['href'])

        if self.msl_requests.msl_switch_requested:
            self.msl_requests.msl_switch_requested = False
            self.bind_events()
        return response[0]['licenseResponseBase64']
 def view_esn(self, pathitems=None):  # pylint: disable=unused-argument
     """Show the ESN in use"""
     ui.show_ok_dialog(common.get_local_string(30016), G.get_esn())
Exemple #11
0
def get_manifest(videoid):
    """Get the manifest from cache"""
    cache_identifier = G.get_esn() + '_' + videoid.value
    return G.CACHE.get(CACHE_MANIFESTS, cache_identifier)
Exemple #12
0
 def load_msl_data(self, msl_data=None):
     self._msl_data = msl_data if msl_data else {}
     if msl_data:
         self.set_mastertoken(msl_data['tokens']['mastertoken'])
         self.bound_esn = msl_data.get('bound_esn', G.get_esn())
 def _verify_esn_existence():
     return bool(G.get_esn())