diff --git a/docs/consumption.rst b/docs/consumption.rst index 280ba6d50..641aa196f 100644 --- a/docs/consumption.rst +++ b/docs/consumption.rst @@ -147,46 +147,83 @@ So, with all that in mind, here's what you do to get it running: HTTP POST ========= -You can also submit a document via HTTP POST. It doesn't do tags yet, and the -URL schema isn't concrete, but it's a start. - -To push your document to Paperless, send an HTTP POST to the server with the -following name/value pairs: +You can also submit a document via HTTP POST, so long as you do so after +authenticating. To push your document to Paperless, send an HTTP POST to the +server with the following name/value pairs: * ``correspondent``: The name of the document's correspondent. Note that there are restrictions on what characters you can use here. Specifically, - alphanumeric characters, `-`, `,`, `.`, and `'` are ok, everything else it + alphanumeric characters, `-`, `,`, `.`, and `'` are ok, everything else is out. You also can't use the sequence ` - ` (space, dash, space). * ``title``: The title of the document. The rules for characters is the same here as the correspondent. -* ``signature``: For security reasons, we have the correspondent send a - signature using a "shared secret" method to make sure that random strangers - don't start uploading stuff to your server. The means of generating this - signature is defined below. +* ``document``: The file you're uploading Specify ``enctype="multipart/form-data"``, and then POST your file with:: Content-Disposition: form-data; name="document"; filename="whatever.pdf" +An example of this in HTML is a typical form: -.. _consumption-http-signature: +.. code:: html -Generating the Signature ------------------------- +
+ + + + +
-Generating a signature based a shared secret is pretty simple: define a secret, -and store it on the server and the client. Then use that secret, along with -the text you want to verify to generate a string that you can use for -verification. - -In the case of Paperless, you configure the server with the secret by setting -``UPLOAD_SHARED_SECRET``. Then on your client, you generate your signature by -concatenating the correspondent, title, and the secret, and then using sha256 -to generate a hexdigest. - -If you're using Python, this is what that looks like: +But a potentially more useful way to do this would be in Python. Here we use +the requests library to handle basic authentication and to send the POST data +to the URL. .. code:: python + import os + from hashlib import sha256 - signature = sha256(correspondent + title + secret).hexdigest() + + import requests + from requests.auth import HTTPBasicAuth + + # You authenticate via BasicAuth or with a session id. + # We use BasicAuth here + username = "my-username" + password = "my-super-secret-password" + + # Where you have Paperless installed and listening + url = "http://localhost:8000/push" + + # Document metadata + correspondent = "Test Correspondent" + title = "Test Title" + + # The local file you want to push + path = "/path/to/some/directory/my-document.pdf" + + + with open(path, "rb") as f: + + response = requests.post( + url=url, + data={"title": title, "correspondent": correspondent}, + files={"document": (os.path.basename(path), f, "application/pdf")}, + auth=HTTPBasicAuth(username, password), + allow_redirects=False + ) + + if response.status_code == 202: + + # Everything worked out ok + print("Upload successful") + + else: + + # If you don't get a 202, it's probably because your credentials + # are wrong or something. This will give you a rough idea of what + # happened. + + print("We got HTTP status code: {}".format(response.status_code)) + for k, v in response.headers.items(): + print("{}: {}".format(k, v)) diff --git a/src/documents/forms.py b/src/documents/forms.py index 6bba3d55b..9c87642d9 100644 --- a/src/documents/forms.py +++ b/src/documents/forms.py @@ -2,7 +2,6 @@ import magic import os from datetime import datetime -from hashlib import sha256 from time import mktime from django import forms @@ -32,10 +31,9 @@ class UploadForm(forms.Form): required=False ) document = forms.FileField() - signature = forms.CharField(max_length=256) def __init__(self, *args, **kwargs): - forms.Form.__init__(*args, **kwargs) + forms.Form.__init__(self, *args, **kwargs) self._file_type = None def clean_correspondent(self): @@ -82,17 +80,6 @@ class UploadForm(forms.Form): return document - def clean(self): - - corresp = self.cleaned_data.get("correspondent") - title = self.cleaned_data.get("title") - signature = self.cleaned_data.get("signature") - - if sha256(corresp + title + self.SECRET).hexdigest() == signature: - return self.cleaned_data - - raise forms.ValidationError("The signature provided did not validate") - def save(self): """ Since the consumer already does a lot of work, it's easier just to save @@ -104,7 +91,7 @@ class UploadForm(forms.Form): title = self.cleaned_data.get("title") document = self.cleaned_data.get("document") - t = int(mktime(datetime.now())) + t = int(mktime(datetime.now().timetuple())) file_name = os.path.join( Consumer.CONSUME, "{} - {}.{}".format(correspondent, title, self._file_type) diff --git a/src/documents/views.py b/src/documents/views.py index b57a0a571..ab157a72f 100644 --- a/src/documents/views.py +++ b/src/documents/views.py @@ -1,5 +1,4 @@ -from django.http import HttpResponse -from django.views.decorators.csrf import csrf_exempt +from django.http import HttpResponse, HttpResponseBadRequest from django.views.generic import DetailView, FormView, TemplateView from django_filters.rest_framework import DjangoFilterBackend from paperless.db import GnuPG @@ -81,15 +80,12 @@ class PushView(SessionOrBasicAuthMixin, FormView): form_class = UploadForm - @classmethod - def as_view(cls, **kwargs): - return csrf_exempt(FormView.as_view(**kwargs)) - def form_valid(self, form): - return HttpResponse("1") + form.save() + return HttpResponse("1", status=202) def form_invalid(self, form): - return HttpResponse("0") + return HttpResponseBadRequest(str(form.errors)) class CorrespondentViewSet(ModelViewSet): diff --git a/src/paperless/urls.py b/src/paperless/urls.py index 8348eef4f..4128064b7 100644 --- a/src/paperless/urls.py +++ b/src/paperless/urls.py @@ -1,27 +1,12 @@ -"""paperless URL Configuration - -The `urlpatterns` list routes URLs to views. For more information please see: - https://docs.djangoproject.com/en/1.10/topics/http/urls/ -Examples: -Function views - 1. Add an import: from my_app import views - 2. Add a URL to urlpatterns: url(r'^$', views.home, name='home') -Class-based views - 1. Add an import: from other_app.views import Home - 2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home') -Including another URLconf - 1. Add an import: from blog import urls as blog_urls - 2. Import the include() function: from django.conf.urls import url, include - 3. Add a URL to urlpatterns: url(r'^blog/', include(blog_urls)) -""" from django.conf import settings from django.conf.urls import url, static, include from django.contrib import admin +from django.views.decorators.csrf import csrf_exempt from rest_framework.routers import DefaultRouter from documents.views import ( - IndexView, FetchView, PushView, + FetchView, PushView, CorrespondentViewSet, TagViewSet, DocumentViewSet, LogViewSet ) from reminders.views import ReminderViewSet @@ -42,9 +27,6 @@ urlpatterns = [ ), url(r"^api/", include(router.urls, namespace="drf")), - # Normal pages (coming soon) - # url(r"^$", IndexView.as_view(), name="index"), - # File downloads url( r"^fetch/(?Pdoc|thumb)/(?P\d+)$", @@ -59,7 +41,10 @@ urlpatterns = [ ] + static.static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) if settings.SHARED_SECRET: - urlpatterns.insert(0, url(r"^push$", PushView.as_view(), name="push")) + urlpatterns.insert( + 0, + url(r"^push$", csrf_exempt(PushView.as_view()), name="push") + ) # Text in each page's

(and above login form). admin.site.site_header = 'Paperless'