mirror of
				https://github.com/paperless-ngx/paperless-ngx.git
				synced 2025-11-03 19:17:13 -05:00 
			
		
		
		
	
						commit
						8417ac7eeb
					
				@ -147,46 +147,83 @@ So, with all that in mind, here's what you do to get it running:
 | 
				
			|||||||
HTTP POST
 | 
					HTTP POST
 | 
				
			||||||
=========
 | 
					=========
 | 
				
			||||||
 | 
					
 | 
				
			||||||
You can also submit a document via HTTP POST.  It doesn't do tags yet, and the
 | 
					You can also submit a document via HTTP POST, so long as you do so after
 | 
				
			||||||
URL schema isn't concrete, but it's a start.
 | 
					authenticating.  To push your document to Paperless, send an HTTP POST to the
 | 
				
			||||||
 | 
					server with the following name/value pairs:
 | 
				
			||||||
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
 | 
					* ``correspondent``: The name of the document's correspondent.  Note that there
 | 
				
			||||||
  are restrictions on what characters you can use here.  Specifically,
 | 
					  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).
 | 
					  out.  You also can't use the sequence ` - ` (space, dash, space).
 | 
				
			||||||
* ``title``: The title of the document.  The rules for characters is the same
 | 
					* ``title``: The title of the document.  The rules for characters is the same
 | 
				
			||||||
  here as the correspondent.
 | 
					  here as the correspondent.
 | 
				
			||||||
* ``signature``: For security reasons, we have the correspondent send a
 | 
					* ``document``: The file you're uploading
 | 
				
			||||||
  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.
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
Specify ``enctype="multipart/form-data"``, and then POST your file with::
 | 
					Specify ``enctype="multipart/form-data"``, and then POST your file with::
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Content-Disposition: form-data; name="document"; filename="whatever.pdf"
 | 
					    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
 | 
					    <form method="post" enctype="multipart/form-data">
 | 
				
			||||||
------------------------
 | 
					        <input type="text" name="correspondent" value="My Correspondent" />
 | 
				
			||||||
 | 
					        <input type="text" name="title" value="My Title" />
 | 
				
			||||||
 | 
					        <input type="file" name="document" />
 | 
				
			||||||
 | 
					        <input type="submit" name="go" value="Do the thing" />
 | 
				
			||||||
 | 
					    </form>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Generating a signature based a shared secret is pretty simple: define a secret,
 | 
					But a potentially more useful way to do this would be in Python.  Here we use
 | 
				
			||||||
and store it on the server and the client.  Then use that secret, along with
 | 
					the requests library to handle basic authentication and to send the POST data
 | 
				
			||||||
the text you want to verify to generate a string that you can use for
 | 
					to the URL.
 | 
				
			||||||
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:
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
.. code:: python
 | 
					.. code:: python
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    import os
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    from hashlib import sha256
 | 
					    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))
 | 
				
			||||||
 | 
				
			|||||||
@ -2,7 +2,6 @@ import magic
 | 
				
			|||||||
import os
 | 
					import os
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from datetime import datetime
 | 
					from datetime import datetime
 | 
				
			||||||
from hashlib import sha256
 | 
					 | 
				
			||||||
from time import mktime
 | 
					from time import mktime
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from django import forms
 | 
					from django import forms
 | 
				
			||||||
@ -32,10 +31,9 @@ class UploadForm(forms.Form):
 | 
				
			|||||||
        required=False
 | 
					        required=False
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
    document = forms.FileField()
 | 
					    document = forms.FileField()
 | 
				
			||||||
    signature = forms.CharField(max_length=256)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __init__(self, *args, **kwargs):
 | 
					    def __init__(self, *args, **kwargs):
 | 
				
			||||||
        forms.Form.__init__(*args, **kwargs)
 | 
					        forms.Form.__init__(self, *args, **kwargs)
 | 
				
			||||||
        self._file_type = None
 | 
					        self._file_type = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def clean_correspondent(self):
 | 
					    def clean_correspondent(self):
 | 
				
			||||||
@ -82,17 +80,6 @@ class UploadForm(forms.Form):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        return document
 | 
					        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):
 | 
					    def save(self):
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        Since the consumer already does a lot of work, it's easier just to save
 | 
					        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")
 | 
					        title = self.cleaned_data.get("title")
 | 
				
			||||||
        document = self.cleaned_data.get("document")
 | 
					        document = self.cleaned_data.get("document")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        t = int(mktime(datetime.now()))
 | 
					        t = int(mktime(datetime.now().timetuple()))
 | 
				
			||||||
        file_name = os.path.join(
 | 
					        file_name = os.path.join(
 | 
				
			||||||
            Consumer.CONSUME,
 | 
					            Consumer.CONSUME,
 | 
				
			||||||
            "{} - {}.{}".format(correspondent, title, self._file_type)
 | 
					            "{} - {}.{}".format(correspondent, title, self._file_type)
 | 
				
			||||||
 | 
				
			|||||||
@ -1,5 +1,4 @@
 | 
				
			|||||||
from django.http import HttpResponse
 | 
					from django.http import HttpResponse, HttpResponseBadRequest
 | 
				
			||||||
from django.views.decorators.csrf import csrf_exempt
 | 
					 | 
				
			||||||
from django.views.generic import DetailView, FormView, TemplateView
 | 
					from django.views.generic import DetailView, FormView, TemplateView
 | 
				
			||||||
from django_filters.rest_framework import DjangoFilterBackend
 | 
					from django_filters.rest_framework import DjangoFilterBackend
 | 
				
			||||||
from paperless.db import GnuPG
 | 
					from paperless.db import GnuPG
 | 
				
			||||||
@ -81,15 +80,12 @@ class PushView(SessionOrBasicAuthMixin, FormView):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    form_class = UploadForm
 | 
					    form_class = UploadForm
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @classmethod
 | 
					 | 
				
			||||||
    def as_view(cls, **kwargs):
 | 
					 | 
				
			||||||
        return csrf_exempt(FormView.as_view(**kwargs))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def form_valid(self, form):
 | 
					    def form_valid(self, form):
 | 
				
			||||||
        return HttpResponse("1")
 | 
					        form.save()
 | 
				
			||||||
 | 
					        return HttpResponse("1", status=202)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def form_invalid(self, form):
 | 
					    def form_invalid(self, form):
 | 
				
			||||||
        return HttpResponse("0")
 | 
					        return HttpResponseBadRequest(str(form.errors))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class CorrespondentViewSet(ModelViewSet):
 | 
					class CorrespondentViewSet(ModelViewSet):
 | 
				
			||||||
 | 
				
			|||||||
@ -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 import settings
 | 
				
			||||||
from django.conf.urls import url, static, include
 | 
					from django.conf.urls import url, static, include
 | 
				
			||||||
from django.contrib import admin
 | 
					from django.contrib import admin
 | 
				
			||||||
 | 
					from django.views.decorators.csrf import csrf_exempt
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from rest_framework.routers import DefaultRouter
 | 
					from rest_framework.routers import DefaultRouter
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from documents.views import (
 | 
					from documents.views import (
 | 
				
			||||||
    IndexView, FetchView, PushView,
 | 
					    FetchView, PushView,
 | 
				
			||||||
    CorrespondentViewSet, TagViewSet, DocumentViewSet, LogViewSet
 | 
					    CorrespondentViewSet, TagViewSet, DocumentViewSet, LogViewSet
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
from reminders.views import ReminderViewSet
 | 
					from reminders.views import ReminderViewSet
 | 
				
			||||||
@ -42,9 +27,6 @@ urlpatterns = [
 | 
				
			|||||||
    ),
 | 
					    ),
 | 
				
			||||||
    url(r"^api/", include(router.urls, namespace="drf")),
 | 
					    url(r"^api/", include(router.urls, namespace="drf")),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Normal pages (coming soon)
 | 
					 | 
				
			||||||
    # url(r"^$", IndexView.as_view(), name="index"),
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    # File downloads
 | 
					    # File downloads
 | 
				
			||||||
    url(
 | 
					    url(
 | 
				
			||||||
        r"^fetch/(?P<kind>doc|thumb)/(?P<pk>\d+)$",
 | 
					        r"^fetch/(?P<kind>doc|thumb)/(?P<pk>\d+)$",
 | 
				
			||||||
@ -59,7 +41,10 @@ urlpatterns = [
 | 
				
			|||||||
] + static.static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
 | 
					] + static.static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
if settings.SHARED_SECRET:
 | 
					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 <h1> (and above login form).
 | 
					# Text in each page's <h1> (and above login form).
 | 
				
			||||||
admin.site.site_header = 'Paperless'
 | 
					admin.site.site_header = 'Paperless'
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user