def test_get_message(self): test_message = {"response": "200", "alert": "test"} test_socket = TestSocket(test_message) self.assertEqual(get_message(test_socket, self.CONFIGS), { 'response': '200', 'alert': 'test' })
def run(self): """Метод содержащий основной цикл работы транспортного потока.""" client_logger.debug('Запущен процесс - приёмник собщений с сервера.') while self.running: # Отдыхаем секунду и снова пробуем захватить сокет. # если не сделать тут задержку, то отправка может достаточно долго # ждать освобождения сокета. time.sleep(1) message = None with socket_lock: try: self.transport.settimeout(0.5) message = get_message(self.transport, CONFIGS) except OSError as err: if err.errno: client_logger.critical( f'Потеряно соединение с сервером.') self.running = False self.connection_lost.emit() # Проблемы с соединением except (ConnectionError, ConnectionAbortedError, ConnectionResetError, json.JSONDecodeError, TypeError): client_logger.debug(f'Потеряно соединение с сервером.') self.running = False self.connection_lost.emit() finally: self.transport.settimeout(5) # Если сообщение получено, то вызываем функцию обработчик: if message: client_logger.debug( f'Принято сообщение с сервера: {message}') self.process_server_ans(message)
def remove_contact(self, contact): """Метод отправляющий на сервер сведения о удалении контакта.""" client_logger.debug(f'Удаление контакта {contact}') req = { CONFIGS.get('ACTION'): CONFIGS.get('REMOVE_CONTACT'), CONFIGS.get('TIME'): time.time(), CONFIGS.get('USER'): self.username, CONFIGS.get('ACCOUNT_NAME'): contact } with socket_lock: send_message(self.transport, req, CONFIGS) self.process_server_ans(get_message(self.transport, CONFIGS))
def send_message(self, to, message): """Метод отправляющий на сервер сообщения для пользователя.""" message_dict = { CONFIGS['ACTION']: CONFIGS['MESSAGE'], CONFIGS['FROM_USER']: self.username, CONFIGS['TO_USER']: to, CONFIGS['TIME']: time.time(), CONFIGS['MESSAGE_TEXT']: message } client_logger.debug(f'Сформирован словарь сообщения: {message_dict}') # Необходимо дождаться освобождения сокета для отправки сообщения with socket_lock: send_message(self.transport, message_dict, CONFIGS) self.process_server_ans(get_message(self.transport, CONFIGS)) client_logger.info(f'Отправлено сообщение для пользователя {to}')
def key_request(self, user): """Метод запрашивающий с сервера публичный ключ пользователя.""" client_logger.debug(f'Запрос публичного ключа для {user}') req = { CONFIGS.get('ACTION'): CONFIGS.get('PUBLIC_KEY_REQUEST'), CONFIGS.get('TIME'): time.time(), CONFIGS.get('ACCOUNT_NAME'): user } with socket_lock: send_message(self.transport, req, CONFIGS) ans = get_message(self.transport, CONFIGS) if CONFIGS.get('RESPONSE') in ans and ans[CONFIGS.get( 'RESPONSE')] == 511: return ans[CONFIGS.get('DATA')] else: client_logger.error(f'Не удалось получить ключ собеседника{user}.')
def user_list_update(self): """Метод обновляющий с сервера список пользователей.""" client_logger.debug( f'Запрос списка известных пользователей {self.username}') req = { CONFIGS.get('ACTION'): CONFIGS.get('USERS_REQUEST'), CONFIGS.get('TIME'): time.time(), CONFIGS.get('ACCOUNT_NAME'): self.username } with socket_lock: send_message(self.transport, req, CONFIGS) ans = get_message(self.transport, CONFIGS) if CONFIGS.get('RESPONSE') in ans and ans[CONFIGS.get( 'RESPONSE')] == 202: self.database.add_users(ans[CONFIGS.get('LIST_INFO')]) else: client_logger.error( 'Не удалось обновить список известных пользователей.')
def run(self): """Метод - основной цикл потока.""" # инициализируем сокет self.init_socket() # основной цикл программы сервера while self.running: # ждём подключения, если таймаут вышел, ловим исключение try: # принимает запрос на установку соединения client, client_address = self.sock.accept() except OSError: pass # timeout вышел else: server_logger.info( f'Установлено соединение с: {str(client_address)}') client.settimeout(5) self.clients.append(client) recv_data_lst = [] send_data_lst = [] err_lst = [] # проверяем на наличие ждущих клиентов try: if self.clients: recv_data_lst, self.listen_sockets, self.error_sockets = \ select.select(self.clients, self.clients, [], 0) except OSError as err: server_logger.error(f'Ошибка работы с сокетами: {err.errno}') # принимаем сообщения и если ошибка, исключаем клиента if recv_data_lst: for client_with_message in recv_data_lst: try: self.process_client_message( get_message(client_with_message, CONFIGS), client_with_message, CONFIGS) except (OSError, json.JSONDecodeError, TypeError) as err: server_logger.debug( f'Getting data from client exception', exc_info=err) self.remove_client(client_with_message)
def contacts_list_update(self): """Метод обновляющий с сервера список контактов.""" self.database.contacts_clear() client_logger.debug( f'Запрос контакт листа для пользователся {self.name}') req = { CONFIGS.get('ACTION'): CONFIGS.get('GET_CONTACTS'), CONFIGS.get('TIME'): time.time(), CONFIGS.get('USER'): self.username } client_logger.debug(f'Сформирован запрос {req}') with socket_lock: send_message(self.transport, req, CONFIGS) ans = get_message(self.transport, CONFIGS) client_logger.debug(f'Получен ответ {ans}') if CONFIGS.get('RESPONSE') in ans and ans[CONFIGS.get( 'RESPONSE')] == 202: for contact in ans[CONFIGS.get('LIST_INFO')]: self.database.add_contact(contact) else: client_logger.error('Не удалось обновить список контактов.')
def authorize_user(self, message, sock): """Метод реализующий авторизцию пользователей.""" # Если имя пользователя уже занято то возвращаем 400 server_logger.debug( f'Start auth process for {message[CONFIGS.get("USER")]}') if message[CONFIGS.get('USER')][CONFIGS.get( 'ACCOUNT_NAME')] in self.names.keys(): response = RESPONSE_400 response[CONFIGS.get('ERROR')] = 'Имя пользователя уже занято.' try: server_logger.debug(f'Username busy, sending {response}') send_message(sock, response, CONFIGS) except OSError: server_logger.debug('OS Error') pass self.clients.remove(sock) sock.close() # Проверяем что пользователь зарегистрирован на сервере. elif not self.database.check_user( message[CONFIGS.get('USER')][CONFIGS.get('ACCOUNT_NAME')]): response = RESPONSE_400 response[CONFIGS.get('ERROR')] = 'Пользователь не зарегистрирован.' try: server_logger.debug(f'Unknown username, sending {response}') send_message(sock, response, CONFIGS) except OSError: pass self.clients.remove(sock) sock.close() else: server_logger.debug('Correct username, starting passwd check.') # Иначе отвечаем 511 и проводим процедуру авторизации # Словарь - заготовка message_auth = RESPONSE_511 # Набор байтов в hex представлении random_str = binascii.hexlify(os.urandom(64)) # В словарь байты нельзя, декодируем (json.dumps -> TypeError) message_auth[CONFIGS.get('DATA')] = random_str.decode('ascii') # Создаём хэш пароля и связки с рандомной строкой, сохраняем # серверную версию ключа hash = hmac.new( self.database.get_hash( message[CONFIGS.get('USER')][CONFIGS.get('ACCOUNT_NAME')]), random_str, 'MD5') digest = hash.digest() server_logger.debug(f'Auth message = {message_auth}') try: # Обмен с клиентом send_message(sock, message_auth, CONFIGS) ans = get_message(sock, CONFIGS) except OSError as err: server_logger.debug('Error in auth, data:', exc_info=err) sock.close() return client_digest = binascii.a2b_base64(ans[CONFIGS.get('DATA')]) # Если ответ клиента корректный, то сохраняем его в список # пользователей. if CONFIGS.get('RESPONSE') in ans and \ ans[CONFIGS.get('RESPONSE')] == 511 and \ hmac.compare_digest(digest, client_digest): self.names[message[CONFIGS.get('USER')][CONFIGS.get( 'ACCOUNT_NAME')]] = sock client_ip, client_port = sock.getpeername() try: send_message(sock, RESPONSE_200, CONFIGS) except OSError: self.remove_client(message[CONFIGS.get('USER')][ CONFIGS.get('ACCOUNT_NAME')]) # добавляем пользователя в список активных и если у него # изменился открытый ключ сохраняем новый self.database.user_login( message[CONFIGS.get('USER')][CONFIGS.get('ACCOUNT_NAME')], client_ip, client_port, message[CONFIGS.get('USER')][CONFIGS.get('PUBLIC_KEY')]) else: response = RESPONSE_400 response[CONFIGS.get('ERROR')] = 'Неверный пароль.' try: send_message(sock, response, CONFIGS) except OSError: pass self.clients.remove(sock) sock.close()
def connection_init(self, port, ip): """Метод отвечающий за устанновку соединения с сервером.""" # Инициализация сокета и сообщение серверу о нашем появлении self.transport = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # Таймаут необходим для освобождения сокета. self.transport.settimeout(5) # Соединяемся, 5 попыток соединения, флаг успеха ставим в True если # удалось connected = False for i in range(5): client_logger.info(f'Попытка подключения №{i + 1}') try: self.transport.connect((ip, port)) except (OSError, ConnectionRefusedError): pass else: connected = True client_logger.debug("Connection established.") break time.sleep(1) # Если соединится не удалось - исключение if not connected: client_logger.critical( 'Не удалось установить соединение с сервером') raise ServerError('Не удалось установить соединение с сервером') client_logger.debug('Starting auth dialog.') # Запускаем процедуру авторизации # Получаем хэш пароля passwd_bytes = self.password.encode('utf-8') salt = self.username.lower().encode('utf-8') passwd_hash = hashlib.pbkdf2_hmac('sha512', passwd_bytes, salt, 10000) passwd_hash_string = binascii.hexlify(passwd_hash) client_logger.debug(f'Passwd hash ready: {passwd_hash_string}') # Получаем публичный ключ и декодируем его из байтов pubkey = self.keys.publickey().export_key().decode('ascii') # Авторизируемся на сервере with socket_lock: presense = { CONFIGS.get('ACTION'): CONFIGS.get('PRESENCE'), CONFIGS.get('TIME'): time.time(), CONFIGS.get('USER'): { CONFIGS.get('ACCOUNT_NAME'): self.username, CONFIGS.get('PUBLIC_KEY'): pubkey } } client_logger.debug(f"Presence message = {presense}") # Отправляем серверу приветственное сообщение. try: send_message(self.transport, presense, CONFIGS) ans = get_message(self.transport, CONFIGS) client_logger.debug(f'Server response = {ans}.') # Если сервер вернул ошибку, бросаем исключение. if CONFIGS.get('RESPONSE') in ans: if ans[CONFIGS.get('RESPONSE')] == 400: raise ServerError(ans[CONFIGS.get('ERROR')]) elif ans[CONFIGS.get('RESPONSE')] == 511: # Если всё нормально, то продолжаем процедуру # авторизации. ans_data = ans[CONFIGS.get('DATA')] hash = hmac.new(passwd_hash_string, ans_data.encode('utf-8'), 'MD5') digest = hash.digest() my_ans = RESPONSE_511 my_ans[CONFIGS.get('DATA')] = binascii.b2a_base64( digest).decode('ascii') send_message(self.transport, my_ans, CONFIGS) self.process_server_ans( get_message(self.transport, CONFIGS)) except (OSError, json.JSONDecodeError) as err: client_logger.debug(f'Connection error.', exc_info=err) raise ServerError('Сбой соединения в процессе авторизации.')