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
import ipaddress
import random
import sys
import etcd
import json
import datetime
# Important imports
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 etcd3_wrapper import Etcd3Wrapper
# Generate random IPv6 address
def get_random_ip(network):
net = ipaddress.IPv6Network(network)
addr_offset = random.randrange(2**64)
addr = net[0] + addr_offset
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):
parser = reqparse.RequestParser()
for arg in 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)
# Classes
class Challenge(object):
""" A sample challenge -- inherit this and overwrite accordingly """
@ -76,16 +61,22 @@ class Challenge(object):
""" should be called when the challenge was solved successfully"""
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):
""" Needs to be implemented per challenge """
pass
# Challenge description works
class RegisterNet(Challenge):
#self.etcbase = etcbase
#self.wrapper = Etcd3Wrapper()
#self.app = Flask(name)
wrapper = Etcd3Wrapper()
points = 10
provides = [ "network" ]
description = """
Register a /64 IPv6 network that you fully control.
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.
"""
def solve(self):
#self.wrapper = Etcd3Wrapper()
args = require_args("user", "network")
network = args['network']
user = args['user']
@ -107,16 +99,25 @@ Submit your network with the "network" parameter.
if not net.prefixlen == 64:
return Response(status=400, response="{} mask is not /64 - please use a /64 network".format(net))
# Save network
self.db.set_user_key(user, "network", network)
self.save_points(user)
path = "{}/{}/points".format("/user", user)
cur = self.wrapper.get(path)
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):
wrapper = Etcd3Wrapper()
points = 20
requires = [ "network" ]
description = """
You have setup your network, great!
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):
args = require_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:
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)
#key = "address"
address = self.wrapper.get('/user/' + user + '/ip')
if not address:
address = get_random_ip(network.value)
self.db.set_user_key(user, key, address)
#self.db.set_user_key(user, key, address)
else:
address = address.value
@ -156,35 +156,76 @@ provide the network are:
def solve(self):
args = require_args("user")
user = args['user']
return Response(status=400, response="""
Not yet implemented""")
address = self.wrapper.get('/user/' + user + '/ip')
response = os.system('ping -c 1 -t 3 ' + address.value)
if response == 0:
x = address.value + ' is up!'
else:
x = address.value + ' is down!'
return x
class Game(object):
def __init__(self, name, etcdclient, etcbase="/ungleichgame/v1"):
self.client = etcdclient
def __init__(self, name, etcdclient, etcbase="/"):
self.etcbase = etcbase
self.wrapper = etcdWrapper(etcdclient, self.etcbase)
self.wrapper = Etcd3Wrapper()
self.app = Flask(name)
#self.userbase = "{}/user".format(self.etcbase)
self.app.add_url_rule('/', 'index', self.index)
self.app.add_url_rule('/points', 'points', self.points)
self.app.add_url_rule('/register', 'register', self.register, methods=['POST'])
# Index text works! _
# 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
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:
@ -212,45 +253,46 @@ class Game(object):
challenge.dependencies_provided_by[requirement] = self.providers[requirement]
# List of challenges works _
def list_of_challenges(self):
base = request.base_url
challenges = [ "{} ({})".format(name, "{}/{}".format(base, name)) for name in self.challenge_names ]
return """The following challenges are available on this server:
def list_of_challenges():
base = request.base_url
challenges = [ "{} ({})".format(name, "{}/{}".format(base, name)) for name in self.challenge_names ]
return """The following challenges are available on this server:
{}
""".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(self):
def get_points(user):
""" Returns a dict['username'] = points """
user_points = {}
#user_points = {}
path = "{}/".format(self.userbase)
users = self.wrapper.read_key_or_none(path)
#path = "{}/".format(self.userbase)
#users = self.wrapper.read_key_or_none(path)
points = self.wrapper.get('/user/' + user + '/points').value
if users:
for user in users.children:
username = user.key.replace(path,"")
user_points[username] = 0
###if users:
### for user in users:
# username = user.key.replace(path,"")
# 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)
points = self.wrapper.read_key_or_none(point_path, recursive=True)
# for challenge in points.children:
# user_points[username] += int(challenge.value)
if not points:
continue
# return user_points
for challenge in points.children:
user_points[username] += int(challenge.value)
return user_points
def index(self):
points = self.points()
# def index(self):
# points = self.points()
return """Welcome to the game server!
@ -278,76 +320,8 @@ Point list (aka high score)
{}
""".format("\n".join(res))
def register(self):
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")
# This (below) works :D
if __name__ == '__main__':
g = Game(__name__, etcd.Client(port=2379))
g = Game(__name__, etcd3.client(port=2379))
g.app.run(host="::", port=5002)