diff --git a/paste/admin.py b/paste/admin.py index e8b89d2..4aab367 100644 --- a/paste/admin.py +++ b/paste/admin.py @@ -12,12 +12,14 @@ class PasteAdmin(admin.ModelAdmin): "paste_time", "access_time", "viewcount", + "auth", ) fields = ( ( "slug", "size", ), + "auth", ( "paste_time", "access_time", diff --git a/paste/migrations/0009_paste_auth.py b/paste/migrations/0009_paste_auth.py new file mode 100644 index 0000000..ff52c43 --- /dev/null +++ b/paste/migrations/0009_paste_auth.py @@ -0,0 +1,17 @@ +# Generated by Django 4.2 on 2023-04-25 09:33 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("paste", "0008_paste_access_time"), + ] + + operations = [ + migrations.AddField( + model_name="paste", + name="auth", + field=models.CharField(default="", max_length=64), + ), + ] diff --git a/paste/models.py b/paste/models.py index eb7563f..70616e3 100644 --- a/paste/models.py +++ b/paste/models.py @@ -1,4 +1,5 @@ from datetime import datetime, timedelta +from hashlib import sha256 import shortuuid from django.core.validators import MaxLengthValidator @@ -10,9 +11,16 @@ from django.utils.translation import gettext_lazy as _ from webtools import settings +class PasteQuerySet(models.QuerySet): + def by_secret(self, secret): + auth = sha256(secret.encode("UTF-8")).hexdigest() + return self.filter(auth=auth) + + class Paste(models.Model): """Paste object.""" + objects = PasteQuerySet.as_manager() filename = models.CharField(max_length=255, default="") slug = models.SlugField(unique=True, editable=False) content = models.TextField( @@ -23,6 +31,14 @@ class Paste(models.Model): access_time = models.DateTimeField(auto_now=True) viewcount = models.IntegerField(default=0, editable=False) + # auth stores a sha256 (hexdigest) of the Authentication header. + auth = models.CharField(max_length=64, default="") + + def set_secret(self, secret=None): + if not secret: + return + self.auth = sha256(secret.encode("UTF-8")).hexdigest() + def compute_size(self): """Computes size.""" self.size = len(self.content) diff --git a/paste/urls.py b/paste/urls.py index 3afeb2f..353f10c 100644 --- a/paste/urls.py +++ b/paste/urls.py @@ -7,5 +7,6 @@ from webtools import settings urlpatterns = [ path("", views.index, name="index"), path("::/static/", serve, {"document_root": settings.STATIC_ROOT}), + path("::/list/", views.list_view), path("", views.show, name="short_paste"), ] diff --git a/paste/views.py b/paste/views.py index 954c140..d5147c5 100644 --- a/paste/views.py +++ b/paste/views.py @@ -1,3 +1,4 @@ +from datetime import datetime from functools import lru_cache from mimetypes import common_types, types_map @@ -29,6 +30,21 @@ def get_files(request): return {"": request} +def pastes_as_table(request, pastes, headers=("URL", "size", "filename")): + def paste_attr(paste, attr): + if attr == "URL": + return request.build_absolute_uri(paste.get_absolute_url()) + value = getattr(paste, attr) + if isinstance(value, datetime): + return value.isoformat(timespec="seconds") + return value + + values = [] + for paste in pastes: + values.append([paste_attr(paste, attr) for attr in headers]) + return tabulate(values, headers=headers, tablefmt="github") + "\n" + + @csrf_exempt def index(request): """Displays form.""" @@ -46,22 +62,32 @@ def index(request): filename=filename.split("/")[-1], content=the_file.read().decode("UTF-8"), ) + paste.set_secret(request.headers.get("Authorization")) paste.compute_size() paste.save() pastes.append(paste) - table = [ - ( - request.build_absolute_uri(paste.get_absolute_url()), - paste.size, - paste.filename, - ) - for paste in pastes - ] - return HttpResponse( - tabulate(table, headers=("URL", "size", "filename"), tablefmt="github") + "\n", - content_type="text/plain", + return HttpResponse(pastes_as_table(request, pastes), content_type="text/plain") + + +def list_view(request): + secret = request.headers.get("Authorization") + pastes = [] + if secret: + pastes = Paste.objects.by_secret(secret).order_by("paste_time") + table = pastes_as_table( + request, + pastes, + headers=("filename", "size", "URL", "paste_time", "access_time"), ) + if "html" in request.headers["accept"]: + return HttpResponse( + loader.render_to_string( + "paste/show-markdown.html", {"highlighted": markdown_to_html(table)} + ) + ) + else: + return HttpResponse(table, content_type="text/plain") def show(request, slug):