def process_message(self, message): """ Метод отправки сообщения клиенту. """ if message[CONFIGS.get('TO_USER')] in self.names and self.names[ message[CONFIGS.get('TO_USER')]] in self.listen_sockets: try: send_message(self.names[message[CONFIGS.get('TO_USER')]], message, CONFIGS) server_logger.info( f'Отправлено сообщение пользователю ' f'{message[CONFIGS.get("TO_USER")]} ' f'от пользователя {message[CONFIGS.get("FROM_USER")]}.') except OSError: self.remove_client(message[CONFIGS.get('TO_USER')]) elif message[CONFIGS.get('TO_USER')] in self.names and self.names[ message[CONFIGS.get('TO_USER')]] not in self.listen_sockets: server_logger.error( f'Связь с клиентом {message[CONFIGS.get("TO_USER")]} ' f'была потеряна. ' f'Соединение закрыто, доставка невозможна.') self.remove_client(self.names[message[CONFIGS.get('TO_USER')]]) else: server_logger.error( f'Пользователь {message[CONFIGS.get("TO_USER")]} ' f'не зарегистрирован на сервере, ' f'отправка сообщения невозможна.')
def service_update_lists(self): """Метод реализующий отправки сервисного сообщения 205 клиентам.""" for client in self.names: try: send_message(self.names[client], RESPONSE_205, CONFIGS) except OSError: self.remove_client(self.names[client])
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 test_send_message(self): test_message_from_client = { 'action': 'presence', 'time': 123456789, 'type': 'status', 'user': { 'account_name': 'Samoryad', 'status': 'Привет, сервер!' } } test_socket = TestSocket(test_message_from_client) send_message(test_socket, test_message_from_client, self.CONFIGS) self.assertEqual(test_socket.encoded_message, test_socket.received_message)
def transport_shutdown(self): """Метод уведомляющий сервер о завершении работы клиента.""" self.running = False message = { CONFIGS.get('ACTION'): CONFIGS.get('EXIT'), CONFIGS.get('TIME'): time.time(), CONFIGS['ACCOUNT_NAME']: self.username } with socket_lock: try: send_message(self.transport, message, CONFIGS) except OSError: pass client_logger.debug('Транспорт завершает работу.') time.sleep(0.5)
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 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 process_client_message(self, message, client, CONFIGS): """Метод - отбработчик поступающих сообщений.""" server_logger.debug(f'Обработка сообщения от клиента: {message}') # если это сообщение о присутствии, принимаем и отвечаем if CONFIGS.get('ACTION') in message and \ message[CONFIGS.get('ACTION')] == CONFIGS.get('PRESENCE') and \ CONFIGS.get('TIME') in message and \ CONFIGS.get('USER') in message: # Если сообщение о присутствии то вызываем функцию авторизации. self.authorize_user(message, client) # Если это сообщение, то отправляем его получателю. elif CONFIGS.get('ACTION') in message and \ message[CONFIGS.get('ACTION')] == CONFIGS.get('MESSAGE') and \ CONFIGS.get('TO_USER') in message and \ CONFIGS.get('TIME') in message and \ CONFIGS.get('FROM_USER') in message and \ CONFIGS.get('MESSAGE_TEXT') in message and \ self.names[message[CONFIGS.get('FROM_USER')]] == client: if message[CONFIGS.get('TO_USER')] in self.names: self.database.process_message( message[CONFIGS.get('FROM_USER')], message[CONFIGS.get('TO_USER')]) self.process_message(message) try: send_message(client, RESPONSE_200, CONFIGS) except OSError: self.remove_client(client) else: response = RESPONSE_400 response[CONFIGS.get( 'ERROR')] = 'Пользователь не зарегистрирован на сервере.' try: send_message(client, response, CONFIGS) except OSError: pass return # если клиент выходит elif CONFIGS.get('ACTION') in message and \ message[CONFIGS.get('ACTION')] == CONFIGS.get('EXIT') and \ CONFIGS.get('ACCOUNT_NAME') in message and \ self.names[message[CONFIGS.get('ACCOUNT_NAME')]] == client: self.remove_client(client) # если это запрос контакт-листа elif CONFIGS.get('ACTION') in message and \ message[CONFIGS.get('ACTION')] == CONFIGS.get('GET_CONTACTS') \ and CONFIGS.get('USER') in message and \ self.names[message[CONFIGS.get('USER')]] == client: response = RESPONSE_202 response[CONFIGS.get('LIST_INFO')] = self.database.get_contacts( message[CONFIGS.get('USER')]) try: send_message(client, response, CONFIGS) except OSError: self.remove_client(client) # если это добавление контакта elif CONFIGS.get('ACTION') in message and \ message[CONFIGS.get('ACTION')] == CONFIGS.get('ADD_CONTACT') \ and CONFIGS.get("ACCOUNT_NAME") in message and \ CONFIGS.get('USER') in message and \ self.names[message[CONFIGS.get('USER')]] == client: self.database.add_contact(message[CONFIGS.get('USER')], message[CONFIGS.get("ACCOUNT_NAME")]) try: send_message(client, RESPONSE_200, CONFIGS) except OSError: self.remove_client(client) # если это удаление контакта elif CONFIGS.get('ACTION') in message and \ message[CONFIGS.get('ACTION')] == \ CONFIGS.get('REMOVE_CONTACT') and \ CONFIGS.get('ACCOUNT_NAME') in message and \ CONFIGS.get('USER') in message and \ self.names[message[CONFIGS.get('USER')]] == client: self.database.remove_contact(message[CONFIGS.get('USER')], message[CONFIGS.get('ACCOUNT_NAME')]) try: send_message(client, RESPONSE_200, CONFIGS) except OSError: self.remove_client(client) # если это запрос известных пользователей elif CONFIGS.get('ACTION') in message and \ message[CONFIGS.get('ACTION')] == CONFIGS.get('USERS_REQUEST')\ and CONFIGS.get('ACCOUNT_NAME') in message and \ self.names[message[CONFIGS.get('ACCOUNT_NAME')]] == client: response = RESPONSE_202 response[CONFIGS.get('LIST_INFO')] = \ [user[0] for user in self.database.users_list()] try: send_message(client, response, CONFIGS) except OSError: self.remove_client(client) # Если это запрос публичного ключа пользователя elif CONFIGS.get('ACTION') in message and \ message[CONFIGS.get('ACTION')] == \ CONFIGS.get('PUBLIC_KEY_REQUEST') and \ CONFIGS.get('ACCOUNT_NAME') in message: response = RESPONSE_511 response[CONFIGS.get('DATA')] = self.database.get_pubkey( message[CONFIGS.get('ACCOUNT_NAME')]) # может быть, что ключа ещё нет (пользователь никогда не логинился, # тогда шлём 400) if response[CONFIGS.get('DATA')]: try: send_message(client, response, CONFIGS) except OSError: self.remove_client(client) else: response = RESPONSE_400 response[CONFIGS.get('ERROR')] = \ 'Нет публичного ключа для данного пользователя' try: send_message(client, response, CONFIGS) except OSError: self.remove_client(client) # иначе отдаём Bad request else: response = RESPONSE_400 response[CONFIGS.get('ERROR')] = 'Запрос некорректен.' try: send_message(client, response, CONFIGS) except OSError: self.remove_client(client)
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('Сбой соединения в процессе авторизации.')