Commit edf0613a authored by Hubert Denkmair's avatar Hubert Denkmair
Browse files

Merge branch 'feature/api'

parents c333191a 0b73aa6c
......@@ -35,7 +35,8 @@ INSTALLED_APPS = [
'visualization',
'ide',
'highscore',
'docs'
'docs',
'api'
]
MIDDLEWARE = [
......
......@@ -18,4 +18,5 @@ urlpatterns = [
path('snake/', include('ide.urls')),
path('highscore/', include('highscore.urls')),
path('docs/', include('docs.urls')),
path('api/v1/', include('api.urls')),
]
from django.contrib import admin
# Register your models here.
from django.apps import AppConfig
class ApiConfig(AppConfig):
name = 'api'
from django.db import models
# Create your models here.
{% extends 'core/base.html' %}
{% load static %}
{% block css %}
<style>
table.api_keys td {
padding: 0 1em;
}
table.api_keys td, table.api_keys th
{
background-color:rgba(0,0,0,0.2);
}
table.api_keys input {
width:100%;
}
table.api_keys button {
border: 2px solid #321700;
background-color: #613600;
color:white;
padding:5px;
}
</style>
{% endblock %}
{% block content %}
<h1>API Keys</h1>
<table class="api_keys">
<thead>
<tr>
<th>API Key</th>
<th>Description</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{% for key in user.apikey_set.all %}
<form action="{% url "api_key_delete" key_id=key.id %}" method="post">
{% csrf_token %}
<tr>
<td>{{ key.key }}</td>
<td>{{ key.comment }}</td>
<td><button type="submit">Revoke</button></td>
</tr>
</form>
{% endfor %}
{% if user.apikey_set.all.count < max_keys %}
<form action="{% url "api_key_create" %}" method="post">
{% csrf_token %}
<tr>
<td colspan="2"><input name="comment"></td>
<td><button type="submit">Create</button></td>
</tr>
</form>
{% endif %}
</tbody>
</table>
{% endblock %}
from django.test import TestCase
# Create your tests here.
from django.urls import path
from . import views
urlpatterns = [
path('version', views.version, name='version'),
path('version/<int:version_id>', views.get_version, name='get_version'),
path('version/active', views.get_active_version, name='get_active_version'),
path('version/active/disable', views.disable_active_version, name='disable_active_version'),
path('version/<int:version_id>/disable', views.disable_version, name='disable_version'),
path('version/active/kill', views.kill_bot, name='kill_bot'),
path('version/activate/<int:version_id>', views.activate_version, name='activate_version'),
path('viewer_key', views.get_viewer_key, name='get_viewer_key'),
path('keys', views.list_api_keys, name='api_keys_list'),
path('keys/create', views.create_api_key, name='api_key_create'),
path('keys/delete/<int:key_id>', views.delete_api_key, name='api_key_delete'),
]
import json
from django.http import JsonResponse, HttpResponseBadRequest
from django.forms import ModelForm
from django.core.exceptions import PermissionDenied, SuspiciousOperation
from django.shortcuts import render, redirect, get_object_or_404
from django.contrib.auth.decorators import login_required
from django.views.decorators.http import require_http_methods
from core.models import ApiKey, SnakeVersion, ServerCommand, get_user_profile
def get_user(request):
try:
key = request.META.get('HTTP_AUTHORIZATION', None)
if key is not None:
return ApiKey.objects.get(key=key)
key = request.GET.get('token', None)
if key is not None:
return ApiKey.objects.get(key=key)
if request.user.is_authenticated:
return request.user
raise PermissionDenied('API access needs login or api key')
except ApiKey.DoesNotExist:
raise PermissionDenied('invalid API key')
def version_dict(v):
return {
'id': v.id,
'parent': None if v.parent is None else v.parent.id,
'version': v.version,
'created': v.created,
'comment': v.comment,
'error': v.server_error_message
}
def full_version_dict(v):
d = version_dict(v)
d['code'] = v.code
return d
@require_http_methods(['GET', 'POST', 'PUT'])
def version(request):
if request.method in ['PUT', 'POST']:
return put_version(request)
else:
return JsonResponse({'versions': [version_dict(v) for v in SnakeVersion.objects.filter(user=get_user(request))]})
@require_http_methods(['GET'])
def get_version(request, version_id):
user = get_user(request)
v = get_object_or_404(SnakeVersion, user=user, id=version_id)
return JsonResponse(full_version_dict(v))
@require_http_methods(['POST', 'PUT'])
def put_version(request):
user = get_user(request)
data = json.loads(request.body)
if not isinstance(data, dict):
return HttpResponseBadRequest('need to send a json dict as request body')
v = SnakeVersion()
v.user = user
v.parent = data.get('parent', None)
v.comment = data.get('comment', None)
v.code = data.get('code', None)
if v.code is None:
return HttpResponseBadRequest('need to provide lua script in code field');
v.save()
return get_version(request, version_id=v.id)
@require_http_methods(['GET'])
def get_active_version(request):
user = get_user(request)
up = get_user_profile(user)
v = up.active_snake
if v:
return get_version(request, version_id=v.id)
else:
return JsonResponse({})
@require_http_methods(['POST'])
def activate_version(request, version_id):
user = get_user(request)
v = get_object_or_404(SnakeVersion, user=user, id=version_id)
up = get_user_profile(user)
up.active_snake = v
up.save()
return JsonResponse(version_dict(v))
@require_http_methods(['POST'])
def disable_active_version(request):
user = get_user(request)
up = get_user_profile(user)
up.active_snake = None
up.save()
return JsonResponse({'result': 'ok'})
@require_http_methods(['POST'])
def disable_version(request, version_id):
user = get_user(request)
up = get_user_profile(user)
if up.active_snake is not None and up.active_snake.id == version_id:
up.active_snake = None
up.save()
return JsonResponse({'result': 'ok'})
@require_http_methods(['POST', 'DELETE'])
def kill_bot(request):
user = get_user(request)
ServerCommand(user=user, command='kill').save()
return JsonResponse({'result': 'ok'})
@require_http_methods(['GET'])
def get_viewer_key(request):
user = get_user(request)
up = get_user_profile(user)
return JsonResponse({'viewer_key': up.viewer_key})
@require_http_methods(['GET'])
@login_required()
def list_api_keys(request):
return render(request, 'api/list_api_keys.html', {
'user': request.user,
'max_keys': ApiKey.MAX_KEYS_PER_USER
})
class CreateKeyForm(ModelForm):
class Meta:
model = ApiKey
fields = ['comment']
@require_http_methods(['POST'])
@login_required()
def create_api_key(request):
if request.user.apikey_set.count() >= ApiKey.MAX_KEYS_PER_USER:
raise SuspiciousOperation
form = CreateKeyForm(request.POST or None)
if form.is_valid():
key = ApiKey(user=request.user)
key.comment = form.cleaned_data.get('comment', None)
key.save()
return redirect('api_keys_list')
@require_http_methods(['POST', 'DELETE'])
@login_required()
def delete_api_key(request, key_id):
key = get_object_or_404(ApiKey, user=request.user, id=key_id)
key.delete()
return redirect('api_keys_list')
......@@ -14,6 +14,6 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='userprofile',
name='viewer_key',
field=models.BigIntegerField(default=core.models.create_viewer_key, unique=True),
field=models.BigIntegerField(default=None, unique=True),
),
]
import random
import uuid
from django.db import models
from django.utils.timezone import now
from django.contrib.auth.models import User
def create_viewer_key():
return None
def get_user_profile(user):
profile, _ = UserProfile.objects.get_or_create(user=user)
return profile
class SnakeVersion(models.Model):
......@@ -88,7 +90,11 @@ class ServerCommand(models.Model):
result_msg = models.TextField(blank=True, null=True, editable=False)
def create_key():
return str(uuid.uuid4())
class ApiKey(models.Model):
MAX_KEYS_PER_USER = 20
user = models.ForeignKey(User, on_delete=models.CASCADE)
key = models.CharField(max_length=100)
comment = models.CharField(max_length=255)
key = models.CharField(max_length=100, default=create_key)
comment = models.CharField(max_length=255, null=True, blank=True)
......@@ -3,4 +3,8 @@
{% block content %}
<h1>Profile</h1>
<h3>API Keys</h3>
<form method="GET" action="{% url "api_keys_list" %}"><button type="submit">Manage API Keys</button></form>
<h3>Viewer Key</h3>
Websocket Viewer Key: {{ profile.viewer_key }}
{% endblock %}
from django.contrib.auth import login, authenticate
from django.contrib.auth.forms import UserCreationForm
from django.shortcuts import render, redirect
from django.contrib.auth.decorators import login_required
from core.models import get_user_profile
def signup(request):
if request.method == 'POST':
......@@ -18,5 +19,6 @@ def signup(request):
return render(request, 'signup.html', {'form': form})
@login_required
def profile(request):
return render(request, 'core/profile.html')
\ No newline at end of file
return render(request, 'core/profile.html', {'user': request.user, 'profile': get_user_profile(request.user)})
from datetime import datetime
import json
from django.contrib.auth.decorators import login_required
from django.core.exceptions import PermissionDenied
......@@ -7,12 +6,7 @@ from django.http import JsonResponse, HttpResponseBadRequest
from django.shortcuts import render, redirect, get_object_or_404
from django.template.loader import render_to_string
from django.views.decorators.http import require_POST
from core.models import SnakeVersion, ServerCommand, UserProfile
def get_user_profile(user):
profile, _ = UserProfile.objects.get_or_create(user=user)
return profile
from core.models import SnakeVersion, ServerCommand, get_user_profile
class CreateSnakeForm(ModelForm):
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment