Update server.py

This commit is contained in:
sxiii 2019-12-01 05:45:34 +01:00
parent 1e19efc15f
commit 3fdb385ad9
1 changed files with 133 additions and 159 deletions

292
server.py
View File

@ -2,54 +2,39 @@
USERLENGTH = 50 USERLENGTH = 50
import ipaddress # Important imports
import random
import sys
import etcd
import json
import datetime
from flask import Flask, abort, request, Response import ipaddress, random, sys, etcd3, json, datetime, os
from flask import Flask, abort, request, Response, jsonify
from flask_restful import reqparse from flask_restful import reqparse
from etcd3_wrapper import Etcd3Wrapper
# Generate random IPv6 address
def get_random_ip(network): def get_random_ip(network):
net = ipaddress.IPv6Network(network) net = ipaddress.IPv6Network(network)
addr_offset = random.randrange(2**64) addr_offset = random.randrange(2**64)
addr = net[0] + addr_offset addr = net[0] + addr_offset
return addr return addr
# Check our own IPv6 address by using ipv6-test API
# This might be used to check internet connectivity
# def check_internet():
# import urllib.request, urllib.error, urllib.parse
# url = 'http://v6.ipv6-test.com/api/myip.php'
# response = urllib.request.urlopen(url)
# webContent = response.read()
# return webContent
# RESTful flask arguments parsing
def require_args(*args): def require_args(*args):
parser = reqparse.RequestParser() parser = reqparse.RequestParser()
for arg in args: for arg in args:
parser.add_argument(arg, required=True) parser.add_argument(arg, required=True)
return parser.parse_args() return parser.parse_args()
class etcdWrapper(object): # Classes
""" 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 """
@ -76,16 +61,22 @@ 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.db.set_user_key(user, key, self.points) #self.db.set_user_key(user, key, self.points)
self.wrapper.put('/users' + user + '/points', self.points)
def solve(self): def solve(self):
""" Needs to be implemented per challenge """ """ Needs to be implemented per challenge """
pass pass
# Challenge description works
class RegisterNet(Challenge): class RegisterNet(Challenge):
#self.etcbase = etcbase
#self.wrapper = Etcd3Wrapper()
#self.app = Flask(name)
wrapper = Etcd3Wrapper()
points = 10 points = 10
provides = [ "network" ] provides = [ "network" ]
description = """ description = """
Register a /64 IPv6 network that you fully control. Register a /64 IPv6 network that you fully control.
Many other challenges depend on this. You will need to Many other challenges depend on this. You will need to
@ -95,6 +86,7 @@ and to setup services listening on these IPv6 addresses.
Submit your network with the "network" parameter. Submit your network with the "network" parameter.
""" """
def solve(self): def solve(self):
#self.wrapper = Etcd3Wrapper()
args = require_args("user", "network") args = require_args("user", "network")
network = args['network'] network = args['network']
user = args['user'] user = args['user']
@ -107,16 +99,25 @@ Submit your network with the "network" parameter.
if not net.prefixlen == 64: if not net.prefixlen == 64:
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 path = "{}/{}/points".format("/user", user)
self.db.set_user_key(user, "network", network) cur = self.wrapper.get(path)
self.save_points(user) if cur == None:
self.wrapper.put('/user/' + user + '/network', network)
points = self.wrapper.get(path)
self.wrapper.put('/user/' + user + '/points', '20') # Adjust points for first challenge here
x = "Network {} registered, have fun with the next challenge!\n".format(network)
else:
x = "Network {} is already registered.\n".format(network)
#self.db.set_user_key(user, "network", network)
#save_points(user)
return x
return "Network {} registered, have fun with the next challenge!".format(network) ######################################
class IPv6Address(Challenge): class IPv6Address(Challenge):
wrapper = Etcd3Wrapper()
points = 20 points = 20
requires = [ "network" ] requires = [ "network" ]
description = """ description = """
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!
@ -131,23 +132,22 @@ and POST to this address, when it should be reachable by ping6.
def describe(self): def describe(self):
args = require_args("user") args = require_args("user")
user = args['user'] user = args['user']
key = "network" #key = "network"
network = self.db.get_user_key_or_none(user, key) network = self.wrapper.get('/user/' + user + '/network')
if not network: if not network:
return Response(status=400, response=""" return Response(status=400, response="""
Register a network before trying to be reachable. Possible challenges that Register a network before trying to be reachable. Possible challenges that
provide the network are: provide the network are:
{} {}
""".format("\n".join(self.dependencies_provided_by['network']))) """.format("\n".join(self.dependencies_provided_by['network'])))
key = "address" #key = "address"
address = self.db.get_user_key_or_none(user, key) address = self.wrapper.get('/user/' + user + '/ip')
if not address: if not address:
address = get_random_ip(network.value) address = get_random_ip(network.value)
self.db.set_user_key(user, key, address) #self.db.set_user_key(user, key, address)
else: else:
address = address.value address = address.value
@ -156,35 +156,76 @@ provide the network are:
def solve(self): def solve(self):
args = require_args("user") args = require_args("user")
user = args['user'] user = args['user']
address = self.wrapper.get('/user/' + user + '/ip')
return Response(status=400, response=""" response = os.system('ping -c 1 -t 3 ' + address.value)
Not yet implemented""") if response == 0:
x = address.value + ' is up!'
else:
x = address.value + ' is down!'
return x
class Game(object): class Game(object):
def __init__(self, name, etcdclient, etcbase="/ungleichgame/v1"): def __init__(self, name, etcdclient, etcbase="/"):
self.client = etcdclient
self.etcbase = etcbase self.etcbase = etcbase
self.wrapper = Etcd3Wrapper()
self.wrapper = etcdWrapper(etcdclient, self.etcbase)
self.app = Flask(name) self.app = Flask(name)
#self.userbase = "{}/user".format(self.etcbase)
self.app.add_url_rule('/', 'index', self.index) # Index text works! _
self.app.add_url_rule('/points', 'points', self.points)
self.app.add_url_rule('/register', 'register', self.register, methods=['POST'])
# etcd paths are below here def index():
try:
from_etcd = self.wrapper.get("/index")
response = from_etcd.key, ' : ', from_etcd.value
except:
response = 'The key is empty.'
return Response(response, 200)
self.app.add_url_rule('/', 'index', index)
self.userbase = "{}/user".format(self.etcbase) # End of index ^
# The registration works! _
def register():
args = require_args("user")
user = args['user']
value = str(datetime.datetime.now())
netaddr = request.remote_addr
cur = self.wrapper.get('/user/' + user)
if cur == None:
self.wrapper.put('/user/' + user, 'user exist')
self.wrapper.put('/user/' + user + '/registered_at', value)
self.wrapper.put('/user/' + user + '/ip', netaddr)
x = "User @" + user + " successfully registered on " + value + " with IP: " + netaddr + '\n'
else:
time = self.wrapper.get('/user/' + user + '/registered_at')
ip = self.wrapper.get('/user/' + user + '/ip')
x = "User @" + user + " is already registered on " + time.value + ". His IP is: " + ip.value + '\n'
return Response(x,200)
self.app.add_url_rule('/register', 'register', register, methods=['POST'])
# End of registration ^
# Getting point for one user works! _
def points():
args = require_args("user")
user = args['user']
try:
from_etcd = self.wrapper.get("/user/" + user + "/points")
response = from_etcd.key, ' : ', from_etcd.value
except:
response = 'The key is empty.'
return Response(response, 200)
self.app.add_url_rule('/points', 'points', points, methods=['POST'])
# End of getting points ^
# Automate this # Automate this
challenges = [ RegisterNet, IPv6Address ] challenges = [ RegisterNet, IPv6Address ]
challenge_instances = [] 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.providers = {}
self.challenge_names = [] self.challenge_names = []
for challenge in challenges: for challenge in challenges:
@ -212,45 +253,46 @@ class Game(object):
challenge.dependencies_provided_by[requirement] = self.providers[requirement] challenge.dependencies_provided_by[requirement] = self.providers[requirement]
# List of challenges works _
def list_of_challenges(self): def list_of_challenges():
base = request.base_url base = request.base_url
challenges = [ "{} ({})".format(name, "{}/{}".format(base, name)) for name in self.challenge_names ] challenges = [ "{} ({})".format(name, "{}/{}".format(base, name)) for name in self.challenge_names ]
return """The following challenges are available on this server:
return """The following challenges are available on this server:
{} {}
""".format("\n".join(challenges)) """.format("\n".join(challenges))
self.app.add_url_rule('/challenge', 'list_of_challenges', list_of_challenges)
self.app.add_url_rule('/challenge/', 'list_of_challenges', list_of_challenges)
# End of list of challenges ^
def get_points(user):
def get_points(self):
""" Returns a dict['username'] = points """ """ Returns a dict['username'] = points """
user_points = {} #user_points = {}
path = "{}/".format(self.userbase) #path = "{}/".format(self.userbase)
users = self.wrapper.read_key_or_none(path) #users = self.wrapper.read_key_or_none(path)
points = self.wrapper.get('/user/' + user + '/points').value
if users: ###if users:
for user in users.children: ### for user in users:
username = user.key.replace(path,"") # username = user.key.replace(path,"")
user_points[username] = 0 # user_points[username] = 0
### print("inside users: user " + user)
#point_path = "{}/points".format(user.key)
### points = self.wrapper.get("{}/points".format(user))
### print("Points: " + points)
# if not points:
# continue
point_path = "{}/points".format(user.key) # for challenge in points.children:
points = self.wrapper.read_key_or_none(point_path, recursive=True) # user_points[username] += int(challenge.value)
if not points: # return user_points
continue
for challenge in points.children: # def index(self):
user_points[username] += int(challenge.value) # points = self.points()
return user_points
def index(self):
points = self.points()
return """Welcome to the game server! return """Welcome to the game server!
@ -278,76 +320,8 @@ Point list (aka high score)
{} {}
""".format("\n".join(res)) """.format("\n".join(res))
def register(self): # This (below) works :D
args = require_args("user")
path = "{}/{}/registered_at".format(self.userbase, args['user'])
value = str(datetime.datetime.now())
cur = self.read_etcd(path)
if cur:
value = cur.value
else:
self.client.write(path, value)
return "Registered at: {}\n".format(value)
# def get_ip_address():
# args = self.require_args("network", "user")
# # Needs to be fixed with ungleich-otp
# username=args['user']
# if request.method == 'GET':
# return Response("""
# This is an easy level - just register any /64 network
# that you fully control. After submission the game server will generate
# a random IPv6 address in this network.
# """)
# client = etcd.Client(port=2379)
# try:
# data = client.read("/ungleichgame/v1/{}/network".format(username))
# # FIXME: differentiate keynotfound and other errors
# except Exception as e:
# return Response(status=400, response="Cannot read your network, try registering first (error: {})".format(e))
# return Response("data={}".format(data.value))
# address = get_random_ip(data.value)
# # FIXME: catch errors
# client.write("/ungleichgame/v1/{}/address".format(username), address)
# return Response("Your IPv6 address for this game is {}. Make it pingable and post to /level/1/result".format(address))
# @app.route("/level/2", methods=['GET', 'POST']) # post for username
# def pingme():
# parser = reqparse.RequestParser()
# parser.add_argument('user', required=True)
# args = parser.parse_args()
# # Needs to be fixed with ungleich-otp
# username=args['user']
# if request.method == 'GET':
# return Response("""
# Proof that you can really control the network that you submitted:
# - Setup the IPv6 address to be ping6 able globally
# - POST to this address when it is configured
# """)
# if request.method == 'POST':
# try:
# data = client.read("/ungleichgame/v1/{}/address".format(username), address)
# except Exception as e:
# return Response(status=400,
# response="""
# You need to register a network before trying to be reachable.
# Please go back to Level 1 for registering your network.
# """)
# return Response("something good")
if __name__ == '__main__': if __name__ == '__main__':
g = Game(__name__, etcd.Client(port=2379)) g = Game(__name__, etcd3.client(port=2379))
g.app.run(host="::", port=5002) g.app.run(host="::", port=5002)