mirror of
https://github.com/GoodStartLabs/AI_Diplomacy.git
synced 2026-04-29 17:35:18 +00:00
INIT
This commit is contained in:
parent
e8530a146d
commit
93c073e2df
295 changed files with 86794 additions and 0 deletions
249
diplomacy/server/users.py
Normal file
249
diplomacy/server/users.py
Normal file
|
|
@ -0,0 +1,249 @@
|
|||
# ==============================================================================
|
||||
# Copyright (C) 2019 - Philip Paquette, Steven Bocco
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it under
|
||||
# the terms of the GNU Affero General Public License as published by the Free
|
||||
# Software Foundation, either version 3 of the License, or (at your option) any
|
||||
# later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
# details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License along
|
||||
# with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
# ==============================================================================
|
||||
""" Helper class to manage user accounts and connections on server side.
|
||||
|
||||
A user is associated to 0 or more connected tokens,
|
||||
and each connected token is associated to at most 1 connection handler.
|
||||
|
||||
When a connection handler is closed or invalidated,
|
||||
related tokens are kept and may be further associated to new connection handlers.
|
||||
|
||||
Tokens are effectively deleted when they expire after TOKEN_LIFETIME_SECONDS seconds since last token usage.
|
||||
"""
|
||||
import logging
|
||||
|
||||
from diplomacy.server.user import User
|
||||
from diplomacy.utils import common, parsing, strings
|
||||
from diplomacy.utils.common import generate_token
|
||||
from diplomacy.utils.jsonable import Jsonable
|
||||
|
||||
LOGGER = logging.getLogger(__name__)
|
||||
|
||||
# Token lifetime in seconds: default 24hours.
|
||||
TOKEN_LIFETIME_SECONDS = 24 * 60 * 60
|
||||
|
||||
class Users(Jsonable):
|
||||
""" Users class.
|
||||
|
||||
Properties:
|
||||
|
||||
- **users**: dictionary mapping usernames to User object.s
|
||||
- **administrators**: set of administrator usernames.
|
||||
- **token_timestamp**: dictionary mapping each token to its creation/last confirmation timestamp.
|
||||
- **token_to_username**: dictionary mapping each token to its username.
|
||||
- **username_to_tokens**: dictionary mapping each username to a set of its tokens.
|
||||
- **token_to_connection_handler**: (memory only) dictionary mapping each token to a connection handler
|
||||
- **connection_handler_to_tokens**: (memory only) dictionary mapping a connection handler to a set of its tokens
|
||||
"""
|
||||
__slots__ = ['users', 'administrators', 'token_timestamp', 'token_to_username', 'username_to_tokens',
|
||||
'token_to_connection_handler', 'connection_handler_to_tokens']
|
||||
model = {
|
||||
strings.USERS: parsing.DefaultValueType(parsing.DictType(str, parsing.JsonableClassType(User)), {}),
|
||||
# {username => User}
|
||||
strings.ADMINISTRATORS: parsing.DefaultValueType(parsing.SequenceType(str, sequence_builder=set), ()),
|
||||
# {usernames}
|
||||
strings.TOKEN_TIMESTAMP: parsing.DefaultValueType(parsing.DictType(str, int), {}),
|
||||
strings.TOKEN_TO_USERNAME: parsing.DefaultValueType(parsing.DictType(str, str), {}),
|
||||
strings.USERNAME_TO_TOKENS: parsing.DefaultValueType(parsing.DictType(str, parsing.SequenceType(str, set)), {}),
|
||||
}
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self.users = {}
|
||||
self.administrators = set()
|
||||
self.token_timestamp = {}
|
||||
self.token_to_username = {}
|
||||
self.username_to_tokens = {}
|
||||
self.token_to_connection_handler = {}
|
||||
self.connection_handler_to_tokens = {}
|
||||
super(Users, self).__init__(**kwargs)
|
||||
|
||||
def has_username(self, username):
|
||||
""" Return True if users have given username. """
|
||||
return username in self.users
|
||||
|
||||
def has_user(self, username, password):
|
||||
""" Return True if users have given username with given password. """
|
||||
return username in self.users and self.users[username].is_valid_password(password)
|
||||
|
||||
def has_admin(self, username):
|
||||
""" Return True if given username is an administrator. """
|
||||
return username in self.administrators
|
||||
|
||||
def has_token(self, token):
|
||||
""" Return True if users have given token. """
|
||||
return token in self.token_to_username
|
||||
|
||||
def token_is_alive(self, token):
|
||||
""" Return True if given token is known and still alive.
|
||||
A token is alive if elapsed time since last token usage does not exceed token lifetime
|
||||
(TOKEN_LIFETIME_SECONDS).
|
||||
"""
|
||||
if self.has_token(token):
|
||||
current_time = common.timestamp_microseconds()
|
||||
elapsed_time_seconds = (current_time - self.token_timestamp[token]) / 1000000
|
||||
return elapsed_time_seconds <= TOKEN_LIFETIME_SECONDS
|
||||
return False
|
||||
|
||||
def relaunch_token(self, token):
|
||||
""" Update timestamp of given token with current timestamp. """
|
||||
if self.has_token(token):
|
||||
self.token_timestamp[token] = common.timestamp_microseconds()
|
||||
|
||||
def token_is_admin(self, token):
|
||||
""" Return True if given token is associated to an administrator. """
|
||||
return self.has_token(token) and self.has_admin(self.get_name(token))
|
||||
|
||||
def count_connections(self):
|
||||
""" Return number of registered connection handlers. """
|
||||
return len(self.connection_handler_to_tokens)
|
||||
|
||||
def get_tokens(self, username):
|
||||
""" Return a sequence of tokens associated to given username. """
|
||||
return self.username_to_tokens[username].copy()
|
||||
|
||||
def get_name(self, token):
|
||||
""" Return username of given token. """
|
||||
return self.token_to_username[token]
|
||||
|
||||
def get_user(self, username):
|
||||
""" Returns user linked to username """
|
||||
return self.users.get(username, None)
|
||||
|
||||
def get_connection_handler(self, token):
|
||||
""" Return connection handler associated to given token, or None if no handler currently associated. """
|
||||
return self.token_to_connection_handler.get(token, None)
|
||||
|
||||
def add_admin(self, username):
|
||||
""" Set given username as administrator. Related user must exists in this Users object. """
|
||||
assert username in self.users
|
||||
self.administrators.add(username)
|
||||
|
||||
def remove_admin(self, username):
|
||||
""" Remove given username from administrators. """
|
||||
if username in self.administrators:
|
||||
self.administrators.remove(username)
|
||||
|
||||
def create_token(self):
|
||||
""" Return a new token guaranteed to not exist in this Users object. """
|
||||
token = generate_token()
|
||||
while self.has_token(token):
|
||||
token = generate_token()
|
||||
return token
|
||||
|
||||
def add_user(self, username, password_hash):
|
||||
""" Add a new user with given username and hashed password.
|
||||
See diplomacy.utils.common.hash_password() for hashing purposes.
|
||||
"""
|
||||
user = User(username=username, password_hash=password_hash)
|
||||
self.users[username] = user
|
||||
return user
|
||||
|
||||
def replace_user(self, username, new_user):
|
||||
""" Replaces user object with a new user """
|
||||
self.users[username] = new_user
|
||||
|
||||
def remove_user(self, username):
|
||||
""" Remove user related to given username. """
|
||||
user = self.users.pop(username)
|
||||
self.remove_admin(username)
|
||||
for token in self.username_to_tokens.pop(user.username):
|
||||
self.token_timestamp.pop(token)
|
||||
self.token_to_username.pop(token)
|
||||
connection_handler = self.token_to_connection_handler.pop(token, None)
|
||||
if connection_handler:
|
||||
self.connection_handler_to_tokens[connection_handler].remove(token)
|
||||
if not self.connection_handler_to_tokens[connection_handler]:
|
||||
self.connection_handler_to_tokens.pop(connection_handler)
|
||||
|
||||
def remove_connection(self, connection_handler, remove_tokens=True):
|
||||
""" Remove given connection handler.
|
||||
Return tokens associated to this connection handler,
|
||||
or None if connection handler is unknown.
|
||||
|
||||
:param connection_handler: connection handler to remove.
|
||||
:param remove_tokens: if True, tokens related to connection handler are deleted.
|
||||
:return: either None or a set of tokens.
|
||||
"""
|
||||
if connection_handler in self.connection_handler_to_tokens:
|
||||
tokens = self.connection_handler_to_tokens.pop(connection_handler)
|
||||
for token in tokens:
|
||||
self.token_to_connection_handler.pop(token)
|
||||
if remove_tokens:
|
||||
self.token_timestamp.pop(token)
|
||||
user = self.users[self.token_to_username.pop(token)]
|
||||
self.username_to_tokens[user.username].remove(token)
|
||||
if not self.username_to_tokens[user.username]:
|
||||
self.username_to_tokens.pop(user.username)
|
||||
return tokens
|
||||
return None
|
||||
|
||||
def connect_user(self, username, connection_handler):
|
||||
""" Connect given username to given connection handler with a new generated token,
|
||||
and return token generated.
|
||||
|
||||
:param username: username to connect
|
||||
:param connection_handler: connection handler to link to user
|
||||
:return: a new token generated for connexion
|
||||
"""
|
||||
token = self.create_token()
|
||||
user = self.users[username]
|
||||
if connection_handler not in self.connection_handler_to_tokens:
|
||||
self.connection_handler_to_tokens[connection_handler] = set()
|
||||
if user.username not in self.username_to_tokens:
|
||||
self.username_to_tokens[user.username] = set()
|
||||
self.token_to_username[token] = user.username
|
||||
self.token_to_connection_handler[token] = connection_handler
|
||||
self.username_to_tokens[user.username].add(token)
|
||||
self.connection_handler_to_tokens[connection_handler].add(token)
|
||||
self.token_timestamp[token] = common.timestamp_microseconds()
|
||||
return token
|
||||
|
||||
def attach_connection_handler(self, token, connection_handler):
|
||||
""" Associate given token with given connection handler if token is known.
|
||||
If there is a previous connection handler associated to given token, it should be
|
||||
the same as given connection handler, otherwise an error is raised
|
||||
(meaning previous connection handler was not correctly disconnected from given token.
|
||||
It should be a programming error).
|
||||
|
||||
:param token: token
|
||||
:param connection_handler: connection handler
|
||||
"""
|
||||
if self.has_token(token):
|
||||
previous_connection = self.get_connection_handler(token)
|
||||
if previous_connection:
|
||||
assert previous_connection == connection_handler, \
|
||||
"A new connection handler cannot be attached to a token always connected to another handler."
|
||||
else:
|
||||
LOGGER.warning('Attaching a new connection handler to a token.')
|
||||
if connection_handler not in self.connection_handler_to_tokens:
|
||||
self.connection_handler_to_tokens[connection_handler] = set()
|
||||
self.token_to_connection_handler[token] = connection_handler
|
||||
self.connection_handler_to_tokens[connection_handler].add(token)
|
||||
self.token_timestamp[token] = common.timestamp_microseconds()
|
||||
|
||||
def disconnect_token(self, token):
|
||||
""" Remove given token. """
|
||||
self.token_timestamp.pop(token)
|
||||
user = self.users[self.token_to_username.pop(token)]
|
||||
self.username_to_tokens[user.username].remove(token)
|
||||
if not self.username_to_tokens[user.username]:
|
||||
self.username_to_tokens.pop(user.username)
|
||||
connection_handler = self.token_to_connection_handler.pop(token, None)
|
||||
if connection_handler:
|
||||
self.connection_handler_to_tokens[connection_handler].remove(token)
|
||||
if not self.connection_handler_to_tokens[connection_handler]:
|
||||
self.connection_handler_to_tokens.pop(connection_handler)
|
||||
Loading…
Add table
Add a link
Reference in a new issue