Implement dependencies, cleanup code

This commit is contained in:
Nico Schottelius 2019-05-26 23:30:58 +02:00
parent 935cf24833
commit 1b0578adf4

148
server.py
View file

@ -25,6 +25,32 @@ def require_args(*args):
parser.add_argument(arg, required=True) parser.add_argument(arg, required=True)
return parser.parse_args() return parser.parse_args()
class etcdWrapper(object):
""" Generalises some etcd actions """
def __init__(self, client, base):
self.client = client
self.base = base
def read_key_or_none(self, path, recursive=False):
try:
data = self.client.read(path, recursive=recursive)
except etcd.EtcdKeyNotFound:
return None
except Exception:
abort(Response(status=400, response="Error connecting to etcd"))
return data
def get_user_key_or_none(self, user, key):
path = "{}/user/{}/{}".format(self.base, user, key)
return self.read_key_or_none(path)
def set_user_key(self, user, key, value):
path = "{}/user/{}/{}".format(self.base, user, key)
self.client.write(path, value)
class Challenge(object): class Challenge(object):
""" A sample challenge -- inherit this and overwrite accordingly """ """ A sample challenge -- inherit this and overwrite accordingly """
@ -32,21 +58,12 @@ class Challenge(object):
provides = [] provides = []
requires = [] requires = []
description = None description = None
dependencies_provided_by = {}
def __init__(self, etcdclient): def __init__(self, wrapper):
self.client = etcdclient self.db = wrapper
def game(self): def game(self):
# Check if preconditions are met - otherwise error out
# This might need to me moved to the Game class to
# retrieve other challenges that we can recommend
# for req in self.requires:
# path = "/ungleichgame/v1/user/{}/{}".format(user, req)
# try:
# data = self.client.read(path)
# except etcd.EtcdKeyNotFound:
# return None
if request.method == 'GET': if request.method == 'GET':
return self.describe() return self.describe()
if request.method == 'POST': if request.method == 'POST':
@ -59,11 +76,7 @@ class Challenge(object):
""" should be called when the challenge was solved successfully""" """ should be called when the challenge was solved successfully"""
key = "points/{}".format(user, type(self).__name__) key = "points/{}".format(user, type(self).__name__)
self.set_user_key(user, key, self.points) self.db.set_user_key(user, key, self.points)
def set_user_key(self, user, key, value):
path = "/ungleichgame/v1/user/{}/{}".format(user, key)
self.client.write(path, value)
def solve(self): def solve(self):
""" Needs to be implemented per challenge """ """ Needs to be implemented per challenge """
@ -95,7 +108,7 @@ Submit your network with the "network" parameter.
return Response(status=400, response="{} mask is not /64 - please use a /64 network".format(net)) return Response(status=400, response="{} mask is not /64 - please use a /64 network".format(net))
# Save network # Save network
self.set_user_key(user, "network", network) self.db.set_user_key(user, "network", network)
self.save_points(user) self.save_points(user)
return "Network {} registered, have fun with the next challenge!".format(network) return "Network {} registered, have fun with the next challenge!".format(network)
@ -108,34 +121,53 @@ class IPv6Address(Challenge):
You have setup your network, great! You have setup your network, great!
Now it is time to show that you are really controlling your network! Now it is time to show that you are really controlling your network!
Setup the IPv6 address
{}
and POST to this address, when it should be reachable by ping6.
""" """
def describe(self): def describe(self):
pass args = require_args("user")
user = args['user']
key = "network"
network = self.db.get_user_key_or_none(user, key)
if not network:
return Response(status=400, response="""
Register a network before trying to be reachable. Possible challenges that
provide the network are:
{}
""".format("\n".join(self.dependencies_provided_by['network'])))
key = "address"
address = self.db.get_user_key_or_none(user, key)
if not address:
address = get_random_ip(network.value)
self.db.set_user_key(user, key, address)
else:
address = address.value
return self.description.format(address)
def solve(self): def solve(self):
args = require_args("user", "network") args = require_args("user")
network = args['network']
user = args['user'] user = args['user']
try: return Response(status=400, response="""
net = ipaddress.IPv6Network(network) Not yet implemented""")
except Exception as e:
return Response(status=400, response="Cannot register network {}: {}".format(network, e))
if not net.prefixlen == 64:
return Response(status=400, response="{} mask is not /64 - please use a /64 network".format(net))
# Save network
self.set_user_key(user, "network", network)
self.save_points(user)
return "Network {} registered, have fun with the next challenge!".format(network)
class Game(object): class Game(object):
def __init__(self, name, etcdclient, etcbase="/ungleichgame/v1"): def __init__(self, name, etcdclient, etcbase="/ungleichgame/v1"):
self.client = etcdclient self.client = etcdclient
self.etcbase = etcbase
self.wrapper = etcdWrapper(etcdclient, self.etcbase)
self.app = Flask(name) self.app = Flask(name)
self.app.add_url_rule('/', 'index', self.index) self.app.add_url_rule('/', 'index', self.index)
@ -143,24 +175,43 @@ class Game(object):
self.app.add_url_rule('/register', 'register', self.register, methods=['POST']) self.app.add_url_rule('/register', 'register', self.register, methods=['POST'])
# etcd paths are below here # etcd paths are below here
self.etcbase = etcbase
self.userbase = "{}/user".format(self.etcbase) self.userbase = "{}/user".format(self.etcbase)
# Automate this # Automate this
challenges = [ RegisterNet ] challenges = [ RegisterNet, IPv6Address ]
challenge_instances = []
self.app.add_url_rule('/challenge', 'list_of_challenges', self.list_of_challenges) self.app.add_url_rule('/challenge', 'list_of_challenges', self.list_of_challenges)
self.app.add_url_rule('/challenge/', 'list_of_challenges', self.list_of_challenges) self.app.add_url_rule('/challenge/', 'list_of_challenges', self.list_of_challenges)
self.providers = {}
self.challenge_names = [] self.challenge_names = []
for challenge in challenges: for challenge in challenges:
c = challenge(self.client) c = challenge(self.wrapper)
challenge_instances.append(c)
name = type(c).__name__ name = type(c).__name__
self.challenge_names.append(name) self.challenge_names.append(name)
path = "/challenge/{}".format(name) path = "/challenge/{}".format(name)
self.app.add_url_rule(path, name, c.game, methods=['GET', 'POST']) self.app.add_url_rule(path, name, c.game, methods=['GET', 'POST'])
for provider in challenge.provides:
if not provider in self.providers:
self.providers[provider] = []
self.providers[provider].append(name)
# Update challenges with provider information
for challenge in challenge_instances:
for requirement in challenge.requires:
if not requirement in self.providers:
raise Exception("Unplayable server/game: {}".format(type(challenge).__name__))
challenge.dependencies_provided_by[requirement] = self.providers[requirement]
def list_of_challenges(self): def list_of_challenges(self):
base = request.base_url base = request.base_url
@ -173,15 +224,6 @@ class Game(object):
""".format("\n".join(challenges)) """.format("\n".join(challenges))
def read_etcd(self, path, recursive=False):
try:
data = self.client.read(path, recursive=recursive)
except etcd.EtcdKeyNotFound:
return None
except Exception:
abort(Response(status=400, response="Error connecting to etcd"))
return data
def get_points(self): def get_points(self):
""" Returns a dict['username'] = points """ """ Returns a dict['username'] = points """
@ -189,16 +231,15 @@ class Game(object):
user_points = {} user_points = {}
path = "{}/".format(self.userbase) path = "{}/".format(self.userbase)
users = self.client.get(path) users = self.wrapper.read_key_or_none(path)
if users: if users:
print(users)
for user in users.children: for user in users.children:
username= user.key # needs to be FIXED username = user.key.replace(path,"")
user_points[username] = 0 user_points[username] = 0
point_path = "{}/points".format(user.key) point_path = "{}/points".format(user.key)
points = self.read_etcd(point_path, recursive=True) points = self.wrapper.read_key_or_none(point_path, recursive=True)
if not points: if not points:
continue continue
@ -308,12 +349,5 @@ Point list (aka high score)
if __name__ == '__main__': if __name__ == '__main__':
# net_base = "2a0a:e5c1:{:x}::/64"
# net_offset = random.randrange(0xffff)
# net = ipaddress.IPv6Network(net_base.format(net_offset))
# username = 'nico{}'.format(net_offset)
# print("{} has {}".format(username, net))
g = Game(__name__, etcd.Client(port=2379)) g = Game(__name__, etcd.Client(port=2379))
g.app.run(host="::", port='5002') g.app.run(host="::", port='5002')