mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2025-04-15 10:13:15 -05:00
Removed WebDAV from dev, since it is kind of broken.
This commit is contained in:
parent
d7ab69fed9
commit
01fed4f49d
@ -204,6 +204,3 @@ PAPERLESS_EMAIL_SECRET=""
|
|||||||
# 100 will be used.
|
# 100 will be used.
|
||||||
#PAPERLESS_LIST_PER_PAGE=100
|
#PAPERLESS_LIST_PER_PAGE=100
|
||||||
|
|
||||||
|
|
||||||
# Enable WebDAV support for Paperless. Default is false.
|
|
||||||
#PAPERLESS_ENABLE_WEBDAV="true"
|
|
||||||
|
@ -10,7 +10,6 @@ django-extensions==2.0.7
|
|||||||
django-filter==1.1.0
|
django-filter==1.1.0
|
||||||
django-flat-responsive==2.0
|
django-flat-responsive==2.0
|
||||||
django==2.0.7
|
django==2.0.7
|
||||||
djangodav==0.0.1b26
|
|
||||||
djangorestframework==3.8.2
|
djangorestframework==3.8.2
|
||||||
docopt==0.6.2
|
docopt==0.6.2
|
||||||
execnet==1.5.0
|
execnet==1.5.0
|
||||||
|
@ -1,275 +0,0 @@
|
|||||||
import base64
|
|
||||||
import binascii
|
|
||||||
import os
|
|
||||||
from functools import wraps
|
|
||||||
from urllib.parse import unquote_plus
|
|
||||||
|
|
||||||
from django.http import HttpResponse
|
|
||||||
from django.utils.decorators import method_decorator
|
|
||||||
from django.contrib.auth import authenticate
|
|
||||||
|
|
||||||
from djangodav.base.resources import MetaEtagMixIn, BaseDavResource
|
|
||||||
from djangodav.utils import url_join
|
|
||||||
from djangodav.views import DavView
|
|
||||||
|
|
||||||
from documents.models import Tag, Document, Correspondent
|
|
||||||
|
|
||||||
|
|
||||||
def extract_basicauth(authorization_header, encoding='utf-8'):
|
|
||||||
splitted = authorization_header.split(' ')
|
|
||||||
if len(splitted) != 2:
|
|
||||||
return None
|
|
||||||
|
|
||||||
auth_type, auth_string = splitted
|
|
||||||
|
|
||||||
if 'basic' != auth_type.lower():
|
|
||||||
return None
|
|
||||||
|
|
||||||
try:
|
|
||||||
b64_decoded = base64.b64decode(auth_string)
|
|
||||||
except (TypeError, binascii.Error):
|
|
||||||
return None
|
|
||||||
try:
|
|
||||||
auth_string_decoded = b64_decoded.decode(encoding)
|
|
||||||
except UnicodeDecodeError:
|
|
||||||
return None
|
|
||||||
|
|
||||||
splitted = auth_string_decoded.split(':')
|
|
||||||
|
|
||||||
if len(splitted) != 2:
|
|
||||||
return None
|
|
||||||
|
|
||||||
username, password = map(unquote_plus, splitted)
|
|
||||||
return username, password
|
|
||||||
|
|
||||||
|
|
||||||
def validate_request(request):
|
|
||||||
|
|
||||||
if 'HTTP_AUTHORIZATION' not in request.META:
|
|
||||||
return False
|
|
||||||
|
|
||||||
authorization_header = request.META['HTTP_AUTHORIZATION']
|
|
||||||
ret = extract_basicauth(authorization_header)
|
|
||||||
if not ret:
|
|
||||||
return False
|
|
||||||
|
|
||||||
username, password = ret
|
|
||||||
|
|
||||||
user = authenticate(username=username, password=password)
|
|
||||||
|
|
||||||
if user is None:
|
|
||||||
return False
|
|
||||||
|
|
||||||
request.META['REMOTE_USER'] = username
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
class HttpResponseUnauthorized(HttpResponse):
|
|
||||||
status_code = 401
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
super(HttpResponseUnauthorized, self).__init__(
|
|
||||||
"""<html><head><title>Basic auth required</title></head>
|
|
||||||
<body><h1>Authorization Required</h1></body></html>""",
|
|
||||||
)
|
|
||||||
realm = 'Paperless WebDAV'
|
|
||||||
self['WWW-Authenticate'] = 'Basic realm="{}"'.format(realm)
|
|
||||||
|
|
||||||
|
|
||||||
def basic_auth_required(func=None,
|
|
||||||
target_test=(lambda request: True)):
|
|
||||||
def actual_decorator(view_func):
|
|
||||||
@wraps(view_func)
|
|
||||||
def _wrapped(request, *args, **kwargs):
|
|
||||||
if target_test(request) and not validate_request(request):
|
|
||||||
return HttpResponseUnauthorized()
|
|
||||||
return view_func(request, *args, **kwargs)
|
|
||||||
return _wrapped
|
|
||||||
|
|
||||||
if func:
|
|
||||||
return actual_decorator(func)
|
|
||||||
else:
|
|
||||||
return actual_decorator
|
|
||||||
|
|
||||||
#@method_decorator(basic_auth_required, name='dispatch')
|
|
||||||
class SecuredDavView(DavView):
|
|
||||||
pass
|
|
||||||
|
|
||||||
class PaperlessDavResource(MetaEtagMixIn, BaseDavResource):
|
|
||||||
|
|
||||||
document = None
|
|
||||||
_exists = True
|
|
||||||
|
|
||||||
def __init__(self, path, **kwargs):
|
|
||||||
super(PaperlessDavResource, self).__init__(path)
|
|
||||||
if 'document' in kwargs:
|
|
||||||
# this greatly reduces the amount of database requests.
|
|
||||||
self.document = kwargs.pop('document')
|
|
||||||
else:
|
|
||||||
self._exists, self.documents, self.document, self.children = parse_path(path)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def getcontentlength(self):
|
|
||||||
if self.document:
|
|
||||||
return os.path.getsize(self.document.source_path)
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
def get_created(self):
|
|
||||||
"""Return the create time as datetime object."""
|
|
||||||
if self.document:
|
|
||||||
return self.document.created
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
def get_modified(self):
|
|
||||||
if self.document:
|
|
||||||
return self.document.modified
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
@property
|
|
||||||
def is_collection(self):
|
|
||||||
return self.exists and not self.document
|
|
||||||
|
|
||||||
@property
|
|
||||||
def is_object(self):
|
|
||||||
return self.exists and self.document
|
|
||||||
|
|
||||||
@property
|
|
||||||
def exists(self):
|
|
||||||
return self._exists
|
|
||||||
|
|
||||||
def get_children(self):
|
|
||||||
if not self.document:
|
|
||||||
for child in self.children:
|
|
||||||
yield self.clone(url_join(*(self.path + [child])))
|
|
||||||
|
|
||||||
for doc in self.documents:
|
|
||||||
yield self.clone(url_join(*(self.path + [doc.file_name])), document=doc)
|
|
||||||
|
|
||||||
def write(self, content, temp_file=None):
|
|
||||||
raise NotImplementedError()
|
|
||||||
|
|
||||||
def read(self):
|
|
||||||
return self.document.source_file
|
|
||||||
|
|
||||||
def delete(self):
|
|
||||||
raise NotImplementedError()
|
|
||||||
|
|
||||||
def create_collection(self):
|
|
||||||
raise NotImplementedError()
|
|
||||||
|
|
||||||
def copy_object(self, destination, depth=0):
|
|
||||||
raise NotImplementedError()
|
|
||||||
|
|
||||||
def move_object(self, destination):
|
|
||||||
raise NotImplementedError()
|
|
||||||
|
|
||||||
def parse_path(path):
|
|
||||||
"""
|
|
||||||
This method serves multiple purposes:
|
|
||||||
1. validate the path and ensure that it valid (i.e., conforms to the specification provided above).
|
|
||||||
2. provide a database filter that returns a set of documents to be displayed, applying filters if necessary.
|
|
||||||
3. provide a set of "folders" that act as filters to narrow down the list of documents.
|
|
||||||
|
|
||||||
This is achieved by implementing a state machine. This machine processes the path segment by segment and switches
|
|
||||||
states as the path is processed. Depending on the state, only certain path segments are allowed
|
|
||||||
:param path:
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
used_tags = []
|
|
||||||
correspondent_selected = False
|
|
||||||
year_selected = False
|
|
||||||
month_selected = False
|
|
||||||
day_selected = False
|
|
||||||
show_documents = False
|
|
||||||
|
|
||||||
def get_filter_children(is_root=False):
|
|
||||||
filters = []
|
|
||||||
if is_root:
|
|
||||||
filters.append("show_all_documents")
|
|
||||||
if not year_selected:
|
|
||||||
filters.append('year')
|
|
||||||
elif not month_selected:
|
|
||||||
filters.append('month')
|
|
||||||
elif not day_selected:
|
|
||||||
filters.append('day')
|
|
||||||
if not correspondent_selected:
|
|
||||||
filters.append('correspondent')
|
|
||||||
#TODO: this should probably not get displayed if the resulting list of tags is empty, but it would result in even more database queries.
|
|
||||||
filters.append('tag')
|
|
||||||
return filters
|
|
||||||
|
|
||||||
path_queue = [x for x in path.split('/') if x]
|
|
||||||
|
|
||||||
filter = Document.objects.all()
|
|
||||||
children = get_filter_children(True)
|
|
||||||
document = None
|
|
||||||
exists = True
|
|
||||||
|
|
||||||
current_state = 'select_filter'
|
|
||||||
|
|
||||||
while len(path_queue) > 0:
|
|
||||||
path_segment = path_queue.pop(0)
|
|
||||||
show_documents = False
|
|
||||||
children = []
|
|
||||||
next_state = ''
|
|
||||||
|
|
||||||
if current_state == 'select_filter' and path_segment == 'year':
|
|
||||||
next_state = 'select_year'
|
|
||||||
children = [str(d.year) for d in filter.dates('created', 'year')]
|
|
||||||
elif current_state == 'select_filter' and path_segment == 'month':
|
|
||||||
next_state = 'select_month'
|
|
||||||
children = [str(d.month) for d in filter.dates('created', 'month')]
|
|
||||||
elif current_state == 'select_filter' and path_segment == 'day':
|
|
||||||
next_state = 'select_day'
|
|
||||||
children = [str(d.day) for d in filter.dates('created', 'day')]
|
|
||||||
elif current_state == 'select_filter' and path_segment == 'correspondent':
|
|
||||||
next_state = 'select_correspondent'
|
|
||||||
children = [c.name for c in Correspondent.objects.filter(documents__in=filter).distinct()]
|
|
||||||
elif current_state == 'select_filter' and path_segment == 'tag':
|
|
||||||
next_state = 'select_tag'
|
|
||||||
children = [t.name for t in Tag.objects.filter(documents__in=filter).distinct() if t.name not in used_tags]
|
|
||||||
elif current_state == 'select_filter' and path_segment == 'show_all_documents':
|
|
||||||
show_documents = True
|
|
||||||
elif current_state == 'select_tag':
|
|
||||||
next_state = 'select_filter'
|
|
||||||
filter = filter.filter(tags__name=path_segment)
|
|
||||||
used_tags.append(path_segment)
|
|
||||||
children = get_filter_children()
|
|
||||||
show_documents = True
|
|
||||||
elif current_state == 'select_correspondent':
|
|
||||||
next_state = 'select_filter'
|
|
||||||
filter = filter.filter(correspondent__name=path_segment)
|
|
||||||
correspondent_selected = True
|
|
||||||
children = get_filter_children()
|
|
||||||
show_documents = True
|
|
||||||
elif current_state == 'select_year':
|
|
||||||
next_state = 'select_filter'
|
|
||||||
filter = filter.filter(created__year=path_segment)
|
|
||||||
year_selected = True
|
|
||||||
children = get_filter_children()
|
|
||||||
show_documents = True
|
|
||||||
elif current_state == 'select_month':
|
|
||||||
next_state = 'select_filter'
|
|
||||||
filter = filter.filter(created__month=path_segment)
|
|
||||||
month_selected = True
|
|
||||||
children = get_filter_children()
|
|
||||||
show_documents = True
|
|
||||||
elif current_state == 'select_day':
|
|
||||||
next_state = 'select_filter'
|
|
||||||
filter = filter.filter(created__day=path_segment)
|
|
||||||
day_selected = True
|
|
||||||
children = get_filter_children()
|
|
||||||
show_documents = True
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
#TODO: this is pretty slow and sketchy.
|
|
||||||
document = [d for d in Document.objects.all() if d.file_name == path_segment][0]
|
|
||||||
except IndexError:
|
|
||||||
exists = False
|
|
||||||
|
|
||||||
current_state = next_state
|
|
||||||
|
|
||||||
return exists, filter if show_documents else [], document, children
|
|
@ -72,9 +72,7 @@ INSTALLED_APPS = [
|
|||||||
|
|
||||||
"rest_framework",
|
"rest_framework",
|
||||||
"crispy_forms",
|
"crispy_forms",
|
||||||
"django_filters",
|
"django_filters"
|
||||||
|
|
||||||
"djangodav"
|
|
||||||
|
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -280,5 +278,3 @@ FY_END = os.getenv("PAPERLESS_FINANCIAL_YEAR_END")
|
|||||||
|
|
||||||
# Specify the default date order (for autodetected dates)
|
# Specify the default date order (for autodetected dates)
|
||||||
DATE_ORDER = os.getenv("PAPERLESS_DATE_ORDER", "DMY")
|
DATE_ORDER = os.getenv("PAPERLESS_DATE_ORDER", "DMY")
|
||||||
|
|
||||||
ENABLE_WEBDAV = bool(os.getenv("PAPERLESS_ENABLE_WEBDAV", "NO").lower() in ("yes", "y", "1", "t", "true"))
|
|
||||||
|
@ -6,9 +6,6 @@ from django.views.decorators.csrf import csrf_exempt
|
|||||||
from django.views.generic import RedirectView
|
from django.views.generic import RedirectView
|
||||||
from rest_framework.routers import DefaultRouter
|
from rest_framework.routers import DefaultRouter
|
||||||
|
|
||||||
from djangodav.acls import FullAcl
|
|
||||||
from djangodav.locks import DummyLock
|
|
||||||
|
|
||||||
from documents.views import (
|
from documents.views import (
|
||||||
CorrespondentViewSet,
|
CorrespondentViewSet,
|
||||||
DocumentViewSet,
|
DocumentViewSet,
|
||||||
@ -17,7 +14,6 @@ from documents.views import (
|
|||||||
PushView,
|
PushView,
|
||||||
TagViewSet
|
TagViewSet
|
||||||
)
|
)
|
||||||
from paperless.dav import PaperlessDavResource, SecuredDavView
|
|
||||||
from reminders.views import ReminderViewSet
|
from reminders.views import ReminderViewSet
|
||||||
|
|
||||||
router = DefaultRouter()
|
router = DefaultRouter()
|
||||||
@ -56,9 +52,6 @@ urlpatterns = [
|
|||||||
|
|
||||||
] + static.static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
] + static.static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||||
|
|
||||||
if settings.ENABLE_WEBDAV:
|
|
||||||
urlpatterns.append(url(r'^dav(?P<path>.*)$', SecuredDavView.as_view(resource_class=PaperlessDavResource, lock_class=DummyLock, acl_class=FullAcl)))
|
|
||||||
|
|
||||||
# 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'
|
||||||
# Text at the end of each page's <title>.
|
# Text at the end of each page's <title>.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user