From 1b0578adf442dbbb6aa0358a8f68aa82fafba60f Mon Sep 17 00:00:00 2001 From: Nico Schottelius Date: Sun, 26 May 2019 23:30:58 +0200 Subject: [PATCH] Implement dependencies, cleanup code --- server.py | 148 +++++++++++++++++++++++++++++++++--------------------- 1 file changed, 91 insertions(+), 57 deletions(-) diff --git a/server.py b/server.py index 903a238..d68fc51 100644 --- a/server.py +++ b/server.py @@ -25,6 +25,32 @@ def require_args(*args): parser.add_argument(arg, required=True) 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): """ A sample challenge -- inherit this and overwrite accordingly """ @@ -32,21 +58,12 @@ class Challenge(object): provides = [] requires = [] description = None + dependencies_provided_by = {} - def __init__(self, etcdclient): - self.client = etcdclient + def __init__(self, wrapper): + self.db = wrapper 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': return self.describe() if request.method == 'POST': @@ -59,11 +76,7 @@ class Challenge(object): """ should be called when the challenge was solved successfully""" key = "points/{}".format(user, type(self).__name__) - self.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) + self.db.set_user_key(user, key, self.points) def solve(self): """ 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)) # Save network - self.set_user_key(user, "network", network) + self.db.set_user_key(user, "network", network) self.save_points(user) return "Network {} registered, have fun with the next challenge!".format(network) @@ -108,34 +121,53 @@ class IPv6Address(Challenge): You have setup your network, great! 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): - 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): - args = require_args("user", "network") - network = args['network'] + args = require_args("user") user = args['user'] - try: - net = ipaddress.IPv6Network(network) - 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) + return Response(status=400, response=""" +Not yet implemented""") class Game(object): def __init__(self, name, etcdclient, etcbase="/ungleichgame/v1"): self.client = etcdclient + self.etcbase = etcbase + + self.wrapper = etcdWrapper(etcdclient, self.etcbase) + self.app = Flask(name) 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']) # etcd paths are below here - self.etcbase = etcbase + self.userbase = "{}/user".format(self.etcbase) # 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.providers = {} self.challenge_names = [] for challenge in challenges: - c = challenge(self.client) + c = challenge(self.wrapper) + challenge_instances.append(c) + name = type(c).__name__ self.challenge_names.append(name) path = "/challenge/{}".format(name) 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): base = request.base_url @@ -173,15 +224,6 @@ class Game(object): """.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): """ Returns a dict['username'] = points """ @@ -189,16 +231,15 @@ class Game(object): user_points = {} path = "{}/".format(self.userbase) - users = self.client.get(path) + users = self.wrapper.read_key_or_none(path) if users: - print(users) for user in users.children: - username= user.key # needs to be FIXED + username = user.key.replace(path,"") user_points[username] = 0 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: continue @@ -308,12 +349,5 @@ Point list (aka high score) 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.app.run(host="::", port='5002')