[ADF-2415] PDF Viewer - open password protected pdf files (#3022)

* pdf password dialog

* PDF viewer onPassword test

* pdf password dialog

* PDF viewer onPassword test

* test

* test

* test

* test

* test

* test

* test

* fix password test

* travis improvements
This commit is contained in:
Cilibiu Bogdan 2018-03-21 23:30:20 +02:00 committed by Eugenio Romano
parent 2951374cc0
commit ae8b7419a0
12 changed files with 377 additions and 7 deletions

View File

@ -6,7 +6,10 @@ node_js:
addons:
chrome: stable
before_script:
- "sudo chown root /opt/google/chrome/chrome-sandbox"
- "sudo chmod 4755 /opt/google/chrome/chrome-sandbox"
before_install:
- export CHROME_BIN=chromium-browser
- export DISPLAY=:99.0
@ -62,6 +65,9 @@ script:
jobs:
include:
- stage: Check build demo shell in production mode AND e2e
before_install:
- "export DISPLAY=:99.0"
- "sh -e /etc/init.d/xvfb start"
script: ./scripts/test-dist.sh
- stage: Check 2.0.0 Project Update
script: ./scripts/test-e2e-bc.sh

View File

@ -46,7 +46,8 @@ module.exports = function (config) {
{pattern: './config/app.config.json', included: false, served: true, watched: false},
{pattern: './core/viewer/assets/fake-test-file.pdf', included: false, served: true, watched: false},
{pattern: './core/viewer/assets/fake-test-file.txt', included: false, served: true, watched: false}
{pattern: './core/viewer/assets/fake-test-file.txt', included: false, served: true, watched: false},
{pattern: './core/viewer/assets/fake-test-password-file.pdf', included: false, served: true, watched: false}
],
webpack: (config.mode === 'coverage') ? webpackCoverage(config) : webpackTest(config),
@ -63,7 +64,8 @@ module.exports = function (config) {
proxies: {
'/app.config.json': '/base/config/app.config.json',
'/fake-test-file.pdf': '/base/core/viewer/assets/fake-test-file.pdf',
'/fake-test-file.txt': '/base/core/viewer/assets/fake-test-file.txt'
'/fake-test-file.txt': '/base/core/viewer/assets/fake-test-file.txt',
'/fake-test-password-file.pdf': '/base/core/viewer/assets/fake-test-password-file.pdf'
},
// level of logging

View File

@ -220,6 +220,12 @@
"MORE_INFORMATION": "More information",
"LESS_INFORMATION": "Less information"
}
},
"PDF_DIALOG": {
"SUBMIT": "Submit",
"CLOSE": "Close",
"PLACEHOLDER": "Password",
"ERROR": "Password is wrong"
}
}
}

Binary file not shown.

View File

@ -0,0 +1,29 @@
<div mat-dialog-title>
<mat-icon>lock</mat-icon>
</div>
<mat-dialog-content>
<form (submit)="submit()">
<mat-input-container class="adf-full-width">
<input matInput
type="password"
placeholder="{{ 'ADF_VIEWER.PDF_DIALOG.PLACEHOLDER' | translate }}"
[formControl]="passwordFormControl" />
</mat-input-container>
<mat-error *ngIf="isError()">{{ 'ADF_VIEWER.PDF_DIALOG.ERROR' | translate }}</mat-error>
</form>
</mat-dialog-content>
<mat-dialog-actions class="adf-dialog-buttons">
<span class="adf-fill-remaining-space"></span>
<button mat-button mat-dialog-close>{{ 'ADF_VIEWER.PDF_DIALOG.CLOSE' | translate }}</button>
<button mat-button
class="adf-dialog-action-button"
[disabled]="!isValid()"
(click)="submit()">
{{ 'ADF_VIEWER.PDF_DIALOG.SUBMIT' | translate }}
</button>
</mat-dialog-actions>

View File

@ -0,0 +1,21 @@
.adf-fill-remaining-space {
flex: 1 1 auto;
}
.adf-full-width {
width: 100%;
}
@mixin adf-dialog-theme($theme) {
$primary: map-get($theme, primary);
.adf-dialog-buttons button {
text-transform: uppercase;
}
.adf-dialog-action-button:enabled {
color: mat-color($primary);
}
}

View File

@ -0,0 +1,111 @@
/*!
* @license
* Copyright 2016 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material';
import { MaterialModule } from '../../material.module';
import { PdfPasswordDialogComponent } from './pdfViewer-password-dialog';
declare let PDFJS: any;
describe('PdfPasswordDialogComponent', () => {
let component: PdfPasswordDialogComponent;
let fixture: ComponentFixture<PdfPasswordDialogComponent>;
let dialogRef: MatDialogRef<PdfPasswordDialogComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
MaterialModule
],
declarations: [
PdfPasswordDialogComponent
],
providers: [
{
provide: MAT_DIALOG_DATA, useValue: {
reason: null
}
},
{
provide: MatDialogRef, useValue: {
close: jasmine.createSpy('open')
}
}
]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(PdfPasswordDialogComponent);
component = fixture.componentInstance;
dialogRef = TestBed.get(MatDialogRef);
});
it('should have empty default value', () => {
fixture.detectChanges();
expect(component.passwordFormControl.value).toBe('');
});
describe('isError', () => {
beforeEach(() => {
fixture.detectChanges();
});
it('should return false', () => {
component.data.reason = PDFJS.PasswordResponses.NEED_PASSWORD;
expect(component.isError()).toBe(false);
});
it('should return true', () => {
component.data.reason = PDFJS.PasswordResponses.INCORRECT_PASSWORD;
expect(component.isError()).toBe(true);
});
});
describe('isValid', () => {
beforeEach(() => {
fixture.detectChanges();
});
it('should return false when input has no value', () => {
component.passwordFormControl.setValue('');
expect(component.isValid()).toBe(false);
});
it('should return true when input has a valid value', () => {
component.passwordFormControl.setValue('some-text');
expect(component.isValid()).toBe(true);
});
});
it('should close dialog with input value', () => {
fixture.detectChanges();
component.passwordFormControl.setValue('some-value');
component.submit();
expect(dialogRef.close).toHaveBeenCalledWith('some-value');
});
});

View File

@ -0,0 +1,52 @@
/*!
* @license
* Copyright 2016 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Component, Inject, OnInit } from '@angular/core';
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material';
import { FormControl, Validators } from '@angular/forms';
declare let PDFJS: any;
@Component({
selector: 'adf-pdf-viewer-password-dialog',
templateUrl: './pdfViewer-password-dialog.html',
styleUrls: [ './pdfViewer-password-dialog.scss' ]
})
export class PdfPasswordDialogComponent implements OnInit {
passwordFormControl: FormControl;
constructor(
private dialogRef: MatDialogRef<PdfPasswordDialogComponent>,
@Inject(MAT_DIALOG_DATA) public data: any
) {}
ngOnInit() {
this.passwordFormControl = new FormControl('', [Validators.required]);
}
isError(): boolean {
return this.data.reason === PDFJS.PasswordResponses.INCORRECT_PASSWORD;
}
isValid(): boolean {
return !this.passwordFormControl.hasError('required');
}
submit(): void {
this.dialogRef.close(this.passwordFormControl.value);
}
}

View File

@ -30,6 +30,18 @@ import { PdfViewerComponent } from './pdfViewer.component';
import { PdfThumbListComponent } from './pdfViewer-thumbnails.component';
import { PdfThumbComponent } from './pdfViewer-thumb.component';
import { RIGHT_ARROW, LEFT_ARROW } from '@angular/cdk/keycodes';
import { MatDialog } from '@angular/material';
import { Observable } from 'rxjs/Observable';
import { ViewerModule } from '../viewer.module';
declare let PDFJS: any;
@Component({
selector: 'adf-test-dialog-component',
template: ''
})
class TestDialogComponent {
}
@Component({
template: `
@ -51,6 +63,26 @@ class UrlTestComponent {
}
}
@Component({
template: `
<adf-pdf-viewer [allowThumbnails]="true"
[showToolbar]="true"
[urlFile]="urlFile">
</adf-pdf-viewer>
`
})
class UrlTestPasswordComponent {
@ViewChild(PdfViewerComponent)
pdfViewerComponent: PdfViewerComponent;
urlFile: any;
constructor() {
this.urlFile = './fake-test-password-file.pdf';
}
}
@Component({
template: `
<adf-pdf-viewer [allowThumbnails]="true"
@ -96,6 +128,7 @@ describe('Test PdfViewer component', () => {
let fixture: ComponentFixture<PdfViewerComponent>;
let element: HTMLElement;
let change: any;
let dialog: MatDialog;
beforeEach(async(() => {
TestBed.configureTestingModule({
@ -104,23 +137,38 @@ describe('Test PdfViewer component', () => {
MaterialModule
],
declarations: [
TestDialogComponent,
PdfViewerComponent,
PdfThumbListComponent,
PdfThumbComponent,
UrlTestComponent,
UrlTestPasswordComponent,
BlobTestComponent
],
providers: [
{
provide: MatDialog, useValue: {
open: () => {
}
}
},
SettingsService,
AuthenticationService,
AlfrescoApiService,
RenderingQueueServices
]
}).compileComponents();
})
.overrideModule(ViewerModule, {
set: {
entryComponents: [TestDialogComponent]
}
})
.compileComponents();
}));
beforeEach((done) => {
fixture = TestBed.createComponent(PdfViewerComponent);
dialog = TestBed.get(MatDialog);
element = fixture.nativeElement;
component = fixture.componentInstance;
@ -489,7 +537,71 @@ describe('Test PdfViewer component', () => {
});
});
}, 5000);
});
});
describe('Password protection dialog', () => {
let fixtureUrlTestPasswordComponent: ComponentFixture<UrlTestPasswordComponent>;
let componentUrlTestPasswordComponent: UrlTestPasswordComponent;
beforeEach((done) => {
fixtureUrlTestPasswordComponent = TestBed.createComponent(UrlTestPasswordComponent);
componentUrlTestPasswordComponent = fixtureUrlTestPasswordComponent.componentInstance;
spyOn(dialog, 'open').and.callFake((comp, context) => {
if (context.data.reason === PDFJS.PasswordResponses.NEED_PASSWORD) {
return {
afterClosed: () => Observable.of('wrong_password')
};
}
if (context.data.reason === PDFJS.PasswordResponses.INCORRECT_PASSWORD) {
return {
afterClosed: () => Observable.of('password')
};
}
});
fixtureUrlTestPasswordComponent.detectChanges();
componentUrlTestPasswordComponent.pdfViewerComponent.rendered.subscribe(() => {
done();
});
});
it('should try to access protected pdf', (done) => {
fixture.detectChanges();
fixture.whenStable().then(() => {
fixture.detectChanges();
expect(dialog.open).toHaveBeenCalledTimes(2);
done();
});
});
it('should raise dialog asking for password', (done) => {
fixture.detectChanges();
fixture.whenStable().then(() => {
fixture.detectChanges();
expect(dialog.open['calls'].all()[0].args[1].data).toEqual({
reason: PDFJS.PasswordResponses.NEED_PASSWORD
});
done();
});
});
it('it should raise dialog with incorrect password', (done) => {
fixture.detectChanges();
fixture.whenStable().then(() => {
fixture.detectChanges();
expect(dialog.open['calls'].all()[1].args[1].data).toEqual({
reason: PDFJS.PasswordResponses.INCORRECT_PASSWORD
});
done();
});
});
});
});

View File

@ -28,6 +28,8 @@ import {
} from '@angular/core';
import { LogService } from '../../services/log.service';
import { RenderingQueueServices } from '../services/rendering-queue.services';
import { PdfPasswordDialogComponent } from './pdfViewer-password-dialog';
import { MatDialog } from '@angular/material';
declare let PDFJS: any;
@ -92,8 +94,10 @@ export class PdfViewerComponent implements OnChanges, OnDestroy {
return Math.round(this.currentScale * 100) + '%';
}
constructor(private renderingQueueServices: RenderingQueueServices,
private logService: LogService) {
constructor(
private dialog: MatDialog,
private renderingQueueServices: RenderingQueueServices,
private logService: LogService) {
// needed to preserve "this" context
this.onPageChange = this.onPageChange.bind(this);
this.onPagesLoaded = this.onPagesLoaded.bind(this);
@ -124,6 +128,10 @@ export class PdfViewerComponent implements OnChanges, OnDestroy {
executePdf(src) {
this.loadingTask = this.getPDFJS().getDocument(src);
this.loadingTask.onPassword = (callback, reason) => {
this.onPdfPassword(callback, reason);
};
this.loadingTask.onProgress = (progressData) => {
let level = progressData.loaded / progressData.total;
this.loadingPercent = Math.round(level * 100);
@ -397,6 +405,20 @@ export class PdfViewerComponent implements OnChanges, OnDestroy {
this.displayPage = event.pageNumber;
}
onPdfPassword(callback, reason) {
this.dialog
.open(PdfPasswordDialogComponent, {
width: '400px',
disableClose: true,
data: { reason }
})
.afterClosed().subscribe(password => {
if (password) {
callback(password);
}
});
}
/**
* Page Rendered Event
*/

View File

@ -431,7 +431,7 @@ describe('ViewerComponent', () => {
});
describe('View', () => {
xdescribe('View', () => {
describe('Overlay mode true', () => {

View File

@ -19,6 +19,7 @@ import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { TranslateModule } from '@ngx-translate/core';
import { FlexLayoutModule } from '@angular/flex-layout';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { MaterialModule } from '../material.module';
import { ToolbarModule } from '../toolbar/toolbar.module';
@ -26,6 +27,7 @@ import { PipeModule } from '../pipes/pipe.module';
import { ImgViewerComponent } from './components/imgViewer.component';
import { MediaPlayerComponent } from './components/mediaPlayer.component';
import { PdfViewerComponent } from './components/pdfViewer.component';
import { PdfPasswordDialogComponent } from './components/pdfViewer-password-dialog';
import { PdfThumbComponent } from './components/pdfViewer-thumb.component';
import { PdfThumbListComponent } from './components/pdfViewer-thumbnails.component';
import { TxtViewerComponent } from './components/txtViewer.component';
@ -43,11 +45,14 @@ import { ViewerToolbarActionsComponent } from './components/viewer-toolbar-actio
CommonModule,
MaterialModule,
TranslateModule,
FormsModule,
ReactiveFormsModule,
ToolbarModule,
PipeModule,
FlexLayoutModule
],
declarations: [
PdfPasswordDialogComponent,
ViewerComponent,
ImgViewerComponent,
TxtViewerComponent,
@ -63,12 +68,16 @@ import { ViewerToolbarActionsComponent } from './components/viewer-toolbar-actio
ViewerMoreActionsComponent,
ViewerToolbarActionsComponent
],
entryComponents: [
PdfPasswordDialogComponent
],
exports: [
ViewerComponent,
ImgViewerComponent,
TxtViewerComponent,
MediaPlayerComponent,
PdfViewerComponent,
PdfPasswordDialogComponent,
PdfThumbComponent,
PdfThumbListComponent,
ViewerExtensionDirective,