test passed
gitea/salix/pipeline/head This commit looks good Details

This commit is contained in:
Javi Gallego 2020-04-03 17:52:47 +02:00
parent e16d1761f9
commit ea1cc68d29
17 changed files with 254 additions and 73 deletions

1
.gitignore vendored
View File

@ -5,6 +5,7 @@ e2e/dms/*/
!e2e/dms/c4c
!e2e/dms/c81
!e2e/dms/ecc
!e2e/dms/a87
npm-debug.log
.eslintcache
datasources.*.json

View File

@ -1,12 +0,0 @@
const UserError = require('vn-loopback/util/user-error');
checkRole = async function(ctx, id) {
const models = Self.app.models;
const dms = await Self.findById(id);
const hasReadRole = await models.DmsType.hasReadRole(ctx, dms.dmsTypeFk);
if (!hasReadRole)
throw new UserError(`You don't have enough privileges`);
return true;
};

View File

@ -1,7 +1,4 @@
const checkRole = require('./checkRole');
const getfile = require('./getfile');
const UserError = require('vn-loopback/util/user-error');
module.exports = Self => {
Self.remoteMethodCtx('downloadFile', {
description: 'Download a document',
@ -36,7 +33,8 @@ module.exports = Self => {
});
Self.downloadFile = async function(ctx, id) {
await checkRole(ctx, id);
return await getfile(ctx, id);
if (!await Self.checkRole(ctx, id))
throw new UserError(`You don't have enough privileges`);
return await Self.getFile(id);
};
};

View File

@ -1,29 +0,0 @@
const UserError = require('vn-loopback/util/user-error');
getFile = async function(ctx, id) {
const storageConnector = Self.app.dataSources.storage.connector;
const models = Self.app.models;
const dms = await Self.findById(id);
const hasReadRole = await models.DmsType.hasReadRole(ctx, dms.dmsTypeFk);
if (!hasReadRole)
throw new UserError(`You don't have enough privileges`);
const pathHash = storageConnector.getPathHash(dms.id);
try {
await models.Container.getFile(pathHash, dms.file);
} catch (e) {
if (e.code != 'ENOENT')
throw e;
const error = new UserError(`File doesn't exists`);
error.statusCode = 404;
throw error;
}
const stream = models.Container.downloadStream(pathHash, dms.file);
return [stream, dms.contentType, `filename="${dms.file}"`];
};

View File

@ -1,6 +1,6 @@
const app = require('vn-loopback/server/server');
fdescribe('dms downloadFile()', () => {
describe('dms downloadFile()', () => {
let dmsId = 1;
it('should return a response for an employee with text content-type', async() => {
@ -11,7 +11,7 @@ fdescribe('dms downloadFile()', () => {
expect(result[1]).toEqual('text/plain');
});
it(`should return an error for a user without enough privileges`, async() => {
it('should return an error for a user without enough privileges', async() => {
let clientId = 101;
let ctx = {req: {accessToken: {userId: clientId}}};

View File

@ -1,6 +1,37 @@
const UserError = require('vn-loopback/util/user-error');
module.exports = Self => {
require('../methods/dms/downloadFile')(Self);
require('../methods/dms/uploadFile')(Self);
require('../methods/dms/removeFile')(Self);
require('../methods/dms/updateFile')(Self);
Self.checkRole = async function(ctx, id) {
const models = Self.app.models;
const dms = await Self.findById(id);
return await models.DmsType.hasReadRole(ctx, dms.dmsTypeFk);
};
Self.getFile = async function(id) {
const storageConnector = Self.app.dataSources.storage.connector;
const models = Self.app.models;
const dms = await Self.findById(id);
const pathHash = storageConnector.getPathHash(dms.id);
try {
await models.Container.getFile(pathHash, dms.file);
} catch (e) {
if (e.code != 'ENOENT')
throw e;
const error = new UserError(`File doesn't exists`);
error.statusCode = 404;
throw error;
}
const stream = models.Container.downloadStream(pathHash, dms.file);
return [stream, dms.contentType, `filename="${dms.file}"`];
};
};

View File

@ -0,0 +1,53 @@
const app = require('vn-loopback/server/server');
describe('Dms', () => {
const Dms = app.models.Dms;
describe('getFile()', () => {
it('should return a response with text content-type', async() => {
const result = await Dms.getFile(1);
expect(result[1]).toEqual('text/plain');
});
it('should return an error for a file does not exists', async() => {
let error = {};
try {
await Dms.getFile(6);
} catch (e) {
error = e;
}
expect(error.statusCode).toBe(404);
});
it('should return an error for a record does not exists', async() => {
let error = {};
try {
await app.models.Dms.getFile('NotExistentId');
} catch (e) {
error = e;
}
expect(error.statusCode).not.toBe(404);
expect(error).toEqual(jasmine.any(Error));
});
});
describe('checkRole()', () => {
const dmsId = 1;
it('should return a true for an employee with permission', async() => {
let ctx = {req: {accessToken: {userId: 107}}};
const result = await Dms.checkRole(ctx, dmsId);
expect(result).toBeTruthy();
});
it('should return false for an employee without permission', async() => {
let ctx = {req: {accessToken: {userId: 101}}};
const result = await Dms.checkRole(ctx, dmsId);
expect(result).toBeFalsy();
});
});
});

View File

@ -0,0 +1,5 @@
INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`)
VALUES
('WorkerDms', 'filter', 'READ', 'ALLOW', 'ROLE', 'employee'),
('WorkerDms', 'downloadFile', 'READ', 'ALLOW', 'ROLE', 'employee');
DELETE FROM `salix`.`ACL` WHERE (`id` = '205');

View File

@ -1957,7 +1957,9 @@ INSERT INTO `vn`.`dms`(`id`, `dmsTypeFk`, `file`, `contentType`, `workerFk`, `wa
(2, 5, '2.txt', 'text/plain', 5, 1, 442, 1, TRUE, 'Client:104', 'Client:104 dms for the client', CURDATE()),
(3, 5, '3.txt', 'text/plain', 5, 1, 442, NULL, TRUE, 'Client: 104', 'Client:104 readme', CURDATE()),
(4, 3, '4.txt', 'text/plain', 5, 1, 442, NULL, TRUE, 'Worker: 106', 'Worker:106 readme', CURDATE()),
(5, 5, '5.txt', 'text/plain', 5, 1, 442, NULL, TRUE, 'travel: 1', 'dmsForThermograph', CURDATE());
(5, 5, '5.txt', 'text/plain', 5, 1, 442, NULL, TRUE, 'travel: 1', 'dmsForThermograph', CURDATE()),
(6, 5, '6.txt', 'text/plain', 5, 1, 442, NULL, TRUE, 'NotExists', 'DoesNotExists', CURDATE());
INSERT INTO `vn`.`ticketDms`(`ticketFk`, `dmsFk`)
VALUES
@ -1968,9 +1970,10 @@ INSERT INTO `vn`.`clientDms`(`clientFk`, `dmsFk`)
(104, 2),
(104, 3);
INSERT INTO `vn`.`workerDocument`(`id`, `worker`, `document`)
INSERT INTO `vn`.`workerDocument`(`id`, `worker`, `document`,`isReadableByWorker`)
VALUES
(1, 106, 4);
(1, 106, 4, TRUE),
(2, 107, 3, FALSE);
INSERT INTO `vn`.`device` (`sn`, `model`, `userFk`)
VALUES

1
e2e/dms/a87/4.txt Normal file
View File

@ -0,0 +1 @@
File: 4.txt. It works!

View File

@ -0,0 +1,40 @@
const UserError = require('vn-loopback/util/user-error');
module.exports = Self => {
Self.remoteMethodCtx('downloadFile', {
description: 'Download a worker document',
accessType: 'READ',
accepts: [
{
arg: 'id',
type: 'Number',
description: 'The document id',
http: {source: 'path'}
}
],
returns: [
{
arg: 'body',
type: 'file',
root: true
}, {
arg: 'Content-Type',
type: 'String',
http: {target: 'header'}
}, {
arg: 'Content-Disposition',
type: 'String',
http: {target: 'header'}
}
],
http: {
path: `/:id/downloadFile`,
verb: 'GET'
}
});
Self.downloadFile = async function(ctx, id) {
if (!await Self.app.models.Dms.checkRole(ctx, id) && !await Self.isMine(ctx, id))
throw new UserError(`You don't have enough privileges`);
return await Self.app.models.Dms.getFile(id);
};
};

View File

@ -0,0 +1,53 @@
const ParameterizedSQL = require('loopback-connector').ParameterizedSQL;
module.exports = Self => {
Self.remoteMethodCtx('filter', {
description: 'Find all instances of the model matched by filter from the data source.',
accessType: 'READ',
accepts: [
{
arg: 'filter',
type: 'Object',
description: 'Filter defining where, order, offset, and limit - must be a JSON-encoded string',
http: {source: 'query'}
}
],
returns: {
type: ['Object'],
root: true
},
http: {
path: `/filter`,
verb: 'GET'
}
});
Self.filter = async(ctx, filter) => {
const conn = Self.dataSource.connector;
const userId = ctx.req.accessToken.userId;
const account = await Self.app.models.Account.findById(userId);
const stmt = new ParameterizedSQL(
`SELECT d.id dmsFk, d.reference, d.description, d.file, d.created
FROM workerDocument wd
JOIN dms d ON d.id = wd.document
JOIN dmsType dt ON dt.id = d.dmsTypeFk
LEFT JOIN account.roleRole rr ON rr.inheritsFrom = dt.readRoleFk AND rr.role = ?
`, [account.roleFk]
);
const oldWhere = filter.where;
const yourOwnDms = {and: [{isReadableByWorker: true}, {worker: userId}]};
filter.where = {
and: [{
or: [yourOwnDms, {
role: {
neq: null
}
}]
}, oldWhere]};
stmt.merge(conn.makeSuffix(filter));
return await conn.executeStmt(stmt);
};
};

View File

@ -0,0 +1,27 @@
const app = require('vn-loopback/server/server');
describe('worker-dms downloadFile()', () => {
let dmsId = 4;
it('should return a response for an employee with text content-type', async() => {
let workerId = 106;
let ctx = {req: {accessToken: {userId: workerId}}};
const result = await app.models.WorkerDms.downloadFile(ctx, dmsId);
expect(result[1]).toEqual('text/plain');
});
it('should return an error for a user without enough privileges', async() => {
let clientId = 1;
let ctx = {req: {accessToken: {userId: clientId}}};
let error;
try {
await app.models.WorkerDms.downloadFile(ctx, dmsId);
} catch (e) {
error = e;
}
expect(error).toBeDefined();
});
});

View File

@ -125,7 +125,6 @@ module.exports = Self => {
LEFT JOIN account.emailUser mu ON mu.userFk = u.id`
);
stmt.merge(conn.makeSuffix(filter));
let itemsIndex = stmts.push(stmt) - 1;

View File

@ -1,5 +1,17 @@
module.exports = Self => {
require('../methods/worker-dms/downloadFile')(Self);
require('../methods/worker-dms/removeFile')(Self);
require('../methods/worker-dms/allowedContentTypes')(Self);
// require('../methods/worker-dms/filter')(Self);
require('../methods/worker-dms/filter')(Self);
Self.isMine = async function(ctx, dmsId) {
const myUserId = ctx.req.accessToken.userId;
let workerDms = await Self.findOne({
where: {
workerFk: myUserId,
dmsFk: dmsId
}
});
return workerDms !== null;
};
};

View File

@ -1,8 +1,7 @@
<vn-crud-model
vn-id="model"
url="filter"
link="{workerFk: $ctrl.$params.id}"
filter="::$ctrl.filter"
url="WorkerDms/filter"
link="{worker: $ctrl.$params.id}"
limit="20"
data="$ctrl.workerDms"
order="dmsFk DESC"
@ -28,28 +27,28 @@
<vn-tbody>
<vn-tr ng-repeat="document in $ctrl.workerDms">
<vn-td number shrink>{{::document.dmsFk}}</vn-td>
<vn-td shrink>
<span title="{{::document.dms.reference}}">
{{::document.dms.reference}}
<vn-td expand>
<span title="{{::document.reference}}">
{{::document.reference}}
</span>
</vn-td>
<vn-td expand>
<span title="{{::document.dms.description}}">
{{::document.dms.description}}
<span title="{{::document.description}}">
{{::document.description}}
</span>
</vn-td>
</vn-td >
<vn-td shrink>
<a target="_blank"
title="{{'Download file' | translate}}"
href="api/dms/{{::document.dmsFk}}/downloadFile?access_token={{::$ctrl.vnToken.token}}">{{::document.dms.file}}
href="api/workerDms/{{::document.dmsFk}}/downloadFile?access_token={{::$ctrl.vnToken.token}}">{{::document.file}}
</a>
</vn-td>
<vn-td>
{{::document.dms.created | date:'dd/MM/yyyy HH:mm'}}
{{::document.created | date:'dd/MM/yyyy HH:mm'}}
</vn-td>
<vn-td shrink>
<a target="_blank"
href="api/dms/{{::document.dmsFk}}/downloadFile?access_token={{::$ctrl.vnToken.token}}">
href="api/workerDms/{{::document.dmsFk}}/downloadFile?access_token={{::$ctrl.vnToken.token}}">
<vn-icon-button
icon="cloud_download"
title="{{'Download file' | translate}}">
@ -57,7 +56,7 @@
</a>
</vn-td>
<vn-td shrink>
<vn-icon-button ui-sref="worker.card.dms.edit({dmsId: {{::document.dmsFk}}})"
<vn-icon-button ui-sref="worker.card.edit({dmsId: {{::document.dmsFk}}})"
icon="edit"
title="{{'Edit file' | translate}}">
</vn-icon-button>
@ -78,7 +77,7 @@
<vn-worker-descriptor-popover
vn-id="workerDescriptor">
</vn-worker-descriptor-popover>
<a ui-sref="worker.card.dms.create"
<a ui-sref="worker.card.create"
vn-tooltip="Upload file"
vn-bind="+"
fixed-bottom-right>

View File

@ -99,7 +99,7 @@
"state": "worker.card.dms.index",
"component": "vn-worker-dms-index",
"description": "My documentation",
"acl": ["hr"]
"acl": ["employee"]
},
{
"url": "/create",