Esempio n. 1
0
def group_add_member(group_name:   str,
                     purp_members: List['str'],
                     group_list:   'GroupList',
                     contact_list: 'ContactList',
                     settings:     'Settings',
                     queues:       Dict[bytes, 'Queue'],
                     master_key:   'MasterKey') -> None:
    """Add new member(s) to group."""
    if group_name not in group_list.get_list_of_group_names():
        if yes(f"Group {group_name} was not found. Create new group?", head=1):
            group_create(group_name, purp_members, group_list, contact_list, settings, queues, master_key)
            return None
        else:
            raise FunctionReturn("Group creation aborted.")

    purp_accounts    = set(purp_members)
    accounts         = set(contact_list.get_list_of_accounts())
    before_adding    = set(group_list.get_group(group_name).get_list_of_member_accounts())
    ok_accounts_set  = set(accounts        & purp_accounts)
    new_in_group_set = set(ok_accounts_set - before_adding)

    end_assembly = list(before_adding | new_in_group_set)
    rejected     = list(purp_accounts - accounts)
    already_in_g = list(before_adding & purp_accounts)
    new_in_group = list(new_in_group_set)
    ok_accounts  = list(ok_accounts_set)

    if len(end_assembly) > settings.max_number_of_group_members:
        raise FunctionReturn(f"Error: TFC settings only allow {settings.max_number_of_group_members} members per group.")

    group = group_list.get_group(group_name)
    group.add_members([contact_list.get_contact(a) for a in new_in_group])

    fields  = [f.encode() for f in ([group_name] + ok_accounts)]
    command = GROUP_ADD_HEADER + US_BYTE.join(fields)
    queue_command(command, settings, queues[COMMAND_PACKET_QUEUE])

    group_management_print(ADDED_MEMBERS,    new_in_group, contact_list, group_name)
    group_management_print(ALREADY_MEMBER,   already_in_g, contact_list, group_name)
    group_management_print(UNKNOWN_ACCOUNTS, rejected,     contact_list, group_name)

    if new_in_group:
        if yes("Publish new list of members to involved?"):
            for member in before_adding:
                queue_message(user_input=UserInput(US_STR.join([group_name] + new_in_group), MESSAGE),
                              window    =MockWindow(member, [contact_list.get_contact(member)]),
                              settings  =settings,
                              m_queue   =queues[MESSAGE_PACKET_QUEUE],
                              header    =GROUP_MSG_MEMBER_ADD_HEADER,
                              log_as_ph =True)

            for member in new_in_group:
                m_list = [m for m in end_assembly if m != member]
                queue_message(user_input=UserInput(US_STR.join([group_name] + m_list), MESSAGE),
                              window    =MockWindow(member, [contact_list.get_contact(member)]),
                              settings  =settings,
                              m_queue   =queues[MESSAGE_PACKET_QUEUE],
                              header    =GROUP_MSG_INVITEJOIN_HEADER,
                              log_as_ph =True)
Esempio n. 2
0
def group_rm_group(group_name: str,
                   group_list: 'GroupList',
                   settings:   'Settings',
                   queues:     Dict[bytes, 'Queue'],
                   master_key: 'MasterKey'):
    """Remove group with it's members."""
    if not yes(f"Remove group '{group_name}'?", head=1):
        raise FunctionReturn("Group removal aborted.")

    rm_logs = yes("Also remove logs for the group?", head=1)

    command = GROUP_DELETE_HEADER + group_name.encode()
    queue_command(command, settings, queues[COMMAND_PACKET_QUEUE])

    if rm_logs:
        command = LOG_REMOVE_HEADER + group_name.encode()
        queue_command(command, settings, queues[COMMAND_PACKET_QUEUE])
        with ignored(FunctionReturn):
            remove_logs(group_name, settings, master_key)

    if group_name not in group_list.get_list_of_group_names():
        raise FunctionReturn(f"TxM has no group '{group_name}' to remove.")

    group = group_list.get_group(group_name)
    if group.has_members() and yes("Notify members about leaving the group?"):
        for member in group:
            queue_message(user_input=UserInput(group_name, MESSAGE),
                          window    =MockWindow(member.rx_account, [member]),
                          settings  =settings,
                          m_queue   =queues[MESSAGE_PACKET_QUEUE],
                          header    =GROUP_MSG_EXIT_GROUP_HEADER,
                          log_as_ph =True)

    group_list.remove_group(group_name)
    raise FunctionReturn(f"Removed group '{group_name}'")
Esempio n. 3
0
    def test_user_input(self):
        # Setup
        user_input = UserInput('test_plaintext', FILE)

        # Test
        self.assertEqual(user_input.plaintext, 'test_plaintext')
        self.assertEqual(user_input.type, FILE)
Esempio n. 4
0
    def test_class(self):
        # Setup
        o_input = builtins.input
        input_list = [
            '/', '', 'testmessage', '/file', '/nick Alice', 'testmessage',
            '/nick Alice', '  '
        ]
        gen = iter(input_list)

        def mock_input(_):
            return str(next(gen))

        builtins.input = mock_input

        window = Window(name='Alice',
                        type='contact',
                        window_contacts=[create_contact('Alice')])
        settings = Settings()

        # Test
        user_input = UserInput(window, settings)
        self.assertEqual(user_input.plaintext, 'testmessage')
        self.assertEqual(user_input.type, 'message')

        user_input = UserInput(window, settings)
        self.assertEqual(user_input.plaintext, '/file')
        self.assertEqual(user_input.type, 'file')

        user_input = UserInput(window, settings)
        self.assertEqual(user_input.plaintext, 'nick Alice')
        self.assertEqual(user_input.type, 'command')

        window = Window(name='Testgroup', type='group', window_contacts=[])

        user_input = UserInput(window, settings)
        self.assertEqual(user_input.plaintext, 'nick Alice')
        self.assertEqual(user_input.type, 'command')

        user_input = UserInput(window, settings)
        self.assertEqual(user_input.plaintext, 'clear')
        self.assertEqual(user_input.type, 'command')

        # Teardown
        builtins.input = o_input
Esempio n. 5
0
def tx_loop(
        settings: 'Settings',
        queues: Dict[bytes, 'Queue'],
        gateway: 'Gateway',
        contact_list: 'ContactList',
        group_list: 'GroupList',
        master_key: 'MasterKey',
        file_no: int  # stdin input file descriptor
) -> None:
    """Get input from user and process it accordingly.

    Tx side of TFC runs two processes -- input and output loop -- separate from
    one another. This approach allows queueing assembly packets and their output
    based on priority of different packets. tx_loop handles TxM-side functions
    excluding message encryption, output and hash ratchet key/counter updates in
    key_list database and log file writes.
    """
    sys.stdin = os.fdopen(file_no)
    window = Window(contact_list, group_list)

    while True:
        try:
            readline.set_completer(
                get_tab_completer(contact_list, group_list, settings))
            readline.parse_and_bind('tab: complete')

            window.update_group_win_members(group_list)

            while not contact_list.has_local_contact():
                new_local_key(contact_list, settings, queues, gateway)

            while not contact_list.has_contacts():
                add_new_contact(contact_list, group_list, settings, queues,
                                gateway)

            while not window.is_selected():
                window.select_tx_window(settings, queues)

            user_input = UserInput(window, settings)

            if user_input.type == 'message':
                queue_message(user_input, window, settings,
                              queues[MESSAGE_PACKET_QUEUE])

            elif user_input.type == 'file':
                queue_file(window, settings, queues[FILE_PACKET_QUEUE],
                           gateway)

            elif user_input.type == 'command':
                process_command(user_input, window, settings, queues,
                                contact_list, group_list, gateway, master_key)

        except (EOFError, FunctionReturn, KeyboardInterrupt):
            pass
Esempio n. 6
0
def group_create(group_name:   str,
                 purp_members: List[str],
                 group_list:   'GroupList',
                 contact_list: 'ContactList',
                 settings:     'Settings',
                 queues:       Dict[bytes, 'Queue'],
                 _:            'MasterKey') -> None:
    """Create a new group.
    
    Validate group name and determine what members that can be added.
    """
    validate_group_name(group_name, contact_list, group_list)

    accounts      = set(contact_list.get_list_of_accounts())
    purp_accounts = set(purp_members)
    accepted      = list(accounts      & purp_accounts)
    rejected      = list(purp_accounts - accounts)

    if len(accepted) > settings.max_number_of_group_members:
        raise FunctionReturn(f"Error: TFC settings only allow {settings.max_number_of_group_members} members per group.")

    if len(group_list) == settings.max_number_of_groups:
        raise FunctionReturn(f"Error: TFC settings only allow {settings.max_number_of_groups} groups.")

    group_list.add_group(group_name,
                         settings.log_messages_by_default,
                         settings.show_notifications_by_default,
                         members=[contact_list.get_contact(c) for c in accepted])

    fields  = [f.encode() for f in ([group_name] + accepted)]
    command = GROUP_CREATE_HEADER + US_BYTE.join(fields)
    queue_command(command, settings, queues[COMMAND_PACKET_QUEUE])

    group_management_print(NEW_GROUP,        accepted, contact_list, group_name)
    group_management_print(UNKNOWN_ACCOUNTS, rejected, contact_list, group_name)

    if accepted:
        if yes("Publish list of group members to participants?"):
            for member in accepted:
                m_list = [m for m in accepted if m != member]
                queue_message(user_input=UserInput(US_STR.join([group_name] + m_list), MESSAGE),
                              window    =MockWindow(member, [contact_list.get_contact(member)]),
                              settings  =settings,
                              m_queue   =queues[MESSAGE_PACKET_QUEUE],
                              header    =GROUP_MSG_INVITEJOIN_HEADER,
                              log_as_ph =True)
    else:
        box_print(f"Created an empty group '{group_name}'", head=1)
Esempio n. 7
0
def group_rm_member(group_name:   str,
                    purp_members: List[str],
                    group_list:   'GroupList',
                    contact_list: 'ContactList',
                    settings:     'Settings',
                    queues:       Dict[bytes, 'Queue'],
                    master_key:   'MasterKey') -> None:
    """Remove member(s) from group or group itself."""
    if not purp_members:
        group_rm_group(group_name, group_list, settings, queues, master_key)

    if group_name not in group_list.get_list_of_group_names():
        raise FunctionReturn(f"Group '{group_name}' does not exist.")

    purp_accounts   = set(purp_members)
    accounts        = set(contact_list.get_list_of_accounts())
    before_removal  = set(group_list.get_group(group_name).get_list_of_member_accounts())
    ok_accounts_set = set(purp_accounts  & accounts)
    removable_set   = set(before_removal & ok_accounts_set)

    end_assembly = list(before_removal  - removable_set)
    not_in_group = list(ok_accounts_set - before_removal)
    rejected     = list(purp_accounts   - accounts)
    removable    = list(removable_set)
    ok_accounts  = list(ok_accounts_set)

    group = group_list.get_group(group_name)
    group.remove_members(removable)

    fields  = [f.encode() for f in ([group_name] + ok_accounts)]
    command = GROUP_REMOVE_M_HEADER + US_BYTE.join(fields)
    queue_command(command, settings, queues[COMMAND_PACKET_QUEUE])

    group_management_print(REMOVED_MEMBERS,  removable,    contact_list, group_name)
    group_management_print(NOT_IN_GROUP,     not_in_group, contact_list, group_name)
    group_management_print(UNKNOWN_ACCOUNTS, rejected,     contact_list, group_name)

    if removable and end_assembly and yes("Publish list of removed members to remaining members?"):
        for member in end_assembly:
            queue_message(user_input=UserInput(US_STR.join([group_name] + removable), MESSAGE),
                          window    =MockWindow(member, [contact_list.get_contact(member)]),
                          settings  =settings,
                          m_queue   =queues[MESSAGE_PACKET_QUEUE],
                          header    =GROUP_MSG_MEMBER_REM_HEADER,
                          log_as_ph =True)
Esempio n. 8
0
def whisper(user_input: 'UserInput', window: 'TxWindow', settings: 'Settings',
            m_queue: 'Queue') -> None:
    """Send a message to contact that overrides enabled logging setting.

    The functionality of this feature is impossible to enforce, but if
    the recipient can be trusted, it can be used to send keys for to be
    imported files as well as off-the-record messages, without worrying
    they are stored into log files, ruining forward secrecy for imported
    (and later deleted) files.
    """
    message = user_input.plaintext[len('whisper '):]

    queue_message(user_input=UserInput(message, MESSAGE),
                  window=window,
                  settings=settings,
                  m_queue=m_queue,
                  header=WHISPER_MESSAGE_HEADER,
                  log_as_ph=True)