예제 #1
0
    def handle(self, client):
        """ The main thread to each connection. will continuously receive messages from a client and perform an
            operation based on the header value. if header value reads 'bcast', the message is to be sent to
            every connected client. if header value reads 'dm', the message is private between two clients and
            is only sent to the user matching the tuple index[1].

            Message examples:
                data = {'head': 'bcast', 'body': ('ExampleUSer1', 'This is my message to be broadcast!')}
                data = {'head': 'dm', 'body': ('TO_ExampleUSer1', 'FROM_ExampleUser2', 'Hello, this is a private message.')}
        """
        if self.authenticate(client):
            try:
                while True:
                    data = do_decrypt(client.key, client.sock.recv(4096))

                    if data is None:
                        self.disconnect(client)
                        break

                    if data['head'] == 'bcast':
                        for user in self.clients:
                            if user != client:
                                user.sock.sendall(do_encrypt(user.key, data))

                    elif data['head'] == 'dm':
                        for user in self.clients:
                            if user.fullname == data['body'][0]:
                                user.sock.sendall(do_encrypt(user.key, data))

            except ConnectionResetError:
                self.disconnect(client)
예제 #2
0
    def send(self, **data):
        """ Create structured message objects and send to the server.
            Messages are constructed using nested dictionaries and container types to indicate the purpose and destination of a message.
            Using dictionaries and key/value pairs, every message is constructed with a head & body field. The header indicates the type
            of message and can be either a broadcast for all to receive or a direct message with a single destination.
            The body field contains the written message and in the case of Dm's includes the names of the message's recipient and sender.

            Example messages:
                Broadcasted message ==  {'head': 'bcast', 'body': TextObject}
                Direct message      ==  {'head': 'dm', 'body': ('NameOfDestination, NameOfSource, TextObject)}
            
        """
        try:
            if data['head'] == 'bcast':
                data = {'head': 'bcast', 'body': data['message']}

            elif data['head'] == 'dm':
                data = {
                    'head': 'dm',
                    'body':
                    (data['recipient'], data['sender'], data['message'])
                }

            self.sock.sendall(do_encrypt(self.key, data))

        except ConnectionAbortedError:
            return  # server has disconneced or stopped
예제 #3
0
 def login(self, username, password):
     """ Receives the user's credentials from Login page and packages the information
         using the set message standard. Enryption is applied and the ciphertext is sent to the server for authentication.
         The server replies with a boolean indicating success or failiure.
     """
     credentials = {'head': 'login', 'body': (username, password)}
     encrypted_data = do_encrypt(self.key, credentials)
     self.sock.sendall(encrypted_data)
     data = do_decrypt(self.key, self.sock.recv(4096))
     if not data:
         return False
     else:
         return data
예제 #4
0
    def is_online(self):
        """ Broadcasts hidden messages to all connected clients containing information of who is online.
            The method is a loop with a two seconds sleep interval and use the Client.state attribute. When a client
            successfully authenticates, his state is first set to 1. This indicates that the client has no prior data of
            who else is currently connected to the chat room. The method then creates a list containing the fullname of
            all currently connected clients and sends it out to the client. The new client is now up-to-date with the
            rest of the connected clients and can be put in state 2.

            State two is triggered by a new connection setting the is_online_flag to True and creates a list of the newly
            connected clients (which would have state 1) and sends it out to all previous connections, allowing them to
            update their display of online clients.

            State 1: Receives list containing fullname of all current connections.
            State 2: Receives list containing fullname of the new connections.

            Example meta message:
            data = {'head': 'meta', 'body': {'online': ['ExampleName1', ExampleName2', 'ExampleName3']}}
        """
        while True:
            time.sleep(2)
            clients = self.clients.copy()
            # Triggered by a new connection, sends out the fullname of the new client to all
            # previously connected clients.
            if self.is_online_flag:
                for user in clients:
                    if user.state == 2:
                        online = [client.username for client in clients if client.state == 1]
                        data = {'head': 'meta', 'body': {'online': online}}
                        user.sock.sendall(do_encrypt(user.key, data))
                self.is_online_flag.clear()

            # Sends out a list containing fullname of all connected clients, to the new client.
            for user in clients:
                if user.state == 1:
                    online = [client.username for client in clients]
                    data = {'head': 'meta', 'body': {'online': online}}
                    user.sock.sendall(do_encrypt(user.key, data))
                    user.state = 2
예제 #5
0
    def disconnect(self, client):
        """ Remove a client from inventory and close the socket. A client has either executed a socket.shutdown(1)
            and is waiting for a (FIN - ACK) or the client application has been killed. Either way, the socket is closed
            and the client's class object is deleted.

            When a client disconnects, the server sends a special hidden message out to every connection to update the
            client applications 'online users' side panel. This message is handled by the clients
            MainWindow.is_online method.
        """

        client.sock.close()
        send = {'head': 'meta', 'body': {'offline': client.username}}
        for user in self.clients:
            if user != client:
                user.sock.sendall(do_encrypt(user.key, send))
        self.clients.remove(client)
        logging.info(f'Client disconnected: {client.username}@{client.address[0]}')
예제 #6
0
    def authenticate(self, client):
        """ Receive client username/password and compare credentials with backend database.
            The method receives login credentials from the connected client and compares the received credentials
            with the user data stored in the database.

            The method has three outcomes:
            1. The received username does not match any entries in the chatroom.users table.
                result: server sends a False boolean back to client application.

            2. the received username matches with a table entry but the password hash comparison fails.
                result: server sends a False boolean back to client application.

            3. The received username matches with a table entry and the password hash comparison is successful.
                result: servers sends a True boolean and the client's full name back to client application.

        :param client: Client class object established upon connection to server.
        """
        while True:
            try:
                data = do_decrypt(client.key, client.sock.recv(4096))
                # Unpack received login credentials from the connected client.
                username = data['body'][0]
                password = data['body'][1]
            except (ConnectionResetError, TypeError):
                client.sock.close()
                break

            with self.database_sock.cursor() as cursor:
                # A SELECT query is performed to return user_id, username, and hashed password from the database,
                # based upon the username sent by the connected client. If the received username does not match any
                # table entries, the query will return a NoneType object which and is handled by the try/except
                # clause below.
                query = "SELECT USER_NAME, PASSWORD FROM users where USER_NAME=%s"
                values = (username,)
                cursor.execute(query, values)
                
            try:
                # Unpacking query results
                ret_username, ret_password = cursor.fetchone()

                # Using bcrypt module to perform a hash comparison with on the password hash from the database and
                # the submitted password from the client. The client is successfully authenticated if both the
                # password and username matches the database entry.
                if bcrypt.checkpw(password.encode('utf-8'), ret_password.encode('utf-8')) and ret_username == username:
                    # the class object's attributes are updated accordingly and the connected client recieves his
                    # full name and a boolean signaling the GUI application to enter the MainPage.
                    client.fullname = ret_username # should be removed but too lazy atm
                    client.username = username
                    send = {'head': 'login', 'body': (True, ret_username)}
                    client.sock.sendall(do_encrypt(client.key, send))
                    self.clients.append(client)
                    self.is_online_flag.append(username)
                    client.state = 1
                    logging.info(f'Client authenticated: {client.username}@{client.address[0]}')
                    return True

                else:
                    # else statement is executed if the password does not match with the hash stored in the database.
                    logging.warning(f'Authentication failed - wrong username/password: {username}@{client.address[0]}')
                    send = {'head': 'login', 'body': (False,)}
                    client.sock.sendall(do_encrypt(client.key, send))
                    continue

            except TypeError:
                logging.warning(f'Authentication failed - no user found: {username}@{client.address[0]}')
                send = {'head': 'login', 'body': (False,)}
                client.sock.sendall(do_encrypt(client.key, send))
                continue