Пример #1
0
def access_logs(window: Union['TxWindow', 'RxWindow'],
                contact_list: 'ContactList',
                group_list: 'GroupList',
                settings: 'Settings',
                master_key: 'MasterKey',
                msg_to_load: int = 0,
                export: bool = False) -> None:
    """\
    Load 'msg_to_load' last messages from log database and display or
    export them.

    The default value of zero for `msg_to_load` means all messages for
    the window will be retrieved from the log database.
    """
    file_name = f'{DIR_USER_DATA}{settings.software_operation}_logs'
    packet_list = PacketList(settings, contact_list)
    message_list = []  # type: List[MsgTuple]
    group_msg_id = b''

    check_log_file_exists(file_name)
    message_log = MessageLog(file_name, master_key.master_key)

    for log_entry in message_log:
        onion_pub_key, timestamp, origin, assembly_packet \
            = separate_headers(log_entry, [ONION_SERVICE_PUBLIC_KEY_LENGTH, TIMESTAMP_LENGTH, ORIGIN_HEADER_LENGTH])

        if window.type == WIN_TYPE_CONTACT and onion_pub_key != window.uid:
            continue

        packet = packet_list.get_packet(onion_pub_key,
                                        origin,
                                        MESSAGE,
                                        log_access=True)
        try:
            packet.add_packet(assembly_packet)
        except SoftError:
            continue
        if not packet.is_complete:
            continue

        group_msg_id = add_complete_message_to_message_list(
            timestamp, onion_pub_key, group_msg_id, packet, message_list,
            window)

    message_log.close_database()

    print_logs(message_list[-msg_to_load:], export, msg_to_load, window,
               contact_list, group_list, settings)
Пример #2
0
def change_log_db_key(old_key: bytes, new_key: bytes,
                      settings: 'Settings') -> None:
    """Re-encrypt the log database with a new master key."""
    ensure_dir(DIR_USER_DATA)
    file_name = f'{DIR_USER_DATA}{settings.software_operation}_logs'
    temp_name = file_name + TEMP_SUFFIX

    if not os.path.isfile(file_name):
        raise SoftError("No log database available.")

    if os.path.isfile(temp_name):
        os.remove(temp_name)

    message_log_old = MessageLog(file_name, old_key)
    message_log_tmp = MessageLog(temp_name, new_key)

    for log_entry in message_log_old:
        message_log_tmp.insert_log_entry(log_entry)

    message_log_old.close_database()
    message_log_tmp.close_database()
Пример #3
0
class TestMessageLog(unittest.TestCase):

    def setUp(self) -> None:
        """Pre-test actions."""
        self.unit_test_dir = cd_unit_test()
        self.file_name     = f'{DIR_USER_DATA}ut_logs'
        self.temp_name     = self.file_name + '_temp'
        self.settings      = Settings()
        self.database_key  = os.urandom(SYMMETRIC_KEY_LENGTH)
        self.message_log   = MessageLog(self.file_name, self.database_key)

    def tearDown(self) -> None:
        """Post-test actions."""
        cleanup(self.unit_test_dir)

    def test_empty_log_database_is_verified(self) -> None:
        self.assertTrue(self.message_log.verify_file(self.file_name))

    def test_database_with_one_entry_is_verified(self) -> None:
        # Setup
        test_entry = b'test_log_entry'
        self.message_log.insert_log_entry(test_entry)

        # Test
        self.assertTrue(self.message_log.verify_file(self.file_name))

    def test_invalid_database_returns_false(self) -> None:
        # Setup
        self.message_log.c.execute("DROP TABLE log_entries")
        self.message_log.conn.commit()

        # Test
        self.assertFalse(self.message_log.verify_file(self.file_name))

    def test_invalid_entry_returns_false(self) -> None:
        # Setup
        params = (os.urandom(LOG_ENTRY_LENGTH),)
        self.message_log.c.execute(f"""INSERT INTO log_entries (log_entry) VALUES (?)""", params)
        self.message_log.conn.commit()

        # Test
        self.assertFalse(self.message_log.verify_file(self.file_name))

    def test_table_creation(self) -> None:
        self.assertIsInstance(self.message_log, MessageLog)
        self.assertTrue(os.path.isfile(self.file_name))

    def test_writing_to_log_database(self) -> None:
        data = os.urandom(LOG_ENTRY_LENGTH)
        self.assertIsNone(self.message_log.insert_log_entry(data))

    def test_iterating_over_log_database(self) -> None:
        data = [os.urandom(LOG_ENTRY_LENGTH), os.urandom(LOG_ENTRY_LENGTH)]
        for entry in data:
            self.assertIsNone(self.message_log.insert_log_entry(entry))

        for index, stored_entry in enumerate(self.message_log):
            self.assertEqual(stored_entry, data[index])

    def test_invalid_temp_database_is_not_loaded(self) -> None:
        log_file = MessageLog(self.file_name, database_key=self.database_key)
        tmp_file = MessageLog(self.temp_name, database_key=self.database_key)

        log_file.insert_log_entry(b'a')
        log_file.insert_log_entry(b'b')
        log_file.insert_log_entry(b'c')
        log_file.insert_log_entry(b'd')
        log_file.insert_log_entry(b'e')

        tmp_file.insert_log_entry(b'a')
        tmp_file.insert_log_entry(b'b')
        tmp_file.c.execute(f"""INSERT INTO log_entries (log_entry) VALUES (?)""", (b'c',))
        tmp_file.conn.commit()
        tmp_file.insert_log_entry(b'd')
        tmp_file.insert_log_entry(b'e')

        self.assertTrue(os.path.isfile(self.temp_name))
        log_file = MessageLog(self.file_name, database_key=self.database_key)
        self.assertEqual(list(log_file), [b'a', b'b', b'c', b'd', b'e'])
        self.assertFalse(os.path.isfile(self.temp_name))

    def test_valid_temp_database_is_loaded(self) -> None:
        log_file = MessageLog(self.file_name, database_key=self.database_key)
        tmp_file = MessageLog(self.temp_name, database_key=self.database_key)

        log_file.insert_log_entry(b'a')
        log_file.insert_log_entry(b'b')
        log_file.insert_log_entry(b'c')
        log_file.insert_log_entry(b'd')
        log_file.insert_log_entry(b'e')

        tmp_file.insert_log_entry(b'f')
        tmp_file.insert_log_entry(b'g')
        tmp_file.insert_log_entry(b'h')
        tmp_file.insert_log_entry(b'i')
        tmp_file.insert_log_entry(b'j')

        self.assertTrue(os.path.isfile(self.temp_name))
        log_file = MessageLog(self.file_name, database_key=self.database_key)
        self.assertEqual(list(log_file), [b'f', b'g', b'h', b'i', b'j'])
        self.assertFalse(os.path.isfile(self.temp_name))

    def test_database_closing(self) -> None:
        self.message_log.close_database()

        # Test insertion would fail at this point
        with self.assertRaises(sqlite3.ProgrammingError):
            self.message_log.c.execute(f"""INSERT INTO log_entries (log_entry) VALUES (?)""",
                                       (os.urandom(LOG_ENTRY_LENGTH),))

        # Test closed database is re-opened during write
        data = os.urandom(LOG_ENTRY_LENGTH)
        self.assertIsNone(self.message_log.insert_log_entry(data))
Пример #4
0
def remove_logs(contact_list: 'ContactList', group_list: 'GroupList',
                settings: 'Settings', master_key: 'MasterKey',
                selector: bytes) -> None:
    """\
    Remove log entries for selector (public key of an account/group ID).

    If the selector is a public key, all messages (both the private
    conversation and any associated group messages) sent to and received
    from the associated contact are removed. If the selector is a group
    ID, only messages for the group matching that group ID are removed.
    """
    ensure_dir(DIR_USER_DATA)
    file_name = f'{DIR_USER_DATA}{settings.software_operation}_logs'
    temp_name = file_name + TEMP_SUFFIX
    packet_list = PacketList(settings, contact_list)
    entries_to_keep = []  # type: List[bytes]
    removed = False
    contact = len(selector) == ONION_SERVICE_PUBLIC_KEY_LENGTH

    check_log_file_exists(file_name)
    message_log = MessageLog(file_name, master_key.master_key)

    for log_entry in message_log:

        onion_pub_key, _, origin, assembly_packet = separate_headers(
            log_entry, [
                ONION_SERVICE_PUBLIC_KEY_LENGTH, TIMESTAMP_LENGTH,
                ORIGIN_HEADER_LENGTH
            ])
        if contact:
            if onion_pub_key == selector:
                removed = True
            else:
                entries_to_keep.append(log_entry)

        else:  # Group
            packet = packet_list.get_packet(onion_pub_key,
                                            origin,
                                            MESSAGE,
                                            log_access=True)
            try:
                packet.add_packet(assembly_packet, log_entry)
            except SoftError:
                continue
            if not packet.is_complete:
                continue

            removed = check_packet_fate(entries_to_keep, packet, removed,
                                        selector)

    message_log.close_database()

    message_log_temp = MessageLog(temp_name, master_key.master_key)

    for log_entry in entries_to_keep:
        message_log_temp.insert_log_entry(log_entry)
    message_log_temp.close_database()

    os.replace(temp_name, file_name)

    try:
        name = contact_list.get_nick_by_pub_key(
            selector) if contact else group_list.get_group_by_id(selector).name
    except StopIteration:
        name = pub_key_to_short_address(selector) if contact else b58encode(
            selector)

    action = "Removed" if removed else "Found no"
    win_type = "contact" if contact else "group"

    raise SoftError(f"{action} log entries for {win_type} '{name}'.")