mirror of
https://github.com/Alfresco/alfresco-content-app.git
synced 2026-09-09 18:02:54 +00:00
extension api: permission fixes (#507)
* fix multiple targets * permission fixes * tests and code fixes * remove famous fdescribe * and the missing semicolon of course * even more tests
This commit is contained in:
committed by
Cilibiu Bogdan
parent
b1554b036e
commit
ff02c6acc4
@@ -0,0 +1,637 @@
|
|||||||
|
/*!
|
||||||
|
* @license
|
||||||
|
* Alfresco Example Content Application
|
||||||
|
*
|
||||||
|
* Copyright (C) 2005 - 2018 Alfresco Software Limited
|
||||||
|
*
|
||||||
|
* This file is part of the Alfresco Example Content Application.
|
||||||
|
* If the software was purchased under a paid Alfresco license, the terms of
|
||||||
|
* the paid license agreement will prevail. Otherwise, the software is
|
||||||
|
* provided under the following open source license terms:
|
||||||
|
*
|
||||||
|
* The Alfresco Example Content Application is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* The Alfresco Example Content Application is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public License
|
||||||
|
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { TestBed } from '@angular/core/testing';
|
||||||
|
import { AppTestingModule } from '../testing/app-testing.module';
|
||||||
|
import { ExtensionService } from './extension.service';
|
||||||
|
import { AppConfigService } from '@alfresco/adf-core';
|
||||||
|
import { Store } from '@ngrx/store';
|
||||||
|
import { AppStore } from '../store/states';
|
||||||
|
import { ContentActionType } from './content-action.extension';
|
||||||
|
|
||||||
|
describe('ExtensionService', () => {
|
||||||
|
let config: AppConfigService;
|
||||||
|
let extensions: ExtensionService;
|
||||||
|
let store: Store<AppStore>;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [AppTestingModule]
|
||||||
|
});
|
||||||
|
|
||||||
|
extensions = TestBed.get(ExtensionService);
|
||||||
|
store = TestBed.get(Store);
|
||||||
|
|
||||||
|
config = TestBed.get(AppConfigService);
|
||||||
|
config.config['extensions'] = {};
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('auth guards', () => {
|
||||||
|
let guard1;
|
||||||
|
let guard2;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
guard1 = {};
|
||||||
|
guard2 = {};
|
||||||
|
|
||||||
|
extensions.authGuards['guard1'] = guard1;
|
||||||
|
extensions.authGuards['guard2'] = guard2;
|
||||||
|
|
||||||
|
extensions.init();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should fetch auth guards by ids', () => {
|
||||||
|
const guards = extensions.getAuthGuards(['guard2', 'guard1']);
|
||||||
|
|
||||||
|
expect(guards.length).toBe(2);
|
||||||
|
expect(guards[0]).toEqual(guard1);
|
||||||
|
expect(guards[1]).toEqual(guard2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not fetch auth guards for missing ids', () => {
|
||||||
|
const guards = extensions.getAuthGuards(null);
|
||||||
|
expect(guards).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should fetch only known guards', () => {
|
||||||
|
const guards = extensions.getAuthGuards(['missing', 'guard1']);
|
||||||
|
|
||||||
|
expect(guards.length).toBe(1);
|
||||||
|
expect(guards[0]).toEqual(guard1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('components', () => {
|
||||||
|
let component1;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
component1 = {};
|
||||||
|
|
||||||
|
extensions.components['component-1'] = component1;
|
||||||
|
extensions.init();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should fetch registered component', () => {
|
||||||
|
const component = extensions.getComponentById('component-1');
|
||||||
|
expect(component).toEqual(component1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not fetch registered component', () => {
|
||||||
|
const component = extensions.getComponentById('missing');
|
||||||
|
expect(component).toBeFalsy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('routes', () => {
|
||||||
|
let component1, component2;
|
||||||
|
let guard1;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
config.config.extensions = {
|
||||||
|
core: {
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
id: 'aca:routes/about',
|
||||||
|
path: 'ext/about',
|
||||||
|
component: 'aca:components/about',
|
||||||
|
layout: 'aca:layouts/main',
|
||||||
|
auth: ['aca:auth'],
|
||||||
|
data: {
|
||||||
|
title: 'Custom About'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
component1 = {};
|
||||||
|
component2 = {};
|
||||||
|
extensions.components['aca:components/about'] = component1;
|
||||||
|
extensions.components['aca:layouts/main'] = component2;
|
||||||
|
|
||||||
|
guard1 = {};
|
||||||
|
extensions.authGuards['aca:auth'] = guard1;
|
||||||
|
|
||||||
|
extensions.init();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should load routes from the config', () => {
|
||||||
|
expect(extensions.routes.length).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should find a route by id', () => {
|
||||||
|
const route = extensions.getRouteById('aca:routes/about');
|
||||||
|
expect(route).toBeTruthy();
|
||||||
|
expect(route.path).toBe('ext/about');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not find a route by id', () => {
|
||||||
|
const route = extensions.getRouteById('some-route');
|
||||||
|
expect(route).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should build application routes', () => {
|
||||||
|
const routes = extensions.getApplicationRoutes();
|
||||||
|
|
||||||
|
expect(routes.length).toBe(1);
|
||||||
|
|
||||||
|
const route = routes[0];
|
||||||
|
expect(route.path).toBe('ext/about');
|
||||||
|
expect(route.component).toEqual(component2);
|
||||||
|
expect(route.canActivateChild).toEqual([guard1]);
|
||||||
|
expect(route.canActivate).toEqual([guard1]);
|
||||||
|
expect(route.children.length).toBe(1);
|
||||||
|
expect(route.children[0].path).toBe('');
|
||||||
|
expect(route.children[0].component).toEqual(component1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('actions', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
config.config.extensions = {
|
||||||
|
core: {
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
id: 'aca:actions/create-folder',
|
||||||
|
type: 'CREATE_FOLDER',
|
||||||
|
payload: 'folder-name'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should load actions from the config', () => {
|
||||||
|
extensions.init();
|
||||||
|
expect(extensions.actions.length).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should have an empty action list if config provides nothing', () => {
|
||||||
|
config.config.extensions = {};
|
||||||
|
extensions.init();
|
||||||
|
|
||||||
|
expect(extensions.actions).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should find action by id', () => {
|
||||||
|
extensions.init();
|
||||||
|
|
||||||
|
const action = extensions.getActionById(
|
||||||
|
'aca:actions/create-folder'
|
||||||
|
);
|
||||||
|
expect(action).toBeTruthy();
|
||||||
|
expect(action.type).toBe('CREATE_FOLDER');
|
||||||
|
expect(action.payload).toBe('folder-name');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not find action by id', () => {
|
||||||
|
extensions.init();
|
||||||
|
|
||||||
|
const action = extensions.getActionById('missing');
|
||||||
|
expect(action).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should run the action via store', () => {
|
||||||
|
extensions.init();
|
||||||
|
spyOn(store, 'dispatch').and.stub();
|
||||||
|
|
||||||
|
extensions.runActionById('aca:actions/create-folder');
|
||||||
|
expect(store.dispatch).toHaveBeenCalledWith({
|
||||||
|
type: 'CREATE_FOLDER',
|
||||||
|
payload: 'folder-name'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not use store if action is missing', () => {
|
||||||
|
extensions.init();
|
||||||
|
spyOn(store, 'dispatch').and.stub();
|
||||||
|
|
||||||
|
extensions.runActionById('missing');
|
||||||
|
expect(store.dispatch).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('content actions', () => {
|
||||||
|
it('should load content actions from the config', () => {
|
||||||
|
config.config.extensions = {
|
||||||
|
core: {
|
||||||
|
features: {
|
||||||
|
content: {
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
id: 'aca:toolbar/separator-1',
|
||||||
|
order: 1,
|
||||||
|
type: 'separator'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'aca:toolbar/separator-2',
|
||||||
|
order: 2,
|
||||||
|
type: 'separator'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
extensions.init();
|
||||||
|
expect(extensions.contentActions.length).toBe(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should have an empty content action list if config is empty', () => {
|
||||||
|
config.config.extensions = {};
|
||||||
|
extensions.init();
|
||||||
|
expect(extensions.contentActions).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should sort content actions by order', () => {
|
||||||
|
config.config.extensions = {
|
||||||
|
core: {
|
||||||
|
features: {
|
||||||
|
content: {
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
id: 'aca:toolbar/separator-2',
|
||||||
|
order: 2,
|
||||||
|
type: 'separator'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'aca:toolbar/separator-1',
|
||||||
|
order: 1,
|
||||||
|
type: 'separator'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
extensions.init();
|
||||||
|
expect(extensions.contentActions.length).toBe(2);
|
||||||
|
expect(extensions.contentActions[0].id).toBe(
|
||||||
|
'aca:toolbar/separator-1'
|
||||||
|
);
|
||||||
|
expect(extensions.contentActions[1].id).toBe(
|
||||||
|
'aca:toolbar/separator-2'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('open with', () => {
|
||||||
|
it('should load [open with] actions for the viewer', () => {
|
||||||
|
config.config.extensions = {
|
||||||
|
core: {
|
||||||
|
features: {
|
||||||
|
viewer: {
|
||||||
|
'open-with': [
|
||||||
|
{
|
||||||
|
disabled: false,
|
||||||
|
id: 'aca:viewer/action1',
|
||||||
|
order: 100,
|
||||||
|
icon: 'build',
|
||||||
|
title: 'Snackbar',
|
||||||
|
action: 'aca:actions/info'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
extensions.init();
|
||||||
|
expect(extensions.openWithActions.length).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should have an empty [open with] list if config is empty', () => {
|
||||||
|
config.config.extensions = {};
|
||||||
|
extensions.init();
|
||||||
|
expect(extensions.openWithActions).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should load only enabled [open with] actions for the viewer', () => {
|
||||||
|
config.config.extensions = {
|
||||||
|
core: {
|
||||||
|
features: {
|
||||||
|
viewer: {
|
||||||
|
'open-with': [
|
||||||
|
{
|
||||||
|
id: 'aca:viewer/action2',
|
||||||
|
order: 200,
|
||||||
|
icon: 'build',
|
||||||
|
title: 'Snackbar',
|
||||||
|
action: 'aca:actions/info'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
disabled: true,
|
||||||
|
id: 'aca:viewer/action1',
|
||||||
|
order: 100,
|
||||||
|
icon: 'build',
|
||||||
|
title: 'Snackbar',
|
||||||
|
action: 'aca:actions/info'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
extensions.init();
|
||||||
|
expect(extensions.openWithActions.length).toBe(1);
|
||||||
|
expect(extensions.openWithActions[0].id).toBe('aca:viewer/action2');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should sort [open with] actions by order', () => {
|
||||||
|
config.config.extensions = {
|
||||||
|
core: {
|
||||||
|
features: {
|
||||||
|
viewer: {
|
||||||
|
'open-with': [
|
||||||
|
{
|
||||||
|
id: 'aca:viewer/action2',
|
||||||
|
order: 200,
|
||||||
|
icon: 'build',
|
||||||
|
title: 'Snackbar',
|
||||||
|
action: 'aca:actions/info'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'aca:viewer/action1',
|
||||||
|
order: 100,
|
||||||
|
icon: 'build',
|
||||||
|
title: 'Snackbar',
|
||||||
|
action: 'aca:actions/info'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
extensions.init();
|
||||||
|
expect(extensions.openWithActions.length).toBe(2);
|
||||||
|
expect(extensions.openWithActions[0].id).toBe('aca:viewer/action1');
|
||||||
|
expect(extensions.openWithActions[1].id).toBe('aca:viewer/action2');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('create', () => {
|
||||||
|
it('should load [create] actions from config', () => {
|
||||||
|
config.config.extensions = {
|
||||||
|
core: {
|
||||||
|
features: {
|
||||||
|
create: [
|
||||||
|
{
|
||||||
|
disabled: false,
|
||||||
|
id: 'aca:create/folder',
|
||||||
|
order: 100,
|
||||||
|
icon: 'create_new_folder',
|
||||||
|
title: 'ext: Create Folder',
|
||||||
|
target: {
|
||||||
|
permissions: ['create'],
|
||||||
|
action: 'aca:actions/create-folder'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
extensions.init();
|
||||||
|
expect(extensions.createActions.length).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should have an empty [create] actions if config is empty', () => {
|
||||||
|
config.config.extensions = {};
|
||||||
|
extensions.init();
|
||||||
|
expect(extensions.createActions).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should sort [create] actions by order', () => {
|
||||||
|
config.config.extensions = {
|
||||||
|
core: {
|
||||||
|
features: {
|
||||||
|
create: [
|
||||||
|
{
|
||||||
|
id: 'aca:create/folder',
|
||||||
|
order: 100,
|
||||||
|
icon: 'create_new_folder',
|
||||||
|
title: 'ext: Create Folder',
|
||||||
|
target: {
|
||||||
|
permissions: ['create'],
|
||||||
|
action: 'aca:actions/create-folder'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'aca:create/folder-2',
|
||||||
|
order: 10,
|
||||||
|
icon: 'create_new_folder',
|
||||||
|
title: 'ext: Create Folder',
|
||||||
|
target: {
|
||||||
|
permissions: ['create'],
|
||||||
|
action: 'aca:actions/create-folder'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
extensions.init();
|
||||||
|
expect(extensions.createActions.length).toBe(2);
|
||||||
|
expect(extensions.createActions[0].id).toBe('aca:create/folder-2');
|
||||||
|
expect(extensions.createActions[1].id).toBe('aca:create/folder');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('expressions', () => {
|
||||||
|
it('should eval static value', () => {
|
||||||
|
const value = extensions.runExpression('hello world');
|
||||||
|
expect(value).toBe('hello world');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should eval string as an expression', () => {
|
||||||
|
const value = extensions.runExpression('$( "hello world" )');
|
||||||
|
expect(value).toBe('hello world');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should eval expression with no context', () => {
|
||||||
|
const value = extensions.runExpression('$( 1 + 1 )');
|
||||||
|
expect(value).toBe(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should eval expression with context', () => {
|
||||||
|
const context = {
|
||||||
|
a: 'hey',
|
||||||
|
b: 'there'
|
||||||
|
};
|
||||||
|
const expression = '$( context.a + " " + context.b + "!" )';
|
||||||
|
const value = extensions.runExpression(expression, context);
|
||||||
|
expect(value).toBe('hey there!');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('permissions', () => {
|
||||||
|
it('should approve node permission', () => {
|
||||||
|
const node: any = {
|
||||||
|
allowableOperations: ['create']
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(extensions.nodeHasPermission(node, 'create')).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not approve node permission', () => {
|
||||||
|
const node: any = {
|
||||||
|
allowableOperations: ['create']
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(extensions.nodeHasPermission(node, 'update')).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not approve node permission when node missing property', () => {
|
||||||
|
const node: any = {
|
||||||
|
allowableOperations: null
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(extensions.nodeHasPermission(node, 'update')).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should require node to check permission', () => {
|
||||||
|
expect(extensions.nodeHasPermission(null, 'create')).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should require permission value to check', () => {
|
||||||
|
const node: any = {
|
||||||
|
allowableOperations: ['create']
|
||||||
|
};
|
||||||
|
expect(extensions.nodeHasPermission(node, null)).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should approve multiple permissions', () => {
|
||||||
|
const node: any = {
|
||||||
|
allowableOperations: ['create', 'update', 'delete']
|
||||||
|
};
|
||||||
|
expect(
|
||||||
|
extensions.nodeHasPermissions(node, ['create', 'delete'])
|
||||||
|
).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should require node to check multiple permissions', () => {
|
||||||
|
expect(extensions.nodeHasPermissions(null, ['create'])).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should require multiple permissions to check', () => {
|
||||||
|
const node: any = {
|
||||||
|
allowableOperations: ['create', 'update', 'delete']
|
||||||
|
};
|
||||||
|
expect(extensions.nodeHasPermissions(node, null)).toBeFalsy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('sorting', () => {
|
||||||
|
it('should sort by provided order', () => {
|
||||||
|
const sorted = [
|
||||||
|
{ id: '1', order: 10 },
|
||||||
|
{ id: '2', order: 1 },
|
||||||
|
{ id: '3', order: 5 }
|
||||||
|
].sort(extensions.sortByOrder);
|
||||||
|
|
||||||
|
expect(sorted[0].id).toBe('2');
|
||||||
|
expect(sorted[1].id).toBe('3');
|
||||||
|
expect(sorted[2].id).toBe('1');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should use implicit order', () => {
|
||||||
|
const sorted = [
|
||||||
|
{ id: '3'},
|
||||||
|
{ id: '2' },
|
||||||
|
{ id: '1', order: 1 }
|
||||||
|
].sort(extensions.sortByOrder);
|
||||||
|
|
||||||
|
expect(sorted[0].id).toBe('1');
|
||||||
|
expect(sorted[1].id).toBe('3');
|
||||||
|
expect(sorted[2].id).toBe('2');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('filtering', () => {
|
||||||
|
it('should filter out disabled items', () => {
|
||||||
|
const items = [
|
||||||
|
{ id: 1, disabled: true },
|
||||||
|
{ id: 2 },
|
||||||
|
{ id: 3, disabled: true }
|
||||||
|
].filter(extensions.filterEnabled);
|
||||||
|
|
||||||
|
expect(items.length).toBe(1);
|
||||||
|
expect(items[0].id).toBe(2);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should reduce duplicate separators', () => {
|
||||||
|
const actions = [
|
||||||
|
{ id: '1', type: ContentActionType.button },
|
||||||
|
{ id: '2', type: ContentActionType.separator },
|
||||||
|
{ id: '3', type: ContentActionType.separator },
|
||||||
|
{ id: '4', type: ContentActionType.separator },
|
||||||
|
{ id: '5', type: ContentActionType.button }
|
||||||
|
];
|
||||||
|
|
||||||
|
const result = actions.reduce(extensions.reduceSeparators, []);
|
||||||
|
expect(result.length).toBe(3);
|
||||||
|
expect(result[0].id).toBe('1');
|
||||||
|
expect(result[1].id).toBe('2');
|
||||||
|
expect(result[2].id).toBe('5');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should trim trailing separators', () => {
|
||||||
|
const actions = [
|
||||||
|
{ id: '1', type: ContentActionType.button },
|
||||||
|
{ id: '2', type: ContentActionType.separator }
|
||||||
|
];
|
||||||
|
|
||||||
|
const result = actions.reduce(extensions.reduceSeparators, []);
|
||||||
|
expect(result.length).toBe(1);
|
||||||
|
expect(result[0].id).toBe('1');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should reduce empty menus', () => {
|
||||||
|
const actions = [
|
||||||
|
<any>{ id: '1', type: ContentActionType.button },
|
||||||
|
{
|
||||||
|
id: '2',
|
||||||
|
type: ContentActionType.menu
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '3',
|
||||||
|
type: ContentActionType.menu,
|
||||||
|
children: [{ id: '3-1', type: ContentActionType.button }]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const result = actions.reduce(extensions.reduceEmptyMenus, []);
|
||||||
|
|
||||||
|
expect(result.length).toBe(2);
|
||||||
|
expect(result[0].id).toBe('1');
|
||||||
|
expect(result[1].id).toBe('3');
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -27,7 +27,10 @@ import { Injectable, Type } from '@angular/core';
|
|||||||
import { RouteExtension } from './route.extension';
|
import { RouteExtension } from './route.extension';
|
||||||
import { ActionExtension } from './action.extension';
|
import { ActionExtension } from './action.extension';
|
||||||
import { AppConfigService } from '@alfresco/adf-core';
|
import { AppConfigService } from '@alfresco/adf-core';
|
||||||
import { ContentActionExtension, ContentActionType } from './content-action.extension';
|
import {
|
||||||
|
ContentActionExtension,
|
||||||
|
ContentActionType
|
||||||
|
} from './content-action.extension';
|
||||||
import { OpenWithExtension } from './open-with.extension';
|
import { OpenWithExtension } from './open-with.extension';
|
||||||
import { AppStore } from '../store/states';
|
import { AppStore } from '../store/states';
|
||||||
import { Store } from '@ngrx/store';
|
import { Store } from '@ngrx/store';
|
||||||
@@ -133,8 +136,10 @@ export class ExtensionService {
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
getAuthGuards(ids: string[] = []): Array<Type<{}>> {
|
getAuthGuards(ids: string[]): Array<Type<{}>> {
|
||||||
return ids.map(id => this.authGuards[id]);
|
return (ids || [])
|
||||||
|
.map(id => this.authGuards[id])
|
||||||
|
.filter(guard => guard);
|
||||||
}
|
}
|
||||||
|
|
||||||
getComponentById(id: string): Type<{}> {
|
getComponentById(id: string): Type<{}> {
|
||||||
@@ -207,15 +212,25 @@ export class ExtensionService {
|
|||||||
return this.contentActions
|
return this.contentActions
|
||||||
.filter(this.filterEnabled)
|
.filter(this.filterEnabled)
|
||||||
.filter(action => this.filterByTarget(nodes, action))
|
.filter(action => this.filterByTarget(nodes, action))
|
||||||
.filter(action => this.filterByPermission(nodes, action, parentNode))
|
.filter(action =>
|
||||||
|
this.filterByPermission(nodes, action, parentNode)
|
||||||
|
)
|
||||||
.reduce(this.reduceSeparators, [])
|
.reduce(this.reduceSeparators, [])
|
||||||
.map(action => {
|
.map(action => {
|
||||||
if (action.type === ContentActionType.menu) {
|
if (action.type === ContentActionType.menu) {
|
||||||
const copy = this.copyAction(action);
|
const copy = this.copyAction(action);
|
||||||
if (copy.children && copy.children.length > 0) {
|
if (copy.children && copy.children.length > 0) {
|
||||||
copy.children = copy.children
|
copy.children = copy.children
|
||||||
.filter(childAction => this.filterByTarget(nodes, childAction))
|
.filter(childAction =>
|
||||||
.filter(childAction => this.filterByPermission(nodes, childAction, parentNode))
|
this.filterByTarget(nodes, childAction)
|
||||||
|
)
|
||||||
|
.filter(childAction =>
|
||||||
|
this.filterByPermission(
|
||||||
|
nodes,
|
||||||
|
childAction,
|
||||||
|
parentNode
|
||||||
|
)
|
||||||
|
)
|
||||||
.reduce(this.reduceSeparators, []);
|
.reduce(this.reduceSeparators, []);
|
||||||
}
|
}
|
||||||
return copy;
|
return copy;
|
||||||
@@ -225,66 +240,7 @@ export class ExtensionService {
|
|||||||
.reduce(this.reduceEmptyMenus, []);
|
.reduce(this.reduceEmptyMenus, []);
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo: support multiple selected nodes
|
reduceSeparators(
|
||||||
private filterByPermission(
|
|
||||||
nodes: MinimalNodeEntity[],
|
|
||||||
action: ContentActionExtension,
|
|
||||||
parentNode: Node
|
|
||||||
): boolean {
|
|
||||||
if (!action) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!action.target) {
|
|
||||||
return action.type === ContentActionType.separator
|
|
||||||
|| action.type === ContentActionType.menu;
|
|
||||||
}
|
|
||||||
|
|
||||||
const permissions = action.target.permissions;
|
|
||||||
|
|
||||||
if (!permissions || permissions.length === 0) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return permissions.some(permission => {
|
|
||||||
if (permission.startsWith('parent.')) {
|
|
||||||
if (parentNode) {
|
|
||||||
const parentQuery = permission.split('.')[1];
|
|
||||||
return this.nodeHasPermissions(parentNode, [parentQuery]);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nodes && nodes.length > 0) {
|
|
||||||
return this.nodeHasPermissions(
|
|
||||||
nodes[0].entry,
|
|
||||||
permissions
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private nodeHasPermissions(
|
|
||||||
node: Node,
|
|
||||||
permissions: string[] = []
|
|
||||||
): boolean {
|
|
||||||
if (
|
|
||||||
node &&
|
|
||||||
node.allowableOperations &&
|
|
||||||
node.allowableOperations.length > 0
|
|
||||||
) {
|
|
||||||
return permissions.some(permission =>
|
|
||||||
node.allowableOperations.includes(permission)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private reduceSeparators(
|
|
||||||
acc: ContentActionExtension[],
|
acc: ContentActionExtension[],
|
||||||
el: ContentActionExtension,
|
el: ContentActionExtension,
|
||||||
i: number,
|
i: number,
|
||||||
@@ -299,17 +255,19 @@ export class ExtensionService {
|
|||||||
) {
|
) {
|
||||||
return acc;
|
return acc;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
// remove trailing separator
|
// remove trailing separator
|
||||||
if (i === arr.length - 1) {
|
if (i === arr.length - 1) {
|
||||||
if (el.type === ContentActionType.separator) {
|
if (el.type === ContentActionType.separator) {
|
||||||
return acc;
|
return acc;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return acc.concat(el);
|
return acc.concat(el);
|
||||||
}
|
}
|
||||||
|
|
||||||
private reduceEmptyMenus(
|
reduceEmptyMenus(
|
||||||
acc: ContentActionExtension[],
|
acc: ContentActionExtension[],
|
||||||
el: ContentActionExtension
|
el: ContentActionExtension
|
||||||
): ContentActionExtension[] {
|
): ContentActionExtension[] {
|
||||||
@@ -321,7 +279,7 @@ export class ExtensionService {
|
|||||||
return acc.concat(el);
|
return acc.concat(el);
|
||||||
}
|
}
|
||||||
|
|
||||||
private sortByOrder(
|
sortByOrder(
|
||||||
a: { order?: number | undefined },
|
a: { order?: number | undefined },
|
||||||
b: { order?: number | undefined }
|
b: { order?: number | undefined }
|
||||||
) {
|
) {
|
||||||
@@ -330,20 +288,20 @@ export class ExtensionService {
|
|||||||
return left - right;
|
return left - right;
|
||||||
}
|
}
|
||||||
|
|
||||||
private filterEnabled(entry: { disabled?: boolean }): boolean {
|
filterEnabled(entry: { disabled?: boolean }): boolean {
|
||||||
return !entry.disabled;
|
return !entry.disabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
private copyAction(
|
copyAction(action: ContentActionExtension): ContentActionExtension {
|
||||||
action: ContentActionExtension
|
|
||||||
): ContentActionExtension {
|
|
||||||
return {
|
return {
|
||||||
...action,
|
...action,
|
||||||
children: (action.children || []).map(child => this.copyAction(child))
|
children: (action.children || []).map(child =>
|
||||||
|
this.copyAction(child)
|
||||||
|
)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private filterByTarget(
|
filterByTarget(
|
||||||
nodes: MinimalNodeEntity[],
|
nodes: MinimalNodeEntity[],
|
||||||
action: ContentActionExtension
|
action: ContentActionExtension
|
||||||
): boolean {
|
): boolean {
|
||||||
@@ -358,9 +316,9 @@ export class ExtensionService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const types = action.target.types;
|
const types = action.target.types || [];
|
||||||
|
|
||||||
if (!types || types.length === 0) {
|
if (types.length === 0) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -369,12 +327,14 @@ export class ExtensionService {
|
|||||||
if (type === 'folder') {
|
if (type === 'folder') {
|
||||||
return action.target.multiple
|
return action.target.multiple
|
||||||
? nodes.some(node => node.entry.isFolder)
|
? nodes.some(node => node.entry.isFolder)
|
||||||
: nodes.every(node => node.entry.isFolder);
|
: nodes.length === 1 &&
|
||||||
|
nodes.every(node => node.entry.isFolder);
|
||||||
}
|
}
|
||||||
if (type === 'file') {
|
if (type === 'file') {
|
||||||
return action.target.multiple
|
return action.target.multiple
|
||||||
? nodes.some(node => node.entry.isFile)
|
? nodes.some(node => node.entry.isFile)
|
||||||
: nodes.every(node => node.entry.isFile);
|
: nodes.length === 1 &&
|
||||||
|
nodes.every(node => node.entry.isFile);
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
@@ -382,4 +342,67 @@ export class ExtensionService {
|
|||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
filterByPermission(
|
||||||
|
nodes: MinimalNodeEntity[],
|
||||||
|
action: ContentActionExtension,
|
||||||
|
parentNode: Node
|
||||||
|
): boolean {
|
||||||
|
if (!action) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!action.target) {
|
||||||
|
return (
|
||||||
|
action.type === ContentActionType.separator ||
|
||||||
|
action.type === ContentActionType.menu
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const permissions = action.target.permissions || [];
|
||||||
|
|
||||||
|
if (permissions.length === 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return permissions.some(permission => {
|
||||||
|
if (permission.startsWith('parent.')) {
|
||||||
|
if (parentNode) {
|
||||||
|
const parentQuery = permission.split('.')[1];
|
||||||
|
return this.nodeHasPermission(parentNode, parentQuery);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nodes && nodes.length > 0) {
|
||||||
|
return action.target.multiple
|
||||||
|
? nodes.some(node =>
|
||||||
|
this.nodeHasPermission(node.entry, permission)
|
||||||
|
)
|
||||||
|
: nodes.length === 1 &&
|
||||||
|
nodes.every(node =>
|
||||||
|
this.nodeHasPermission(node.entry, permission)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
nodeHasPermissions(node: Node, permissions: string[]): boolean {
|
||||||
|
if (node && permissions && permissions.length > 0) {
|
||||||
|
return permissions.some(permission =>
|
||||||
|
this.nodeHasPermission(node, permission)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
nodeHasPermission(node: Node, permission: string): boolean {
|
||||||
|
if (node && permission) {
|
||||||
|
const allowableOperations = node.allowableOperations || [];
|
||||||
|
return allowableOperations.includes(permission);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user