def sync_down(params):
    """Sync full or partial data down to the client"""

    if not params.server:
        raise CommunicationError('No server provided')

    if not params.user:
        raise CommunicationError('No username provided')

    print('Downloading records...')

    def make_json(params):
        return {
               'include':[
                   'sfheaders',
                   'sfrecords',
                   'sfusers',
                   'sfteams'
               ],
               'revision':params.revision,
               'client_time':current_milli_time(),
               'device_id':'Commander', 
               'device_name':'Commander', 
               'command':'sync_down', 
               'protocol_version':1, 
               'client_version':CLIENT_VERSION,
               '2fa_token':params.mfa_token,
               '2fa_type':params.mfa_type, 
               'session_token':params.session_token, 
               'username':params.user
        }
        
    if not params.session_token:
        try:
            login(params)
        except:
            raise
            
    payload = make_json(params)
    
    try:
        r = requests.post(params.server, json=payload)
    except:
        raise CommunicationError(sys.exc_info()[0])

    response_json = r.json()

    if response_json['result_code'] == 'auth_failed':
        if params.debug: print('Re-authorizing.')

        try:
            login(params)
        except:
            raise

        payload = make_json(params)

        try:
            r = requests.post(params.server, json=payload)
        except:
            print('Comm error after re-authorizing')
            raise CommunicationError(sys.exc_info()[0])

        response_json = r.json()


    if params.debug:
        debug_response(params, payload, r)

    if response_json['result'] == 'success':

        if 'full_sync' in response_json:
            if response_json['full_sync']:
                if params.debug: print('Full Sync response')
                params.record_cache = {}  
                params.meta_data_cache = {}  
                params.shared_folder_cache = {}  

        if 'revision' in response_json:
            params.revision = response_json['revision']
            if params.debug: print('Getting revision ' + str(params.revision))
    
        if 'removed_records' in response_json:
            if params.debug: print('Processing removed records')
            for uid in response_json['removed_records']:
                del params.record_cache[uid]
    
        if 'removed_shared_folders' in response_json:
            if params.debug: print('Processing removed shared folders')
            for shared_folder in response_json['removed_shared_folders']:
                if 'records' in shared_folder:
                    for record in shared_folder['records']: 
                        if 'record_uid' in record:
                            record_uid = record['record_uid']
                            if record_uid in params.record_cache:
                                if not params.record_cache[record_uid]['owner']:
                                    if num_folders_with_record(record_uid) == 1:
                                        del params.record_cache[record_uid]
            for uid in response_json['removed_shared_folders']:
                del params.shared_folder_cache[uid]

        # convert record keys from RSA to AES-256
        if 'record_meta_data' in response_json:
            if params.debug: print('Processing record_meta_data')
            for meta_data in response_json['record_meta_data']:

                if params.debug: print('meta data: ' + str(meta_data))

                if not 'record_key' in meta_data: 
                    # old record that doesn't have a record key so make one
                    if params.debug: print('...no record key.  creating...')
                    unencrypted_key = os.urandom(32)
                    iv = os.urandom(16)
                    cipher = AES.new(params.data_key, AES.MODE_CBC, iv)
                    type1key = iv + cipher.encrypt(pad_binary(unencrypted_key))

                    if params.debug: print('generated key=' + str(type1key))

                    # store as b64 encoded string
                    # note: decode() converts bytestream (b') to string
                    # note2: remove == from the end 
                    meta_data['record_key'] = \
                        (base64.urlsafe_b64encode(
                            type1key).decode()).rstrip('==')
                    meta_data['record_key_type'] = 1

                    # temporary flag for decryption routine below
                    meta_data['old_record_flag'] = True 

                if meta_data['record_key_type'] == 2:
                    if params.debug: print('Converting RSA-encrypted key')

                    # decrypt the type2 key using their RSA key
                    decoded_key = base64.urlsafe_b64decode(
                        meta_data['record_key'] +'==')
                    dsize = SHA.digest_size
                    sentinel = Random.new().read(15+dsize)
                    cipher = PKCS1_v1_5.new(params.rsa_key)
                    unencrypted_key = cipher.decrypt(decoded_key, sentinel)

                    if len(unencrypted_key) != 32:
                        raise CryptoError('Invalid record key length')

                    if params.debug: 
                        print('Before: ' + str(meta_data['record_key'])) 
                        print('After: ' + str(unencrypted_key)) 

                    # re-encrypt as type1 key with user's data key
                    iv = os.urandom(16)
                    cipher = AES.new(params.data_key, AES.MODE_CBC, iv)
                    type1key = iv + cipher.encrypt(pad_binary(unencrypted_key))

                    # store as b64 encoded string
                    # note: decode() converts bytestream (b') to string
                    # note2: remove == from the end 
                    meta_data['record_key'] = \
                        (base64.urlsafe_b64encode(
                            type1key).decode()).rstrip('==')
                    meta_data['record_key_type'] = 1 

                    if params.debug: 
                        print('encrypted record key: ' + str(type1key)) 
                        print('base64: ' + str(meta_data['record_key'])) 

                # add to local cache
                if params.debug: print('Adding meta data to cache')
                params.meta_data_cache[meta_data['record_uid']] = meta_data
    

        # decrypt shared folder keys and folder name
        if 'shared_folders' in response_json:
            if params.debug: print('Processing shared_folders')
            for shared_folder in response_json['shared_folders']:
                decoded_key = base64.urlsafe_b64decode(
                    shared_folder['shared_folder_key'] +'==')

                if shared_folder['key_type'] == 1:
                    # decrypt folder key with data_key 
                    iv = decoded_key[:16]
                    ciphertext = decoded_key[16:]
                    cipher = AES.new(params.data_key, AES.MODE_CBC, iv)
                    unencrypted_key = unpad_binary(cipher.decrypt(ciphertext))

                if shared_folder['key_type'] == 2:
                    # decrypt folder key with RSA key
                    dsize = SHA.digest_size
                    sentinel = Random.new().read(15+dsize)
                    cipher = PKCS1_v1_5.new(params.rsa_key)
                    unencrypted_key = cipher.decrypt(decoded_key, sentinel)

                if params.debug: 
                    print('Type=' + str(shared_folder['key_type']) + \
                        ' Record Key: ' + str(unencrypted_key))

                if len(unencrypted_key) != 32:
                    raise CryptoError('Invalid folder key length')
                    
                # save the decrypted key
                shared_folder['shared_folder_key'] = unencrypted_key

                # decrypt the folder name
                decoded_folder_name = base64.urlsafe_b64decode(
                    shared_folder['name'] +'==')
                iv = decoded_folder_name[:16]
                ciphertext = decoded_folder_name[16:]
                cipher = AES.new(unencrypted_key, AES.MODE_CBC, iv)
                folder_name = unpad_binary(cipher.decrypt(ciphertext))

                if params.debug: print('Folder name: ' + str(folder_name))
                shared_folder['name'] = folder_name

                # add to local cache
                params.shared_folder_cache[shared_folder['shared_folder_uid']] \
                    = shared_folder


        # decrypt record keys
        if 'records' in response_json:
            if params.debug: print('Processing records')
            for record in response_json['records']:
                record_uid = record['record_uid']

                if params.debug: 
                    print('Looking for record key on ' + str(record_uid))

                if record_uid in params.meta_data_cache:
                    # merge meta data into record
                    record.update(params.meta_data_cache[record_uid])
                   
                unencrypted_key = ''
                if 'record_key' in record:
                    # decrypt record key with my data key
                    if params.debug: print('Record: ' + str(record))
                    record_key = record['record_key']
                    decoded_key = base64.urlsafe_b64decode(record_key+'==')
                    iv = decoded_key[:16]
                    ciphertext = decoded_key[16:]
                    cipher = AES.new(params.data_key, AES.MODE_CBC, iv)
                    unencrypted_key = cipher.decrypt(ciphertext)[:32]
                    if params.debug: 
                        print('...unencrypted_key=' + str(unencrypted_key))
                else: 
                    # If record has no record_key, look in a shared folder
                    for shared_folder_uid in params.shared_folder_cache:
                        shared_folder = \
                            params.shared_folder_cache[shared_folder_uid]
                        sf_key = shared_folder['shared_folder_key']
                        if 'records' in shared_folder:
                            sf_records = shared_folder['records']
                            for sf_record in sf_records:
                                if 'record_uid' in sf_record:
                                    if sf_record['record_uid'] == record_uid:
                                        if 'record_key' in sf_record:
                                            sf_rec_key = sf_record['record_key']
                                            record['record_key'] = sf_rec_key

                                            decoded_key = \
                                                base64.urlsafe_b64decode(
                                                sf_rec_key +'==')
                                             
                                            iv = decoded_key[:16]
                                            ciphertext = decoded_key[16:]
                                            cipher = AES.new(sf_key, \
                                                AES.MODE_CBC, iv)
                                            unencrypted_key = \
                                                cipher.decrypt(ciphertext)[:32]

                if unencrypted_key:
                    if len(unencrypted_key) != 32:
                        raise CryptoError('Invalid record key length')

                    # save the decrypted key in record_key_unencrypted
                    record['record_key_unencrypted'] = unencrypted_key
                else:
                    raise CryptoError('No record key found')

                if params.debug: 
                    print('Got record key: ' + str(unencrypted_key))

                ''' Decrypt the record data and extra... '''

                if ('old_record_flag' in record) and record['old_record_flag']:
                    # special case for super old records that are encrypted
                    # with the data key. no extra exists for these.
                    if params.debug: print('Old record type...')
                    decoded_data = \
                        base64.urlsafe_b64decode(record['data'] +'==')
                    iv = decoded_data[:16]
                    ciphertext = decoded_data[16:]
                    cipher = AES.new(params.data_key, AES.MODE_CBC, iv)
                    record['data'] = unpad_binary(cipher.decrypt(ciphertext))
                    record['record_key_type'] = 1 

                elif 'data' in record:
                    # encrypted with record key
                    if params.debug: print('Got data')
                    decoded_data = \
                        base64.urlsafe_b64decode(record['data'] +'==')
                    iv = decoded_data[:16]
                    ciphertext = decoded_data[16:]
                    cipher = AES.new(record['record_key_unencrypted'], \
                        AES.MODE_CBC, iv)
                    record['data'] = unpad_binary(cipher.decrypt(ciphertext))
                else:
                    if params.debug: print('No data')
                    record['data'] = b'{}' 
    
                if 'extra' in record:
                    if params.debug: print('Got extra')
                    decoded_extra = \
                        base64.urlsafe_b64decode(record['extra'] +'==')
                    iv = decoded_extra[:16]
                    ciphertext = decoded_extra[16:]
                    cipher = AES.new(\
                        record['record_key_unencrypted'], AES.MODE_CBC, iv)
                    record['extra'] = unpad_binary(cipher.decrypt(ciphertext))
                else:
                    if params.debug: print('No extra')
                    record['extra'] = b'{}' 

                # Store the record in the cache
                if params.debug: 
                    print('record is dict: ' + str(isinstance(record, dict)))
                    print('params.record_cache is dict: ' + \
                        str(isinstance(params.record_cache, dict)))
                    print('record is ' + str(record))

                params.record_cache[record_uid] = record 


        if 'pending_shares_from' in response_json:
            print('Note: You have pending share requests.')

        if params.debug:
            print('--- Meta Data Cache: ' + str(params.meta_data_cache))
            print('--- Record Cache: ' + str(params.record_cache))
            print('--- Folders Cache: ' + str(params.shared_folder_cache))

        if len(params.record_cache) == 1:
            print('Downloaded & Decrypted [1] Record')
        else:
            print('Downloaded & Decrypted [' + \
                str(len(params.record_cache)) + '] Records')

    else :
        if response_json['result_code'] == 'auth_failed':
            raise CommunicationError('Authentication Failed. ' + \
                'Check email, password or Two-Factor code.')
        else:            
            raise CommunicationError('Unknown comm problem')
dataset/ETHPy150Open Keeper-Security/Commander/keepercommander/api.py/sync_down/original