mirror of
				https://github.com/paperless-ngx/paperless-ngx.git
				synced 2025-10-30 03:56:23 -05:00 
			
		
		
		
	Removed WebDAV from dev, since it is kind of broken.
This commit is contained in:
		| @@ -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>. | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Jonas Winkler
					Jonas Winkler