Added first pass of an api key system

+API Keys are kept in the database and are 25 characters long
+Each key will have certain permission allowed to it
+Only permission right now is for using bot commands
doc_update
Joey Hines 2018-12-29 19:41:18 -06:00
parent 61433c271f
commit 23739b31f9
9 changed files with 126 additions and 62 deletions

View File

@ -6,4 +6,5 @@ admin.site.register(Player)
admin.site.register(Base) admin.site.register(Base)
admin.site.register(Shop) admin.site.register(Shop)
admin.site.register(Tunnel) admin.site.register(Tunnel)
admin.site.register(ItemListing) admin.site.register(ItemListing)
admin.site.register(APIToken)

View File

@ -7,7 +7,16 @@ import inspect
command_dict = {"GET": {}, "POST": {}, "DELETE": {}} command_dict = {"GET": {}, "POST": {}, "DELETE": {}}
def getRequiredArgs(func): def objects_list_to_json(obj_list):
json_list = []
for obj in obj_list:
json_list.append(obj.json)
return json_list
def get_required_args(func):
args = inspect.getfullargspec(func) args = inspect.getfullargspec(func)
return args.args + args.kwonlyargs return args.args + args.kwonlyargs
@ -16,7 +25,8 @@ def getRequiredArgs(func):
def command(type): def command(type):
def command_dec(func): def command_dec(func):
def add_command(): def add_command():
command_dict[type][func.__name__] = {"func": func, "params": getRequiredArgs(func)} command_dict[type][func.__name__] = {"func": func, "params": get_required_args(func)}
return func
return add_command() return add_command()
@ -82,7 +92,7 @@ def add_location(x_pos, z_pos, name=None, discord_uuid=None, mc_uuid=None, loc_t
try: try:
get_location(player, name, loc_type=loc_type) get_location(player, name, loc_type=loc_type)
raise EntryNameNotUniqueError raise EntryNameNotUniqueError
except Location.DoesNotExist: except (Location.DoesNotExist, NoLocationsInDatabase):
if name is None: if name is None:
name = "{}'s {}".format(player.name, loc_type.__name__) name = "{}'s {}".format(player.name, loc_type.__name__)
@ -106,23 +116,19 @@ def add_tunnel(tunnel_direction, tunnel_number, location_name=None, discord_uuid
tunnel = Tunnel.objects.create(tunnel_direction=tunnel_direction, tunnel_number=tunnel_number, tunnel = Tunnel.objects.create(tunnel_direction=tunnel_direction, tunnel_number=tunnel_number,
location=get_location(player, location_name)) location=get_location(player, location_name))
return tunnel return tunnel.json
@command("GET") @command("GET")
def find_location(search): def find_location(search):
limit = 25 limit = 25
locations_obj = Location.objects.filter(Q(name__icontains=search) | Q(owner__name__icontains=search)).all()[:limit] locations = Location.objects.filter(Q(name__icontains=search) | Q(owner__name__icontains=search)).all()[:limit]
if len(locations_obj) == 0: if len(locations) == 0:
raise LocationLookUpError raise LocationLookUpError
locations_json = [] return objects_list_to_json(locations)
for location in locations_obj:
locations_json.append(location.json)
return locations_json
@command("DELETE") @command("DELETE")
@ -133,10 +139,10 @@ def delete(name, discord_uuid=None, mc_uuid=None):
@command("GET") @command("GET")
def find_around(x_pos, z_pos, radius=200): def find_around(x_pos, z_pos, radius=200):
locations = Location.objects.get(x_coord__range=(x_pos - radius, x_pos + radius), locations = Location.objects.filter(x_coord__range=(x_pos - radius, x_pos + radius),
z_coord__range=(z_pos - radius, z_pos + radius), dimension='O') z_coord__range=(z_pos - radius, z_pos + radius)).all()
return locations return objects_list_to_json(locations)
@command("POST") @command("POST")
@ -147,7 +153,7 @@ def add_item(item_name, quantity, diamond_price, shop_name=None, discord_uuid=No
item_listing = ItemListing.objects.create(shop=shop, amount=quantity, price=diamond_price, item_name=item_name) item_listing = ItemListing.objects.create(shop=shop, amount=quantity, price=diamond_price, item_name=item_name)
return item_listing return item_listing.json
# TODO Re-implement selling shop search # TODO Re-implement selling shop search
@ -167,9 +173,9 @@ def selling(item_name):
shop = Shop.objects.get(pk=shop_id['shop_id']).json shop = Shop.objects.get(pk=shop_id['shop_id']).json
item_query = ItemListing.objects.annotate(normalized_price=F('price') / F('amount')) \ item_query = ItemListing.objects.annotate(normalized_price=F('price') / F('amount')) \
.filter(item_name__icontains=item_name, shop_id=shop_id) \ .filter(item_name__icontains=item_name, shop_id=shop_id['shop_id']) \
.order_by('normalized_price') \ .order_by('normalized_price') \
.values("item_name", "amount", "price") .values("item_name", "price", "amount")
item_list = [] item_list = []
for item in item_query: for item in item_query:
@ -196,17 +202,12 @@ def info(location_name):
@command("GET") @command("GET")
def tunnel(player_name): def tunnel(player_name):
tunnels_obj = Tunnel.objects.filter(location__owner__name__icontains=player_name).all() tunnels = Tunnel.objects.filter(location__owner__name__icontains=player_name).all()
if len(tunnels_obj) == 0: if len(tunnels) == 0:
raise LocationLookUpError raise LocationLookUpError
tunnels_json = [] return objects_list_to_json(tunnel)
for t in tunnels_obj:
tunnels_json.append(t.json)
return tunnels_json
@command("POST") @command("POST")
@ -219,7 +220,7 @@ def edit_pos(x, z, loc_name, discord_uuid=None, mc_uuid=None):
location.z_coord = z location.z_coord = z
location.save() location.save()
return location return location.json
@command("POST") @command("POST")
@ -233,7 +234,7 @@ def edit_tunnel(tunnel_direction, tunnel_number, loc_name, discord_uuid=None, mc
else: else:
Tunnel.objects.create(tunnel_direction=tunnel_direction, tunnel_number=tunnel_number, location=location) Tunnel.objects.create(tunnel_direction=tunnel_direction, tunnel_number=tunnel_number, location=location)
return location return location.json
@command("POST") @command("POST")
@ -244,7 +245,7 @@ def edit_name(new_name, loc_name, discord_uuid=None, mc_uuid=None):
location.name = new_name location.name = new_name
location.save() location.save()
return location return location.json
@command("DELETE") @command("DELETE")
@ -255,20 +256,16 @@ def delete_item(item, shop_name, discord_uuid=None, mc_uuid=None):
ItemListing.objects.filter(item_name__iexact=item, shop=shop).delete() ItemListing.objects.filter(item_name__iexact=item, shop=shop).delete()
return shop return shop.json
@command("GET") @command("GET")
def me(discord_uuid=None, mc_uuid=None): def me(discord_uuid=None, mc_uuid=None):
player = get_player(discord_uuid=discord_uuid, mc_uuid=mc_uuid) player = get_player(discord_uuid=discord_uuid, mc_uuid=mc_uuid)
locations_obj = Location.objects.filter(owner=player).all() locations = Location.objects.filter(owner=player).all()
if len(locations_obj) == 0: if len(locations) == 0:
raise PlayerNotFound raise PlayerNotFound
locations_json = [] return objects_list_to_json(locations)
for location in locations_obj:
locations_json.append(location.json)
return locations_json

1
api/models.py 100644
View File

@ -0,0 +1 @@
from django.db import models

View File

@ -1,21 +1,33 @@
from django.views.generic import View from django.views.generic import View
from django.http import JsonResponse from django.http import JsonResponse
from django.conf import settings from django.conf import settings
import inspect
import GeoffreyApp.api.commands as commands import GeoffreyApp.api.commands as commands
from GeoffreyApp.errors import * from GeoffreyApp.errors import *
from GeoffreyApp.models import APIToken
def check_token(request, **kwargs):
if "api" in request:
key = request["api"]
if APIToken.objects.filter(key=key, **kwargs).exists():
return True
return False
def run_command(request, command, req_type): def run_command(request, command, req_type):
command = command.lower() command = command.lower()
try: try:
if command in commands.command_dict[req_type]: if command in commands.command_dict[req_type]:
response = commands.command_dict[req_type][command]["func"](**request.dict()) params = request.dict()
params.pop("api")
response = commands.command_dict[req_type][command]["func"](**params)
else: else:
raise CommandNotFound raise CommandNotFound
except Exception as e: except Exception as e:
response = {"error": e.__class__.__name__} response = {"error": e.__class__.__name__, "error_message": str(e)}
return JsonResponse(response, safe=False) return JsonResponse(response, safe=False)
@ -23,18 +35,28 @@ def run_command(request, command, req_type):
class CommandAPI(View): class CommandAPI(View):
def get(self, request, command): def get(self, request, command):
get = request.GET get = request.GET
if command.lower() == "commands":
return JsonResponse(command.commands_dict) if check_token(get, commands_perm=True):
if command.lower() == "commands":
return JsonResponse(command.commands_dict)
else:
return run_command(get, command, "GET")
else: else:
return run_command(get, command, "GET") return JsonResponse({})
def post(self, request, command): def post(self, request, command):
post = request.POST post = request.POST
return run_command(post, command, "POST") if check_token(post, commands_perm=True):
return run_command(post, command, "POST")
else:
return JsonResponse({})
def delete(self, request, command): def delete(self, request, command):
delete = request.DELETE delete = request.DELETE
return run_command(delete, command, "DELETE") if check_token(delete, commands_perm=True):
return run_command(delete, command, "DELETE")
else:
return JsonResponse({})
class SettingsAPI(View): class SettingsAPI(View):

View File

@ -2,9 +2,23 @@ from django.db import models
from django.conf import settings from django.conf import settings
from sys import maxsize from sys import maxsize
from GeoffreyApp.util import create_token
# Create your models here. # Create your models here.
class APIToken(models.Model):
key = models.CharField(default=create_token, max_length=25, unique=True)
name = models.CharField(max_length=50, blank=True)
commands_perm = models.BooleanField(default=False)
def __str__(self):
if len(self.name):
return "{}: {}".format(self.name, self.key)
else:
return self.key
class Player(models.Model): class Player(models.Model):
name = models.CharField(max_length=30, unique=True) name = models.CharField(max_length=30, unique=True)
mc_uuid = models.CharField(max_length=36, unique=True) mc_uuid = models.CharField(max_length=36, unique=True)
@ -38,6 +52,9 @@ class Location(models.Model):
return {"Type": self.__class__.__name__, "Name": self.name, "x_coord": self.x_coord, "z_coord": self.z_coord, return {"Type": self.__class__.__name__, "Name": self.name, "x_coord": self.x_coord, "z_coord": self.z_coord,
"dimension": self.dimension, "Owner": self.owner.json} "dimension": self.dimension, "Owner": self.owner.json}
def __str__(self):
return self.name
class Shop(Location): class Shop(Location):
def __str__(self): def __str__(self):

View File

@ -22,37 +22,38 @@ class CommandsAPITestCase(TestCase):
Tunnel.objects.all().delete() Tunnel.objects.all().delete()
def populate(self): def populate(self):
base = Base.objects.create(owner=self.player, name="test", x_coord=0, z_coord=0) base = Base.objects.create(owner=self.player, name="test", x_coord=0, z_coord=0, dimension="O")
shop = Shop.objects.create(owner=self.player, name="test shop", x_coord=0, z_coord=0) shop = Shop.objects.create(owner=self.player, name="test shop", x_coord=500, z_coord=500,
dimension="O")
item = ItemListing.objects.create(shop=shop, price=1, amount=5, item_name="sed") item = ItemListing.objects.create(shop=shop, price=1, amount=5, item_name="sed")
tunnel = Tunnel.objects.create(tunnel_number="42", tunnel_direction="S", location=base) tunnel = Tunnel.objects.create(tunnel_number="42", tunnel_direction="S", location=base)
def test_register(self): def test_register(self):
command_dict["POST"]["register"]["func"](player_name="Vakky", discord_uuid="229423434256351233") register(player_name="Vakky", discord_uuid="229423434256351233")
count = Player.objects.filter(mc_uuid__iexact="7afbf6632bf049ef915f22e81b298d17").count() count = Player.objects.filter(mc_uuid__iexact="7afbf6632bf049ef915f22e81b298d17").count()
self.assertEqual(count, 1) self.assertEqual(count, 1)
def test_add_base(self): def test_add_base(self):
command_dict["POST"]["add_base"]["func"](x_pos=0, z_pos=0, name=None, discord_uuid=DISCORD_UUID) add_base(x_pos=0, z_pos=0, name=None, discord_uuid=DISCORD_UUID)
base = Base.objects.filter(name__icontains=USERNAME).all().first() base = Base.objects.filter(name__icontains=USERNAME).all().first()
self.assertEqual(base.owner.name, "ZeroHD") self.assertEqual(base.owner.name, "ZeroHD")
def test_add_shop(self): def test_add_shop(self):
command_dict["POST"]["add_shop"]["func"](x_pos=0, z_pos=0, name=None, discord_uuid=DISCORD_UUID) add_shop(x_pos=0, z_pos=0, name=None, discord_uuid=DISCORD_UUID)
shop = Shop.objects.filter(name__icontains=USERNAME).all().first() shop = Shop.objects.filter(name__icontains=USERNAME).all().first()
self.assertEqual(shop.owner.name, "zerohd") self.assertEqual(shop.owner.name, "ZeroHD")
def test_add_tunnel(self): def test_add_tunnel(self):
base = Base.objects.create(owner=self.player, name="Test", x_coord=0, z_coord=0) base = Base.objects.create(owner=self.player, name="Test", x_coord=0, z_coord=0)
command_dict["POST"]["add_tunnel"]["func"](tunnel_direction="N", tunnel_number=500, discord_uuid=DISCORD_UUID) add_tunnel(tunnel_direction="N", tunnel_number=500, discord_uuid=DISCORD_UUID)
qbase = Base.objects.filter(tunnel_location__tunnel_direction="N").all().first() qbase = Base.objects.filter(tunnel_location__tunnel_direction="N").all().first()
@ -60,8 +61,7 @@ class CommandsAPITestCase(TestCase):
def test_add_item(self): def test_add_item(self):
shop = Shop.objects.create(owner=self.player, name="Test", x_coord=0, z_coord=0) shop = Shop.objects.create(owner=self.player, name="Test", x_coord=0, z_coord=0)
command_dict["POST"]["add_item"]["func"](item_name="sed", quantity=5, diamond_price=5, add_item(item_name="sed", quantity=5, diamond_price=5, discord_uuid=DISCORD_UUID)
discord_uuid=DISCORD_UUID)
item = ItemListing.objects.filter(shop_id=shop.id).all().first() item = ItemListing.objects.filter(shop_id=shop.id).all().first()
@ -70,7 +70,7 @@ class CommandsAPITestCase(TestCase):
def test_edit_post(self): def test_edit_post(self):
shop = Shop.objects.create(owner=self.player, name="Test", x_coord=0, z_coord=0) shop = Shop.objects.create(owner=self.player, name="Test", x_coord=0, z_coord=0)
command_dict["POST"]["edit_pos"]["func"](x=500, z=500, loc_name="Test", discord_uuid=DISCORD_UUID) edit_pos(x=500, z=500, loc_name="Test", discord_uuid=DISCORD_UUID)
qshop = Shop.objects.filter(name__exact="Test").all().first() qshop = Shop.objects.filter(name__exact="Test").all().first()
@ -78,43 +78,49 @@ class CommandsAPITestCase(TestCase):
def test_find_location(self): def test_find_location(self):
self.populate() self.populate()
locations = command_dict["GET"]["find_location"]["func"](search="Test") locations = find_location(search="Test")
count = len(locations) count = len(locations)
self.assertEqual(count, 2) self.assertEqual(count, 2)
def test_find_around(self):
self.populate()
locations = find_around(x_pos=0, z_pos=0, radius=50)
self.assertEqual(len(locations), 1)
def test_selling(self): def test_selling(self):
self.populate() self.populate()
items = command_dict["GET"]["selling"]["func"](item_name="sED") items = selling(item_name="sED")
self.assertEqual(len(items), 1) self.assertEqual(len(items), 1)
def test_info(self): def test_info(self):
self.populate() self.populate()
location = command_dict["GET"]["info"]["func"](location_name="test") location = info(location_name="test")
self.assertEqual(location["Name"], "test") self.assertEqual(location["Name"], "test")
def test_tunnel(self): def test_tunnel(self):
self.populate() self.populate()
tunnels = command_dict["GET"]["tunnel"]["func"](player_name=USERNAME) tunnels = tunnel(player_name=USERNAME)
self.assertEqual(len(tunnels), 1) self.assertEqual(len(tunnels), 1)
def test_me(self): def test_me(self):
self.populate() self.populate()
locations = command_dict["GET"]["me"]["func"](discord_uuid=DISCORD_UUID) locations = me(discord_uuid=DISCORD_UUID)
self.assertEqual(len(locations), 2) self.assertEqual(len(locations), 2)
def test_delete(self): def test_delete(self):
self.populate() self.populate()
command_dict["DELETE"]["delete"]["func"](name="test", discord_uuid=DISCORD_UUID) delete(name="test", discord_uuid=DISCORD_UUID)
locations = Location.objects.filter(name__icontains="test").all() locations = Location.objects.filter(name__icontains="test").all()
@ -123,7 +129,7 @@ class CommandsAPITestCase(TestCase):
def delete_item(self): def delete_item(self):
self.populate() self.populate()
command_dict["DELETE"]["delete_item"]["func"](item="sed", shop_name="test shop", discord_uuid=DISCORD_UUID) delete_item(item="sed", shop_name="test shop", discord_uuid=DISCORD_UUID)
items = ItemListing.objects.filter(item_name__iexact="sed").all() items = ItemListing.objects.filter(item_name__iexact="sed").all()

7
test/test_key.py 100644
View File

@ -0,0 +1,7 @@
from django.test import TestCase
from GeoffreyApp.util import create_token
class TokenAPITestCase(TestCase):
def test_create_token(self):
print(create_token())

12
util.py 100644
View File

@ -0,0 +1,12 @@
import string
import random
def create_token(length=25):
token = ''
for i in range(0, length):
d = random.choice(string.digits + string.ascii_lowercase)
token += d
return token

View File

@ -45,6 +45,7 @@ class ShopList(generic.ListView):
class BaseList(generic.ListView): class BaseList(generic.ListView):
model = Base model = Base
class ItemListingList(generic.ListView): class ItemListingList(generic.ListView):
model = ItemListing model = ItemListing