Merge branch 'dev' into feature-better-bs-icons

This commit is contained in:
shamoon 2024-01-17 16:19:41 -08:00 committed by GitHub
commit 3538f362a3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 141 additions and 29 deletions

View File

@ -139,7 +139,7 @@ document. Paperless only reports PDF metadata at this point.
## Authorization
The REST api provides three different forms of authentication.
The REST api provides four different forms of authentication.
1. Basic authentication
@ -177,6 +177,12 @@ The REST api provides three different forms of authentication.
Tokens can also be managed in the Django admin.
4. Remote User authentication
If already setup (see
[configuration](configuration.md#PAPERLESS_ENABLE_HTTP_REMOTE_USER)),
you can authenticate against the API using Remote User auth.
## Searching for documents
Full text searching is available on the `/api/documents/` endpoint. Two

View File

@ -3,8 +3,8 @@
i18n-title
info="Review the log files for the application and for email checking."
i18n-info>
<div class="form-check form-switch" (click)="toggleAutoRefresh()">
<input class="form-check-input" type="checkbox" role="switch" id="autoRefreshSwitch" [attr.checked]="autoRefreshInterval">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" id="autoRefreshSwitch" (click)="toggleAutoRefresh()" [attr.checked]="autoRefreshInterval">
<label class="form-check-label" for="autoRefreshSwitch" i18n>Auto refresh</label>
</div>
</pngx-page-header>

View File

@ -2,9 +2,9 @@ import {
Component,
ElementRef,
OnInit,
AfterViewChecked,
ViewChild,
OnDestroy,
ChangeDetectorRef,
} from '@angular/core'
import { Subject, takeUntil } from 'rxjs'
import { LogService } from 'src/app/services/rest/log.service'
@ -14,8 +14,11 @@ import { LogService } from 'src/app/services/rest/log.service'
templateUrl: './logs.component.html',
styleUrls: ['./logs.component.scss'],
})
export class LogsComponent implements OnInit, AfterViewChecked, OnDestroy {
constructor(private logService: LogService) {}
export class LogsComponent implements OnInit, OnDestroy {
constructor(
private logService: LogService,
private changedetectorRef: ChangeDetectorRef
) {}
public logs: string[] = []
@ -47,10 +50,6 @@ export class LogsComponent implements OnInit, AfterViewChecked, OnDestroy {
})
}
ngAfterViewChecked() {
this.scrollToBottom()
}
ngOnDestroy(): void {
this.unsubscribeNotifier.next(true)
this.unsubscribeNotifier.complete()
@ -66,6 +65,7 @@ export class LogsComponent implements OnInit, AfterViewChecked, OnDestroy {
next: (result) => {
this.logs = result
this.isLoading = false
this.scrollToBottom()
},
error: () => {
this.logs = []
@ -89,6 +89,7 @@ export class LogsComponent implements OnInit, AfterViewChecked, OnDestroy {
}
scrollToBottom(): void {
this.changedetectorRef.detectChanges()
this.logContainer?.nativeElement.scroll({
top: this.logContainer.nativeElement.scrollHeight,
left: 0,

View File

@ -11,8 +11,8 @@
<button class="btn btn-sm btn-outline-primary me-4" (click)="dismissTasks()" *pngxIfPermissions="{ action: PermissionAction.Change, type: PermissionType.PaperlessTask }" [disabled]="tasksService.total === 0">
<i-bs name="check2-all"></i-bs>&nbsp;{{dismissButtonText}}
</button>
<div class="form-check form-switch mb-0" (click)="toggleAutoRefresh()">
<input class="form-check-input" type="checkbox" role="switch" id="autoRefreshSwitch" [attr.checked]="autoRefreshInterval">
<div class="form-check form-switch mb-0">
<input class="form-check-input" type="checkbox" role="switch" id="autoRefreshSwitch" (click)="toggleAutoRefresh()" [attr.checked]="autoRefreshInterval">
<label class="form-check-label" for="autoRefreshSwitch" i18n>Auto refresh</label>
</div>
</div>

View File

@ -71,8 +71,8 @@
<form [formGroup]='documentForm' (ngSubmit)="save()">
<div class="btn-toolbar mb-1 pb-3 border-bottom">
<div class="btn-group">
<div class="btn-toolbar mb-1 border-bottom">
<div class="btn-group pb-3">
<button type="button" class="btn btn-sm btn-outline-secondary" i18n-title title="Close" (click)="close()">
<i-bs width="1.2em" height="1.2em" name="x"></i-bs>
</button>
@ -310,7 +310,7 @@
</div>
<ng-template #saveButtons>
<div class="btn-group ms-auto">
<div class="btn-group pb-3 ms-auto">
<ng-container *pngxIfPermissions="{ action: PermissionAction.Change, type: PermissionType.Document }">
<button type="submit" class="order-3 btn btn-sm btn-primary" i18n [disabled]="!userCanEdit || networkActive || (isDirty$ | async) !== true">Save</button>
@if (hasNext()) {

View File

@ -861,8 +861,11 @@ export class DocumentDetailComponent
get userIsOwner(): boolean {
let doc: Document = Object.assign({}, this.document)
// dont disable while editing
if (this.document && this.store?.value.permissions_form?.owner) {
doc.owner = this.store?.value.permissions_form?.owner
if (
this.document &&
this.store?.value.permissions_form?.hasOwnProperty('owner')
) {
doc.owner = this.store.value.permissions_form.owner
}
return !this.document || this.permissionsService.currentUserOwnsObject(doc)
}
@ -870,8 +873,11 @@ export class DocumentDetailComponent
get userCanEdit(): boolean {
let doc: Document = Object.assign({}, this.document)
// dont disable while editing
if (this.document && this.store?.value.permissions_form?.owner) {
doc.owner = this.store?.value.permissions_form?.owner
if (
this.document &&
this.store?.value.permissions_form?.hasOwnProperty('owner')
) {
doc.owner = this.store.value.permissions_form.owner
}
return (
!this.document ||

View File

@ -11,6 +11,7 @@ import {
NgbPaginationModule,
NgbModalModule,
NgbModalRef,
NgbPopoverModule,
} from '@ng-bootstrap/ng-bootstrap'
import { of, throwError } from 'rxjs'
import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
@ -64,6 +65,7 @@ describe('CustomFieldsComponent', () => {
FormsModule,
ReactiveFormsModule,
NgbModalModule,
NgbPopoverModule,
],
})

View File

@ -7,6 +7,7 @@ import {
NgbPaginationModule,
NgbModalRef,
NgbModalModule,
NgbPopoverModule,
} from '@ng-bootstrap/ng-bootstrap'
import { of, throwError } from 'rxjs'
import { Workflow } from 'src/app/data/workflow'
@ -99,6 +100,7 @@ describe('WorkflowsComponent', () => {
FormsModule,
ReactiveFormsModule,
NgbModalModule,
NgbPopoverModule,
],
})

View File

@ -47,3 +47,11 @@ class HttpRemoteUserMiddleware(PersistentRemoteUserMiddleware):
"""
header = settings.HTTP_REMOTE_USER_HEADER_NAME
class PaperlessRemoteUserAuthentication(authentication.RemoteUserAuthentication):
"""
REMOTE_USER authentication for DRF which overrides the default header.
"""
header = settings.HTTP_REMOTE_USER_HEADER_NAME

View File

@ -420,19 +420,31 @@ if AUTO_LOGIN_USERNAME:
# regular login in case the provided user does not exist.
MIDDLEWARE.insert(_index + 1, "paperless.auth.AutoLoginMiddleware")
ENABLE_HTTP_REMOTE_USER = __get_boolean("PAPERLESS_ENABLE_HTTP_REMOTE_USER")
HTTP_REMOTE_USER_HEADER_NAME = os.getenv(
def _parse_remote_user_settings() -> str:
global MIDDLEWARE, AUTHENTICATION_BACKENDS, REST_FRAMEWORK
enable = __get_boolean("PAPERLESS_ENABLE_HTTP_REMOTE_USER")
if enable:
MIDDLEWARE.append("paperless.auth.HttpRemoteUserMiddleware")
AUTHENTICATION_BACKENDS.insert(
0,
"django.contrib.auth.backends.RemoteUserBackend",
)
REST_FRAMEWORK["DEFAULT_AUTHENTICATION_CLASSES"].insert(
0,
"paperless.auth.PaperlessRemoteUserAuthentication",
)
header_name = os.getenv(
"PAPERLESS_HTTP_REMOTE_USER_HEADER_NAME",
"HTTP_REMOTE_USER",
)
if ENABLE_HTTP_REMOTE_USER:
MIDDLEWARE.append("paperless.auth.HttpRemoteUserMiddleware")
AUTHENTICATION_BACKENDS.insert(0, "django.contrib.auth.backends.RemoteUserBackend")
REST_FRAMEWORK["DEFAULT_AUTHENTICATION_CLASSES"].append(
"rest_framework.authentication.RemoteUserAuthentication",
)
return header_name
HTTP_REMOTE_USER_HEADER_NAME = _parse_remote_user_settings()
# X-Frame options for embedded PDF display:
X_FRAME_OPTIONS = "ANY" if DEBUG else "SAMEORIGIN"

View File

@ -0,0 +1,75 @@
import os
from unittest import mock
from django.contrib.auth.models import User
from rest_framework import status
from rest_framework.test import APITestCase
from documents.tests.utils import DirectoriesMixin
from paperless.settings import _parse_remote_user_settings
class TestRemoteUser(DirectoriesMixin, APITestCase):
def setUp(self):
super().setUp()
self.user = User.objects.create_superuser(
username="temp_admin",
)
def test_remote_user(self):
"""
GIVEN:
- Configured user
- Remote user auth is enabled
WHEN:
- API call is made to get documents
THEN:
- Call succeeds
"""
with mock.patch.dict(
os.environ,
{
"PAPERLESS_ENABLE_HTTP_REMOTE_USER": "True",
},
):
_parse_remote_user_settings()
response = self.client.get("/api/documents/")
# 403 testing locally, 401 on ci...
self.assertIn(
response.status_code,
[status.HTTP_401_UNAUTHORIZED, status.HTTP_403_FORBIDDEN],
)
response = self.client.get(
"/api/documents/",
headers={
"Remote-User": self.user.username,
},
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
def test_remote_user_header_setting(self):
"""
GIVEN:
- Remote user header name is set
WHEN:
- Settings are parsed
THEN:
- Correct header name is returned
"""
with mock.patch.dict(
os.environ,
{
"PAPERLESS_ENABLE_HTTP_REMOTE_USER": "True",
"PAPERLESS_HTTP_REMOTE_USER_HEADER_NAME": "HTTP_FOO",
},
):
header_name = _parse_remote_user_settings()
self.assertEqual(header_name, "HTTP_FOO")