DocumentList - Check permissions on delete folder/file (#1808)

* Add check permission on delete folder/file

* Provide a way to disable the action when if there is no permission

* Improve the code using the external permission inside the folder/document action service

* Add basic documentation.
 - How disable the button when the permission is missing
 - How to show a notification message when the permission is missing

* Resize the image

* Change the value to true for demo purpose

* Update folder-actions.service.ts

* Update document-actions.service.ts
This commit is contained in:
Maurizio Vitale
2017-04-11 13:43:33 +01:00
committed by Mario Romano
parent ab3d18e5c1
commit f6102dfc07
20 changed files with 460 additions and 32 deletions

View File

@@ -41,9 +41,18 @@ export class ContentActionComponent implements OnInit, OnChanges {
@Input()
target: string;
@Input()
permission: string;
@Input()
disableWithNoPermission: boolean;
@Output()
execute = new EventEmitter();
@Output()
permissionEvent = new EventEmitter();
model: ContentActionModel;
constructor(
@@ -57,6 +66,8 @@ export class ContentActionComponent implements OnInit, OnChanges {
this.model = new ContentActionModel({
title: this.title,
icon: this.icon,
permission: this.permission,
disableWithNoPermission: this.disableWithNoPermission,
target: this.target
});
@@ -98,6 +109,9 @@ export class ContentActionComponent implements OnInit, OnChanges {
if (ltarget === 'folder') {
if (this.folderActions) {
this.folderActions.permissionEvent.subscribe((permision) => {
this.permissionEvent.emit(permision);
});
return this.folderActions.getHandler(name);
}
return null;

View File

@@ -86,7 +86,22 @@ describe('DocumentList', () => {
spyOn(action, 'handler').and.stub();
documentList.executeContentAction(node, action);
expect(action.handler).toHaveBeenCalledWith(node, documentList);
expect(action.handler).toHaveBeenCalledWith(node, documentList, undefined);
});
it('should execute action with node and permission', () => {
let node = new FileNode();
let action = new ContentActionModel();
action.handler = function () {
console.log('mock handler');
};
action.permission = 'fake-permission';
spyOn(action, 'handler').and.stub();
documentList.executeContentAction(node, action);
expect(action.handler).toHaveBeenCalledWith(node, documentList, 'fake-permission');
});

View File

@@ -306,7 +306,7 @@ export class DocumentListComponent implements OnInit, OnChanges, AfterContentIni
*/
executeContentAction(node: MinimalNodeEntity, action: ContentActionModel) {
if (node && node.entry && action) {
action.handler(node, this);
action.handler(node, this, action.permission);
}
}

View File

@@ -0,0 +1,28 @@
/*!
* @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 { PermissionModel } from './../models/permissions.model';
export class PermissionErrorEvent {
readonly error: PermissionModel;
constructor(error: PermissionModel) {
this.error = error;
}
}

View File

@@ -20,6 +20,8 @@ export class ContentActionModel {
title: string;
handler: ContentActionHandler;
target: string;
permission: string;
disableWithNoPermission: boolean;
constructor(obj?: any) {
if (obj) {
@@ -27,12 +29,14 @@ export class ContentActionModel {
this.title = obj.title;
this.handler = obj.handler;
this.target = obj.target;
this.permission = obj.permission;
this.disableWithNoPermission = obj.disableWithNoPermission;
}
}
}
export interface ContentActionHandler {
(obj: any, target?: any): any;
(obj: any, target?: any, permission?: string): any;
}
export class DocumentActionModel extends ContentActionModel {

View File

@@ -0,0 +1,30 @@
/*!
* @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.
*/
export class PermissionModel {
type: string;
action: string;
permission: string;
constructor(obj?: any) {
if (obj) {
this.type = obj.type || null;
this.action = obj.action || null;
this.permission = obj.permission || null;
}
}
}

View File

@@ -101,6 +101,62 @@ describe('DocumentActionsService', () => {
expect(service.getHandler('delete')).toBeDefined();
});
it('should not delete the file node if there are no permissions', (done) => {
spyOn(documentListService, 'deleteNode').and.callThrough();
service.permissionEvent.subscribe((permission) => {
expect(permission).toBeDefined();
expect(permission.type).toEqual('content');
expect(permission.action).toEqual('delete');
done();
});
let file = new FileNode();
service.getHandler('delete')(file);
});
it('should delete the file node if there is the delete permission', () => {
spyOn(documentListService, 'deleteNode').and.callThrough();
let permission = 'delete';
let file = new FileNode();
let fileWithPermission: any = file;
fileWithPermission.entry.allowableOperations = [permission];
service.getHandler('delete')(fileWithPermission, null, permission);
expect(documentListService.deleteNode).toHaveBeenCalledWith(file.entry.id);
});
it('should not delete the file node if there is no delete permission', (done) => {
spyOn(documentListService, 'deleteNode').and.callThrough();
service.permissionEvent.subscribe((permission) => {
expect(permission).toBeDefined();
expect(permission.type).toEqual('content');
expect(permission.action).toEqual('delete');
done();
});
let permission = 'delete';
let file = new FileNode();
let fileWithPermission: any = file;
fileWithPermission.entry.allowableOperations = ['create', 'update'];
service.getHandler('delete')(fileWithPermission, null, permission);
});
it('should delete the file node if there is the delete and others permission ', () => {
spyOn(documentListService, 'deleteNode').and.callThrough();
let permission = 'delete';
let file = new FileNode();
let fileWithPermission: any = file;
fileWithPermission.entry.allowableOperations = ['create', 'update', permission];
service.getHandler('delete')(fileWithPermission, null, permission);
expect(documentListService.deleteNode).toHaveBeenCalledWith(file.entry.id);
});
it('should register download action', () => {
expect(service.getHandler('download')).toBeDefined();
});
@@ -152,8 +208,11 @@ describe('DocumentActionsService', () => {
it('should delete file node', () => {
spyOn(documentListService, 'deleteNode').and.callThrough();
let permission = 'delete';
let file = new FileNode();
service.getHandler('delete')(file);
let fileWithPermission: any = file;
fileWithPermission.entry.allowableOperations = [permission];
service.getHandler('delete')(fileWithPermission, null, permission);
expect(documentListService.deleteNode).toHaveBeenCalledWith(file.entry.id);
});
@@ -165,8 +224,11 @@ describe('DocumentActionsService', () => {
service.getHandler('delete')(folder);
expect(documentListService.deleteNode).not.toHaveBeenCalled();
let permission = 'delete';
let file = new FileNode();
service.getHandler('delete')(file);
let fileWithPermission: any = file;
fileWithPermission.entry.allowableOperations = [permission];
service.getHandler('delete')(fileWithPermission, null, permission);
expect(documentListService.deleteNode).toHaveBeenCalled();
});
@@ -184,8 +246,11 @@ describe('DocumentActionsService', () => {
spyOn(documentListService, 'deleteNode').and.callThrough();
let target = jasmine.createSpyObj('obj', ['reload']);
let permission = 'delete';
let file = new FileNode();
service.getHandler('delete')(file, target);
let fileWithPermission: any = file;
fileWithPermission.entry.allowableOperations = [permission];
service.getHandler('delete')(fileWithPermission, target, permission);
expect(documentListService.deleteNode).toHaveBeenCalled();
expect(target.reload).toHaveBeenCalled();

View File

@@ -19,9 +19,14 @@ import { Injectable } from '@angular/core';
import { ContentActionHandler } from '../models/content-action.model';
import { DocumentListService } from './document-list.service';
import { AlfrescoContentService } from 'ng2-alfresco-core';
import { PermissionModel } from '../models/permissions.model';
import { Subject } from 'rxjs/Rx';
@Injectable()
export class DocumentActionsService {
permissionEvent: Subject<PermissionModel> = new Subject<PermissionModel>();
private handlers: { [id: string]: ContentActionHandler; } = {};
constructor(private documentListService?: DocumentListService,
@@ -82,13 +87,28 @@ export class DocumentActionsService {
return false;
}
private deleteNode(obj: any, target?: any) {
if (this.canExecuteAction(obj) && obj.entry && obj.entry.id) {
this.documentListService.deleteNode(obj.entry.id).subscribe(() => {
if (target && typeof target.reload === 'function') {
target.reload();
}
});
private deleteNode(obj: any, target?: any, permission?: string) {
if (this.canExecuteAction(obj)) {
if (this.hasPermission(obj.entry, permission)) {
this.documentListService.deleteNode(obj.entry.id).subscribe(() => {
if (target && typeof target.reload === 'function') {
target.reload();
}
});
} else {
this.permissionEvent.next(new PermissionModel({type: 'content', action: 'delete', permission: permission}));
}
}
}
private hasPermission(node: any, permission: string): boolean {
if (this.hasPermissions(node)) {
return node.allowableOperations.find(permision => permision === permission) ? true : false;
}
return false;
}
private hasPermissions(node: any): boolean {
return node && node.allowableOperations ? true : false;
}
}

View File

@@ -72,7 +72,7 @@ export class DocumentListService {
let params: any = {
includeSource: true,
include: ['path', 'properties']
include: ['path', 'properties', 'allowableOperations']
};
if (folder) {
@@ -121,7 +121,7 @@ export class DocumentListService {
getFolderNode(nodeId: string): Promise<MinimalNodeEntryEntity> {
let opts: any = {
includeSource: true,
include: ['path', 'properties']
include: ['path', 'properties', 'allowableOperations']
};
let nodes: any = this.apiService.getInstance().nodes;

View File

@@ -97,24 +97,73 @@ describe('FolderActionsService', () => {
expect(service.getHandler('delete')).toBeDefined();
});
it('should delete folder node', () => {
it('should not delete the folder node if there are no permissions', (done) => {
spyOn(documentListService, 'deleteNode').and.callThrough();
service.permissionEvent.subscribe((permission) => {
expect(permission).toBeDefined();
expect(permission.type).toEqual('folder');
expect(permission.action).toEqual('delete');
done();
});
let folder = new FolderNode();
service.getHandler('delete')(folder);
});
it('should delete the folder node if there is the delete permission', () => {
spyOn(documentListService, 'deleteNode').and.callThrough();
let permission = 'delete';
let folder = new FolderNode();
let folderWithPermission: any = folder;
folderWithPermission.entry.allowableOperations = [ permission ];
service.getHandler('delete')(folderWithPermission, null, permission);
expect(documentListService.deleteNode).toHaveBeenCalledWith(folder.entry.id);
});
it('should not delete the folder node if there is no delete permission', (done) => {
spyOn(documentListService, 'deleteNode').and.callThrough();
service.permissionEvent.subscribe((permission) => {
expect(permission).toBeDefined();
expect(permission.type).toEqual('folder');
expect(permission.action).toEqual('delete');
done();
});
let folder = new FolderNode();
let folderWithPermission: any = folder;
folderWithPermission.entry.allowableOperations = ['create', 'update'];
service.getHandler('delete')(folderWithPermission);
});
it('should delete the folder node if there is the delete and others permission ', () => {
spyOn(documentListService, 'deleteNode').and.callThrough();
let permission = 'delete';
let folder = new FolderNode();
let folderWithPermission: any = folder;
folderWithPermission.entry.allowableOperations = ['create', 'update', permission];
service.getHandler('delete')(folderWithPermission, null, permission);
expect(documentListService.deleteNode).toHaveBeenCalledWith(folder.entry.id);
});
it('should support deletion only folder node', () => {
spyOn(documentListService, 'deleteNode').and.callThrough();
let permission = 'delete';
let file = new FileNode();
service.getHandler('delete')(file);
expect(documentListService.deleteNode).not.toHaveBeenCalled();
let folder = new FolderNode();
service.getHandler('delete')(folder);
let folderWithPermission: any = folder;
folderWithPermission.entry.allowableOperations = [permission];
service.getHandler('delete')(folderWithPermission, null, permission);
expect(documentListService.deleteNode).toHaveBeenCalled();
});
@@ -131,9 +180,13 @@ describe('FolderActionsService', () => {
it('should reload target upon node deletion', () => {
spyOn(documentListService, 'deleteNode').and.callThrough();
let permission = 'delete';
let target = jasmine.createSpyObj('obj', ['reload']);
let folder = new FolderNode();
service.getHandler('delete')(folder, target);
let folderWithPermission: any = folder;
folderWithPermission.entry.allowableOperations = [permission];
service.getHandler('delete')(folderWithPermission, target, permission);
expect(documentListService.deleteNode).toHaveBeenCalled();
expect(target.reload).toHaveBeenCalled();

View File

@@ -17,10 +17,15 @@
import { Injectable } from '@angular/core';
import { ContentActionHandler } from '../models/content-action.model';
import { PermissionModel } from '../models/permissions.model';
import { DocumentListService } from './document-list.service';
import { Subject } from 'rxjs/Rx';
@Injectable()
export class FolderActionsService {
permissionEvent: Subject<PermissionModel> = new Subject<PermissionModel>();
private handlers: { [id: string]: ContentActionHandler; } = {};
constructor(private documentListService?: DocumentListService) {
@@ -66,13 +71,28 @@ export class FolderActionsService {
window.alert('standard folder action 2');
}
private deleteNode(obj: any, target?: any) {
if (this.canExecuteAction(obj) && obj.entry && obj.entry.id) {
this.documentListService.deleteNode(obj.entry.id).subscribe(() => {
if (target && typeof target.reload === 'function') {
target.reload();
}
});
private deleteNode(obj: any, target?: any, permission?: string) {
if (this.canExecuteAction(obj)) {
if (this.hasPermission(obj.entry, permission)) {
this.documentListService.deleteNode(obj.entry.id).subscribe(() => {
if (target && typeof target.reload === 'function') {
target.reload();
}
});
} else {
this.permissionEvent.next(new PermissionModel({type: 'folder', action: 'delete', permission: permission}));
}
}
}
private hasPermission(node: any, permissionToCheck: string): boolean {
if (this.hasPermissions(node)) {
return node.allowableOperations.find(permision => permision === permissionToCheck) ? true : false;
}
return false;
}
private hasPermissions(node: any): boolean {
return node && node.allowableOperations ? true : false;
}
}