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 = [ ...@@ -35,7 +35,8 @@ INSTALLED_APPS = [
'visualization', 'visualization',
'ide', 'ide',
'highscore', 'highscore',
'docs' 'docs',
'api'
] ]
MIDDLEWARE = [ MIDDLEWARE = [
......
...@@ -18,4 +18,5 @@ urlpatterns = [ ...@@ -18,4 +18,5 @@ urlpatterns = [
path('snake/', include('ide.urls')), path('snake/', include('ide.urls')),
path('highscore/', include('highscore.urls')), path('highscore/', include('highscore.urls')),
path('docs/', include('docs.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): ...@@ -14,6 +14,6 @@ class Migration(migrations.Migration):
migrations.AddField( migrations.AddField(
model_name='userprofile', model_name='userprofile',
name='viewer_key', name='viewer_key',
field=models.BigIntegerField(default=core.models.create_viewer_key, unique=True), field=models.BigIntegerField(default=None, unique=True),
), ),
] ]
import random import random
import uuid
from django.db import models from django.db import models
from django.utils.timezone import now from django.utils.timezone import now
from django.contrib.auth.models import User from django.contrib.auth.models import User
def create_viewer_key(): def get_user_profile(user):
return None profile, _ = UserProfile.objects.get_or_create(user=user)
return profile
class SnakeVersion(models.Model): class SnakeVersion(models.Model):
...@@ -88,7 +90,11 @@ class ServerCommand(models.Model): ...@@ -88,7 +90,11 @@ class ServerCommand(models.Model):
result_msg = models.TextField(blank=True, null=True, editable=False) result_msg = models.TextField(blank=True, null=True, editable=False)
def create_key():
return str(uuid.uuid4())
class ApiKey(models.Model): class ApiKey(models.Model):
MAX_KEYS_PER_USER = 20
user = models.ForeignKey(User, on_delete=models.CASCADE) user = models.ForeignKey(User, on_delete=models.CASCADE)
key = models.CharField(max_length=100) key = models.CharField(max_length=100, default=create_key)
comment = models.CharField(max_length=255) comment = models.CharField(max_length=255, null=True, blank=True)
...@@ -3,4 +3,8 @@ ...@@ -3,4 +3,8 @@
{% block content %} {% block content %}
<h1>Profile</h1> <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 %} {% endblock %}
from django.contrib.auth import login, authenticate from django.contrib.auth import login, authenticate
from django.contrib.auth.forms import UserCreationForm from django.contrib.auth.forms import UserCreationForm
from django.shortcuts import render, redirect from django.shortcuts import render, redirect
from django.contrib.auth.decorators import login_required
from core.models import get_user_profile
def signup(request): def signup(request):
if request.method == 'POST': if request.method == 'POST':
...@@ -18,5 +19,6 @@ def signup(request): ...@@ -18,5 +19,6 @@ def signup(request):
return render(request, 'signup.html', {'form': form}) return render(request, 'signup.html', {'form': form})
@login_required
def profile(request): def profile(request):
return render(request, 'core/profile.html') return render(request, 'core/profile.html', {'user': request.user, 'profile': get_user_profile(request.user)})
\ No newline at end of file
from datetime import datetime
import json import json
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
...@@ -7,12 +6,7 @@ from django.http import JsonResponse, HttpResponseBadRequest ...@@ -7,12 +6,7 @@ from django.http import JsonResponse, HttpResponseBadRequest
from django.shortcuts import render, redirect, get_object_or_404 from django.shortcuts import render, redirect, get_object_or_404
from django.template.loader import render_to_string from django.template.loader import render_to_string
from django.views.decorators.http import require_POST from django.views.decorators.http import require_POST
from core.models import SnakeVersion, ServerCommand, UserProfile from core.models import SnakeVersion, ServerCommand, get_user_profile
def get_user_profile(user):
profile, _ = UserProfile.objects.get_or_create(user=user)
return profile
class CreateSnakeForm(ModelForm): class CreateSnakeForm(ModelForm):
......
Supports Markdown
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