mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2025-12-18 01:41:14 -06:00
Compare commits
1 Commits
fix-rmpipe
...
dependabot
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4f82110742 |
15
.github/workflows/ci.yml
vendored
15
.github/workflows/ci.yml
vendored
@@ -275,12 +275,8 @@ jobs:
|
|||||||
tests-frontend-e2e:
|
tests-frontend-e2e:
|
||||||
name: "Frontend E2E Tests (Node ${{ matrix.node-version }} - ${{ matrix.shard-index }}/${{ matrix.shard-count }})"
|
name: "Frontend E2E Tests (Node ${{ matrix.node-version }} - ${{ matrix.shard-index }}/${{ matrix.shard-count }})"
|
||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
container: mcr.microsoft.com/playwright:v1.57.0-noble
|
|
||||||
needs:
|
needs:
|
||||||
- install-frontend-dependencies
|
- install-frontend-dependencies
|
||||||
env:
|
|
||||||
PLAYWRIGHT_BROWSERS_PATH: /ms-playwright
|
|
||||||
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1
|
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
@@ -309,8 +305,19 @@ jobs:
|
|||||||
key: ${{ runner.os }}-frontenddeps-${{ hashFiles('src-ui/pnpm-lock.yaml') }}
|
key: ${{ runner.os }}-frontenddeps-${{ hashFiles('src-ui/pnpm-lock.yaml') }}
|
||||||
- name: Re-link Angular cli
|
- name: Re-link Angular cli
|
||||||
run: cd src-ui && pnpm link @angular/cli
|
run: cd src-ui && pnpm link @angular/cli
|
||||||
|
- name: Cache Playwright browsers
|
||||||
|
uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: ~/.cache/ms-playwright
|
||||||
|
key: ${{ runner.os }}-playwright-${{ hashFiles('src-ui/pnpm-lock.yaml') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-playwright-
|
||||||
|
- name: Install Playwright system dependencies
|
||||||
|
run: npx playwright install-deps
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: cd src-ui && pnpm install --no-frozen-lockfile
|
run: cd src-ui && pnpm install --no-frozen-lockfile
|
||||||
|
- name: Install Playwright
|
||||||
|
run: cd src-ui && pnpm exec playwright install
|
||||||
- name: Run Playwright e2e tests
|
- name: Run Playwright e2e tests
|
||||||
run: cd src-ui && pnpm exec playwright test --shard ${{ matrix.shard-index }}/${{ matrix.shard-count }}
|
run: cd src-ui && pnpm exec playwright test --shard ${{ matrix.shard-index }}/${{ matrix.shard-count }}
|
||||||
frontend-bundle-analysis:
|
frontend-bundle-analysis:
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ RUN set -eux \
|
|||||||
# Purpose: Installs s6-overlay and rootfs
|
# Purpose: Installs s6-overlay and rootfs
|
||||||
# Comments:
|
# Comments:
|
||||||
# - Don't leave anything extra in here either
|
# - Don't leave anything extra in here either
|
||||||
FROM ghcr.io/astral-sh/uv:0.9.15-python3.12-trixie-slim AS s6-overlay-base
|
FROM ghcr.io/astral-sh/uv:0.9.17-python3.12-trixie-slim AS s6-overlay-base
|
||||||
|
|
||||||
WORKDIR /usr/src/s6
|
WORKDIR /usr/src/s6
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
@if (previewText) {
|
@if (previewText) {
|
||||||
<div class="bg-light p-3 overflow-auto whitespace-preserve" width="100%">{{previewText}}</div>
|
<div class="bg-light p-3 overflow-auto whitespace-preserve" width="100%">{{previewText}}</div>
|
||||||
} @else {
|
} @else {
|
||||||
<object [data]="previewURL" width="100%" class="bg-light" [class.p-2]="!isPdf"></object>
|
<object [data]="previewURL | safeUrl" width="100%" class="bg-light" [class.p-2]="!isPdf"></object>
|
||||||
}
|
}
|
||||||
} @else {
|
} @else {
|
||||||
@if (requiresPassword) {
|
@if (requiresPassword) {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { first, Subject, takeUntil } from 'rxjs'
|
|||||||
import { Document } from 'src/app/data/document'
|
import { Document } from 'src/app/data/document'
|
||||||
import { SETTINGS_KEYS } from 'src/app/data/ui-settings'
|
import { SETTINGS_KEYS } from 'src/app/data/ui-settings'
|
||||||
import { DocumentTitlePipe } from 'src/app/pipes/document-title.pipe'
|
import { DocumentTitlePipe } from 'src/app/pipes/document-title.pipe'
|
||||||
|
import { SafeUrlPipe } from 'src/app/pipes/safeurl.pipe'
|
||||||
import { DocumentService } from 'src/app/services/rest/document.service'
|
import { DocumentService } from 'src/app/services/rest/document.service'
|
||||||
import { SettingsService } from 'src/app/services/settings.service'
|
import { SettingsService } from 'src/app/services/settings.service'
|
||||||
|
|
||||||
@@ -18,6 +19,7 @@ import { SettingsService } from 'src/app/services/settings.service'
|
|||||||
NgbPopoverModule,
|
NgbPopoverModule,
|
||||||
DocumentTitlePipe,
|
DocumentTitlePipe,
|
||||||
PdfViewerModule,
|
PdfViewerModule,
|
||||||
|
SafeUrlPipe,
|
||||||
NgxBootstrapIconsModule,
|
NgxBootstrapIconsModule,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ import { IfPermissionsDirective } from 'src/app/directives/if-permissions.direct
|
|||||||
import { PermissionsGuard } from 'src/app/guards/permissions.guard'
|
import { PermissionsGuard } from 'src/app/guards/permissions.guard'
|
||||||
import { CustomDatePipe } from 'src/app/pipes/custom-date.pipe'
|
import { CustomDatePipe } from 'src/app/pipes/custom-date.pipe'
|
||||||
import { DocumentTitlePipe } from 'src/app/pipes/document-title.pipe'
|
import { DocumentTitlePipe } from 'src/app/pipes/document-title.pipe'
|
||||||
|
import { SafeUrlPipe } from 'src/app/pipes/safeurl.pipe'
|
||||||
import { DocumentListViewService } from 'src/app/services/document-list-view.service'
|
import { DocumentListViewService } from 'src/app/services/document-list-view.service'
|
||||||
import { PermissionsService } from 'src/app/services/permissions.service'
|
import { PermissionsService } from 'src/app/services/permissions.service'
|
||||||
import { CustomFieldsService } from 'src/app/services/rest/custom-fields.service'
|
import { CustomFieldsService } from 'src/app/services/rest/custom-fields.service'
|
||||||
@@ -127,6 +128,7 @@ describe('SavedViewWidgetComponent', () => {
|
|||||||
IfPermissionsDirective,
|
IfPermissionsDirective,
|
||||||
CustomDatePipe,
|
CustomDatePipe,
|
||||||
DocumentTitlePipe,
|
DocumentTitlePipe,
|
||||||
|
SafeUrlPipe,
|
||||||
PreviewPopupComponent,
|
PreviewPopupComponent,
|
||||||
CustomFieldDisplayComponent,
|
CustomFieldDisplayComponent,
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -379,7 +379,7 @@
|
|||||||
<ng-template #previewContent>
|
<ng-template #previewContent>
|
||||||
<div class="thumb-preview position-absolute pe-none text-center" [class.fade]="previewLoaded">
|
<div class="thumb-preview position-absolute pe-none text-center" [class.fade]="previewLoaded">
|
||||||
@if (showThumbnailOverlay) {
|
@if (showThumbnailOverlay) {
|
||||||
<img [src]="thumbUrl" class="mx-auto" [attr.width]="previewZoomScale === 'page-fit' ? 'auto' : '100%'" [attr.height]="previewZoomScale === 'page-fit' ? '100%' : 'auto'" alt="Document loading..." i18n-alt />
|
<img [src]="thumbUrl | safeUrl" class="mx-auto" [attr.width]="previewZoomScale === 'page-fit' ? 'auto' : '100%'" [attr.height]="previewZoomScale === 'page-fit' ? '100%' : 'auto'" alt="Document loading..." i18n-alt />
|
||||||
}
|
}
|
||||||
<div class="position-absolute top-0 start-0 m-2 p-2 d-flex align-items-center justify-content-center">
|
<div class="position-absolute top-0 start-0 m-2 p-2 d-flex align-items-center justify-content-center">
|
||||||
<div>
|
<div>
|
||||||
@@ -406,7 +406,7 @@
|
|||||||
</pdf-viewer>
|
</pdf-viewer>
|
||||||
</div>
|
</div>
|
||||||
} @else {
|
} @else {
|
||||||
<object [data]="previewUrl" class="preview-sticky" width="100%"></object>
|
<object [data]="previewUrl | safeUrl" class="preview-sticky" width="100%"></object>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@case (ContentRenderType.Text) {
|
@case (ContentRenderType.Text) {
|
||||||
@@ -414,7 +414,7 @@
|
|||||||
}
|
}
|
||||||
@case (ContentRenderType.Image) {
|
@case (ContentRenderType.Image) {
|
||||||
<div class="preview-sticky">
|
<div class="preview-sticky">
|
||||||
<img [src]="previewUrl" width="100%" height="100%" alt="{{title}}" />
|
<img [src]="previewUrl | safeUrl" width="100%" height="100%" alt="{{title}}" />
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@case (ContentRenderType.TIFF) {
|
@case (ContentRenderType.TIFF) {
|
||||||
@@ -427,7 +427,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
@case (ContentRenderType.Other) {
|
@case (ContentRenderType.Other) {
|
||||||
<object [data]="previewUrl" class="preview-sticky" width="100%"></object>
|
<object [data]="previewUrl | safeUrl" class="preview-sticky" width="100%"></object>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@if (requiresPassword) {
|
@if (requiresPassword) {
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ import { IfPermissionsDirective } from 'src/app/directives/if-permissions.direct
|
|||||||
import { CustomDatePipe } from 'src/app/pipes/custom-date.pipe'
|
import { CustomDatePipe } from 'src/app/pipes/custom-date.pipe'
|
||||||
import { DocumentTitlePipe } from 'src/app/pipes/document-title.pipe'
|
import { DocumentTitlePipe } from 'src/app/pipes/document-title.pipe'
|
||||||
import { FileSizePipe } from 'src/app/pipes/file-size.pipe'
|
import { FileSizePipe } from 'src/app/pipes/file-size.pipe'
|
||||||
|
import { SafeUrlPipe } from 'src/app/pipes/safeurl.pipe'
|
||||||
import { ComponentRouterService } from 'src/app/services/component-router.service'
|
import { ComponentRouterService } from 'src/app/services/component-router.service'
|
||||||
import { DocumentListViewService } from 'src/app/services/document-list-view.service'
|
import { DocumentListViewService } from 'src/app/services/document-list-view.service'
|
||||||
import { HotKeyService } from 'src/app/services/hot-key.service'
|
import { HotKeyService } from 'src/app/services/hot-key.service'
|
||||||
@@ -168,6 +169,7 @@ export enum ZoomSetting {
|
|||||||
FormsModule,
|
FormsModule,
|
||||||
ReactiveFormsModule,
|
ReactiveFormsModule,
|
||||||
NgTemplateOutlet,
|
NgTemplateOutlet,
|
||||||
|
SafeUrlPipe,
|
||||||
NgbNavModule,
|
NgbNavModule,
|
||||||
NgbDropdownModule,
|
NgbDropdownModule,
|
||||||
NgxBootstrapIconsModule,
|
NgxBootstrapIconsModule,
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import { IfPermissionsDirective } from 'src/app/directives/if-permissions.direct
|
|||||||
import { CustomDatePipe } from 'src/app/pipes/custom-date.pipe'
|
import { CustomDatePipe } from 'src/app/pipes/custom-date.pipe'
|
||||||
import { DocumentTitlePipe } from 'src/app/pipes/document-title.pipe'
|
import { DocumentTitlePipe } from 'src/app/pipes/document-title.pipe'
|
||||||
import { IsNumberPipe } from 'src/app/pipes/is-number.pipe'
|
import { IsNumberPipe } from 'src/app/pipes/is-number.pipe'
|
||||||
|
import { SafeUrlPipe } from 'src/app/pipes/safeurl.pipe'
|
||||||
import { CustomFieldDisplayComponent } from '../../common/custom-field-display/custom-field-display.component'
|
import { CustomFieldDisplayComponent } from '../../common/custom-field-display/custom-field-display.component'
|
||||||
import { PreviewPopupComponent } from '../../common/preview-popup/preview-popup.component'
|
import { PreviewPopupComponent } from '../../common/preview-popup/preview-popup.component'
|
||||||
import { DocumentCardLargeComponent } from './document-card-large.component'
|
import { DocumentCardLargeComponent } from './document-card-large.component'
|
||||||
@@ -52,6 +53,7 @@ describe('DocumentCardLargeComponent', () => {
|
|||||||
DocumentTitlePipe,
|
DocumentTitlePipe,
|
||||||
CustomDatePipe,
|
CustomDatePipe,
|
||||||
IfPermissionsDirective,
|
IfPermissionsDirective,
|
||||||
|
SafeUrlPipe,
|
||||||
IsNumberPipe,
|
IsNumberPipe,
|
||||||
PreviewPopupComponent,
|
PreviewPopupComponent,
|
||||||
CustomFieldDisplayComponent,
|
CustomFieldDisplayComponent,
|
||||||
|
|||||||
32
src-ui/src/app/pipes/safeurl.pipe.spec.ts
Normal file
32
src-ui/src/app/pipes/safeurl.pipe.spec.ts
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import { TestBed } from '@angular/core/testing'
|
||||||
|
import { BrowserModule, DomSanitizer } from '@angular/platform-browser'
|
||||||
|
import { SafeUrlPipe } from './safeurl.pipe'
|
||||||
|
|
||||||
|
describe('SafeUrlPipe', () => {
|
||||||
|
let pipe: SafeUrlPipe
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
providers: [SafeUrlPipe],
|
||||||
|
imports: [BrowserModule],
|
||||||
|
})
|
||||||
|
pipe = TestBed.inject(SafeUrlPipe)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should bypass security and trust the url', () => {
|
||||||
|
const url = 'https://example.com'
|
||||||
|
const domSanitizer = TestBed.inject(DomSanitizer)
|
||||||
|
const sanitizerSpy = jest.spyOn(
|
||||||
|
domSanitizer,
|
||||||
|
'bypassSecurityTrustResourceUrl'
|
||||||
|
)
|
||||||
|
|
||||||
|
let safeResourceUrl = pipe.transform(url)
|
||||||
|
expect(safeResourceUrl).not.toBeNull()
|
||||||
|
expect(sanitizerSpy).toHaveBeenCalled()
|
||||||
|
|
||||||
|
safeResourceUrl = pipe.transform(null)
|
||||||
|
expect(safeResourceUrl).not.toBeNull()
|
||||||
|
expect(sanitizerSpy).toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
})
|
||||||
17
src-ui/src/app/pipes/safeurl.pipe.ts
Normal file
17
src-ui/src/app/pipes/safeurl.pipe.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import { Pipe, PipeTransform, inject } from '@angular/core'
|
||||||
|
import { DomSanitizer } from '@angular/platform-browser'
|
||||||
|
|
||||||
|
@Pipe({
|
||||||
|
name: 'safeUrl',
|
||||||
|
})
|
||||||
|
export class SafeUrlPipe implements PipeTransform {
|
||||||
|
private sanitizer = inject(DomSanitizer)
|
||||||
|
|
||||||
|
transform(url) {
|
||||||
|
if (url == null) {
|
||||||
|
return this.sanitizer.bypassSecurityTrustResourceUrl('')
|
||||||
|
} else {
|
||||||
|
return this.sanitizer.bypassSecurityTrustResourceUrl(url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user