def group_create(group_name: str, purp_members: List[bytes], contact_list: 'ContactList', group_list: 'GroupList', settings: 'Settings', queues: 'QueueDict', _: 'MasterKey', group_id: Optional[bytes] = None ) -> None: """Create a new group. Validate the group name and determine what members can be added. """ error_msg = validate_group_name(group_name, contact_list, group_list) if error_msg: raise FunctionReturn(error_msg, head_clear=True) public_keys = set(contact_list.get_list_of_pub_keys()) purp_pub_keys = set(purp_members) accepted = list(purp_pub_keys & public_keys) rejected = list(purp_pub_keys - public_keys) if len(accepted) > settings.max_number_of_group_members: raise FunctionReturn(f"Error: TFC settings only allow {settings.max_number_of_group_members} " f"members per group.", head_clear=True) if len(group_list) == settings.max_number_of_groups: raise FunctionReturn(f"Error: TFC settings only allow {settings.max_number_of_groups} groups.", head_clear=True) header = GROUP_MSG_INVITE_HEADER if group_id is None else GROUP_MSG_JOIN_HEADER if group_id is None: while True: group_id = os.urandom(GROUP_ID_LENGTH) if group_id not in group_list.get_list_of_group_ids(): break group_list.add_group(group_name, group_id, settings.log_messages_by_default, settings.show_notifications_by_default, members=[contact_list.get_contact_by_pub_key(k) for k in accepted]) command = GROUP_CREATE + group_id + group_name.encode() + US_BYTE + b''.join(accepted) queue_command(command, settings, queues) 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 the list of group members to participants?", abort=False): create_packet = header + group_id + b''.join(accepted) queue_to_nc(create_packet, queues[RELAY_PACKET_QUEUE]) else: m_print(f"Created an empty group '{group_name}'.", bold=True, head=1)
def test_group_management_print(self): group_management_print(NEW_GROUP, self.lines, self.contact_list, self.group_name) self.assert_prints("""\ ┌──────────────────────────────────────────────────────────────┐ │ Created new group 'test_group' with following members: │ │ * Alice │ │ * zwp3dykiztmeils2u5eqjtdtx5x3kti5ktjthpkznku3ws5u5fq2bnad │ └──────────────────────────────────────────────────────────────┘ """, group_management_print, NEW_GROUP, self.lines, self.contact_list, self.group_name) self.assert_prints("""\ ┌──────────────────────────────────────────────────────────────┐ │ Added following accounts to group 'test_group': │ │ * Alice │ │ * zwp3dykiztmeils2u5eqjtdtx5x3kti5ktjthpkznku3ws5u5fq2bnad │ └──────────────────────────────────────────────────────────────┘ """, group_management_print, ADDED_MEMBERS, self.lines, self.contact_list, self.group_name) self.assert_prints("""\ ┌──────────────────────────────────────────────────────────────┐ │ Following accounts were already in group 'test_group': │ │ * Alice │ │ * zwp3dykiztmeils2u5eqjtdtx5x3kti5ktjthpkznku3ws5u5fq2bnad │ └──────────────────────────────────────────────────────────────┘ """, group_management_print, ALREADY_MEMBER, self.lines, self.contact_list, self.group_name) self.assert_prints("""\ ┌──────────────────────────────────────────────────────────────┐ │ Removed following members from group 'test_group': │ │ * Alice │ │ * zwp3dykiztmeils2u5eqjtdtx5x3kti5ktjthpkznku3ws5u5fq2bnad │ └──────────────────────────────────────────────────────────────┘ """, group_management_print, REMOVED_MEMBERS, self.lines, self.contact_list, self.group_name) self.assert_prints("""\ ┌──────────────────────────────────────────────────────────────┐ │ Following accounts were not in group 'test_group': │ │ * Alice │ │ * zwp3dykiztmeils2u5eqjtdtx5x3kti5ktjthpkznku3ws5u5fq2bnad │ └──────────────────────────────────────────────────────────────┘ """, group_management_print, NOT_IN_GROUP, self.lines, self.contact_list, self.group_name) self.assert_prints("""\ ┌──────────────────────────────────────────────────────────────┐ │ Following unknown accounts were ignored: │ │ * Alice │ │ * zwp3dykiztmeils2u5eqjtdtx5x3kti5ktjthpkznku3ws5u5fq2bnad │ └──────────────────────────────────────────────────────────────┘ """, group_management_print, UNKNOWN_ACCOUNTS, self.lines, self.contact_list, self.group_name)
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)
def group_create(cmd_data: bytes, ts: 'datetime', window_list: 'WindowList', contact_list: 'ContactList', group_list: 'GroupList', settings: 'Settings') -> None: """Create a new group.""" group_id, variable_len_data = separate_header(cmd_data, GROUP_ID_LENGTH) group_name_bytes, ser_members = variable_len_data.split(US_BYTE, 1) group_name = group_name_bytes.decode() purp_pub_keys = set( split_byte_string(ser_members, ONION_SERVICE_PUBLIC_KEY_LENGTH)) pub_keys = set(contact_list.get_list_of_pub_keys()) accepted = list(purp_pub_keys & pub_keys) rejected = list(purp_pub_keys - pub_keys) if len(accepted) > settings.max_number_of_group_members: raise SoftError( 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 SoftError( f"Error: TFC settings only allow {settings.max_number_of_groups} groups." ) accepted_contacts = [ contact_list.get_contact_by_pub_key(k) for k in accepted ] group_list.add_group(group_name, group_id, settings.log_messages_by_default, settings.show_notifications_by_default, accepted_contacts) group = group_list.get_group(group_name) window = window_list.get_window(group.group_id) window.window_contacts = accepted_contacts window.message_log = [] window.unread_messages = 0 window.create_handle_dict() group_management_print(NEW_GROUP, accepted, contact_list, group_name) group_management_print(UNKNOWN_ACCOUNTS, rejected, contact_list, group_name) cmd_win = window_list.get_window(WIN_UID_COMMAND) cmd_win.add_new(ts, f"Created new group {group_name}.")
def group_rm_member(cmd_data: bytes, ts: 'datetime', window_list: 'WindowList', contact_list: 'ContactList', group_list: 'GroupList') -> None: """Remove member(s) from group.""" fields = [f.decode() for f in cmd_data.split(US_BYTE)] group_name = fields[0] purp_accounts = set(fields[1:]) 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) not_in_group = list(ok_accounts_set - before_removal) rejected = list(purp_accounts - accounts) removable = list(removable_set) group = group_list.get_group(group_name) group.remove_members(removable) window = window_list.get_window(group_name) window.remove_contacts(removable) 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) local_win = window_list.get_window(LOCAL_ID) local_win.add_new(ts, f"Removed members from group {group_name}.")
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)
def group_add_member(group_name: str, purp_members: List['bytes'], contact_list: 'ContactList', group_list: 'GroupList', settings: 'Settings', queues: 'QueueDict', master_key: 'MasterKey', _: Optional[bytes] = None) -> None: """Add new member(s) to a specified group.""" if group_name not in group_list.get_list_of_group_names(): if not yes(f"Group {group_name} was not found. Create new group?", abort=False, head=1): raise SoftError("Group creation aborted.", head=0, delay=1, tail_clear=True) group_create(group_name, purp_members, contact_list, group_list, settings, queues, master_key) return None purp_pub_keys = set(purp_members) pub_keys = set(contact_list.get_list_of_pub_keys()) before_adding = set( group_list.get_group(group_name).get_list_of_member_pub_keys()) ok_pub_keys_set = set(pub_keys & purp_pub_keys) new_in_group_set = set(ok_pub_keys_set - before_adding) end_assembly = list(before_adding | new_in_group_set) rejected = list(purp_pub_keys - pub_keys) already_in_g = list(before_adding & purp_pub_keys) new_in_group = list(new_in_group_set) ok_pub_keys = list(ok_pub_keys_set) if len(end_assembly) > settings.max_number_of_group_members: raise SoftError( f"Error: TFC settings only allow {settings.max_number_of_group_members} members per group.", head_clear=True) group = group_list.get_group(group_name) group.add_members( [contact_list.get_contact_by_pub_key(k) for k in new_in_group]) command = GROUP_ADD + group.group_id + b''.join(ok_pub_keys) queue_command(command, settings, queues) 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 the list of new members to involved?", abort=False): add_packet = (GROUP_MSG_MEMBER_ADD_HEADER + group.group_id + int_to_bytes(len(before_adding)) + b''.join(before_adding) + b''.join(new_in_group)) queue_to_nc(add_packet, queues[RELAY_PACKET_QUEUE])
def group_create(cmd_data: bytes, ts: 'datetime', window_list: 'WindowList', contact_list: 'ContactList', group_list: 'GroupList', settings: 'Settings') -> None: """Create a new group.""" fields = [f.decode() for f in cmd_data.split(US_BYTE)] group_name = fields[0] purp_accounts = set(fields[1:]) accounts = set(contact_list.get_list_of_accounts()) 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." ) accepted_contacts = [contact_list.get_contact(c) for c in accepted] group_list.add_group(group_name, settings.log_messages_by_default, settings.show_notifications_by_default, accepted_contacts) window = window_list.get_window(group_name) window.window_contacts = accepted_contacts window.message_log = [] window.unread_messages = 0 window.create_handle_dict() group_management_print(NEW_GROUP, accepted, contact_list, group_name) group_management_print(UNKNOWN_ACCOUNTS, rejected, contact_list, group_name) local_win = window_list.get_window(LOCAL_ID) local_win.add_new(ts, f"Created new group {group_name}.")
def group_rm_member(group_name: str, purp_members: List[bytes], contact_list: 'ContactList', group_list: 'GroupList', settings: 'Settings', queues: 'QueueDict', master_key: 'MasterKey', _: Optional[bytes] = None) -> None: """Remove member(s) from the specified group or remove the group itself.""" if not purp_members: group_rm_group(group_name, contact_list, 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.", head_clear=True) purp_pub_keys = set(purp_members) pub_keys = set(contact_list.get_list_of_pub_keys()) before_removal = set( group_list.get_group(group_name).get_list_of_member_pub_keys()) ok_pub_keys_set = set(purp_pub_keys & pub_keys) removable_set = set(before_removal & ok_pub_keys_set) remaining = list(before_removal - removable_set) not_in_group = list(ok_pub_keys_set - before_removal) rejected = list(purp_pub_keys - pub_keys) removable = list(removable_set) ok_pub_keys = list(ok_pub_keys_set) group = group_list.get_group(group_name) group.remove_members(removable) command = GROUP_REMOVE + group.group_id + b''.join(ok_pub_keys) queue_command(command, settings, queues) 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 remaining and yes( "Publish the list of removed members to remaining members?", abort=False): rem_packet = (GROUP_MSG_MEMBER_REM_HEADER + group.group_id + int_to_bytes(len(remaining)) + b''.join(remaining) + b''.join(removable)) queue_to_nc(rem_packet, queues[RELAY_PACKET_QUEUE])
def group_add(cmd_data: bytes, ts: 'datetime', window_list: 'WindowList', contact_list: 'ContactList', group_list: 'GroupList', settings: 'Settings') -> None: """Add member(s) to group.""" group_id, ser_members = separate_header(cmd_data, GROUP_ID_LENGTH) purp_pub_keys = set( split_byte_string(ser_members, ONION_SERVICE_PUBLIC_KEY_LENGTH)) try: group_name = group_list.get_group_by_id(group_id).name except StopIteration: raise SoftError( f"Error: No group with ID '{b58encode(group_id)}' found.") pub_keys = set(contact_list.get_list_of_pub_keys()) before_adding = set( group_list.get_group(group_name).get_list_of_member_pub_keys()) ok_accounts = set(pub_keys & purp_pub_keys) new_in_group_set = set(ok_accounts - before_adding) end_assembly = list(before_adding | new_in_group_set) already_in_g = list(purp_pub_keys & before_adding) rejected = list(purp_pub_keys - pub_keys) new_in_group = list(new_in_group_set) if len(end_assembly) > settings.max_number_of_group_members: raise SoftError( 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_by_pub_key(k) for k in new_in_group]) window = window_list.get_window(group.group_id) window.add_contacts(new_in_group) window.create_handle_dict() 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) cmd_win = window_list.get_window(WIN_UID_COMMAND) cmd_win.add_new(ts, f"Added members to group {group_name}.")
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)
def group_add_member(cmd_data: bytes, ts: 'datetime', window_list: 'WindowList', contact_list: 'ContactList', group_list: 'GroupList', settings: 'Settings') -> None: """Add member(s) to group.""" fields = [f.decode() for f in cmd_data.split(US_BYTE)] group_name = fields[0] purp_accounts = set(fields[1:]) 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(accounts & purp_accounts) new_in_group_set = set(ok_accounts - 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) 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]) window = window_list.get_window(group_name) window.add_contacts(new_in_group) window.create_handle_dict() 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) local_win = window_list.get_window(LOCAL_ID) local_win.add_new(ts, f"Added members to group {group_name}.")
def group_remove(cmd_data: bytes, ts: 'datetime', window_list: 'WindowList', contact_list: 'ContactList', group_list: 'GroupList') -> None: """Remove member(s) from the group.""" group_id, ser_members = separate_header(cmd_data, GROUP_ID_LENGTH) purp_pub_keys = set( split_byte_string(ser_members, ONION_SERVICE_PUBLIC_KEY_LENGTH)) try: group_name = group_list.get_group_by_id(group_id).name except StopIteration: raise SoftError( f"Error: No group with ID '{b58encode(group_id)}' found.") pub_keys = set(contact_list.get_list_of_pub_keys()) before_removal = set( group_list.get_group(group_name).get_list_of_member_pub_keys()) ok_accounts_set = set(purp_pub_keys & pub_keys) removable_set = set(before_removal & ok_accounts_set) not_in_group = list(ok_accounts_set - before_removal) rejected = list(purp_pub_keys - pub_keys) removable = list(removable_set) group = group_list.get_group(group_name) group.remove_members(removable) window = window_list.get_window(group.group_id) window.remove_contacts(removable) 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) cmd_win = window_list.get_window(WIN_UID_COMMAND) cmd_win.add_new(ts, f"Removed members from group {group_name}.")