diff --git a/.gitignore b/.gitignore index 8cb76a829..f4b1ab270 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ e2e/dms/*/ !e2e/dms/c4c !e2e/dms/c81 !e2e/dms/ecc +!e2e/dms/a87 npm-debug.log .eslintcache datasources.*.json diff --git a/back/methods/dms/checkRole.js b/back/methods/dms/checkRole.js deleted file mode 100644 index b8beed3cb..000000000 --- a/back/methods/dms/checkRole.js +++ /dev/null @@ -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; -}; diff --git a/back/methods/dms/downloadFile.js b/back/methods/dms/downloadFile.js index 6f0d379aa..1b9150053 100644 --- a/back/methods/dms/downloadFile.js +++ b/back/methods/dms/downloadFile.js @@ -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); }; }; diff --git a/back/methods/dms/getFile.js b/back/methods/dms/getFile.js deleted file mode 100644 index 5c1000b69..000000000 --- a/back/methods/dms/getFile.js +++ /dev/null @@ -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}"`]; -}; diff --git a/back/methods/dms/specs/downloadFile.spec.js b/back/methods/dms/specs/downloadFile.spec.js index 43969fc1f..701a484df 100644 --- a/back/methods/dms/specs/downloadFile.spec.js +++ b/back/methods/dms/specs/downloadFile.spec.js @@ -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}}}; diff --git a/back/models/dms.js b/back/models/dms.js index 9a06928db..8be539a0f 100644 --- a/back/models/dms.js +++ b/back/models/dms.js @@ -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}"`]; + }; }; diff --git a/back/models/specs/dms.spec.js b/back/models/specs/dms.spec.js new file mode 100644 index 000000000..8e76a4956 --- /dev/null +++ b/back/models/specs/dms.spec.js @@ -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(); + }); + }); +}); + diff --git a/db/changes/10170-NOFallas/00-aclWorkerDms.sql b/db/changes/10170-NOFallas/00-aclWorkerDms.sql new file mode 100644 index 000000000..46e56f77e --- /dev/null +++ b/db/changes/10170-NOFallas/00-aclWorkerDms.sql @@ -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'); diff --git a/db/dump/fixtures.sql b/db/dump/fixtures.sql index effd1a348..e7cd2f3b1 100644 --- a/db/dump/fixtures.sql +++ b/db/dump/fixtures.sql @@ -1953,11 +1953,13 @@ INSERT INTO `vn`.`dmsType`(`id`, `name`, `path`, `readRoleFk`, `writeRoleFk`, `c INSERT INTO `vn`.`dms`(`id`, `dmsTypeFk`, `file`, `contentType`, `workerFk`, `warehouseFk`, `companyFk`, `hardCopyNumber`, `hasFile`, `reference`, `description`, `created`) VALUES - (1, 14, '1.txt', 'text/plain', 5, 1, 442, NULL, FALSE, 'Ticket:11', 'Ticket:11 dms for the ticket', CURDATE()), - (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()); + (1, 14, '1.txt', 'text/plain', 5, 1, 442, NULL, FALSE, 'Ticket:11', 'Ticket:11 dms for the ticket', CURDATE()), + (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()), + (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 diff --git a/e2e/dms/a87/4.txt b/e2e/dms/a87/4.txt new file mode 100644 index 000000000..a46bfbeda --- /dev/null +++ b/e2e/dms/a87/4.txt @@ -0,0 +1 @@ +File: 4.txt. It works! \ No newline at end of file diff --git a/modules/worker/back/methods/worker-dms/downloadFile.js b/modules/worker/back/methods/worker-dms/downloadFile.js new file mode 100644 index 000000000..cc8653e0e --- /dev/null +++ b/modules/worker/back/methods/worker-dms/downloadFile.js @@ -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); + }; +}; diff --git a/modules/worker/back/methods/worker-dms/filter.js b/modules/worker/back/methods/worker-dms/filter.js index e69de29bb..553cf2a25 100644 --- a/modules/worker/back/methods/worker-dms/filter.js +++ b/modules/worker/back/methods/worker-dms/filter.js @@ -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); + }; +}; diff --git a/modules/worker/back/methods/worker-dms/specs/downloadFile.spec.js b/modules/worker/back/methods/worker-dms/specs/downloadFile.spec.js new file mode 100644 index 000000000..27ed204ad --- /dev/null +++ b/modules/worker/back/methods/worker-dms/specs/downloadFile.spec.js @@ -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(); + }); +}); diff --git a/modules/worker/back/methods/worker/filter.js b/modules/worker/back/methods/worker/filter.js index 21462eb42..d08b27a18 100644 --- a/modules/worker/back/methods/worker/filter.js +++ b/modules/worker/back/methods/worker/filter.js @@ -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; diff --git a/modules/worker/back/models/worker-dms.js b/modules/worker/back/models/worker-dms.js index c81ec1560..4b862a81e 100644 --- a/modules/worker/back/models/worker-dms.js +++ b/modules/worker/back/models/worker-dms.js @@ -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; + }; }; diff --git a/modules/worker/front/dms/index/index.html b/modules/worker/front/dms/index/index.html index 6db824787..1bf1af61e 100644 --- a/modules/worker/front/dms/index/index.html +++ b/modules/worker/front/dms/index/index.html @@ -1,8 +1,7 @@ {{::document.dmsFk}} - - - {{::document.dms.reference}} + + + {{::document.reference}} - - {{::document.dms.description}} + + {{::document.description}} - + {{::document.dms.file}} + href="api/workerDms/{{::document.dmsFk}}/downloadFile?access_token={{::$ctrl.vnToken.token}}">{{::document.file}} - {{::document.dms.created | date:'dd/MM/yyyy HH:mm'}} + {{::document.created | date:'dd/MM/yyyy HH:mm'}} + href="api/workerDms/{{::document.dmsFk}}/downloadFile?access_token={{::$ctrl.vnToken.token}}"> @@ -57,7 +56,7 @@ - @@ -78,7 +77,7 @@ - diff --git a/modules/worker/front/routes.json b/modules/worker/front/routes.json index 39ca155ed..49156fc6d 100644 --- a/modules/worker/front/routes.json +++ b/modules/worker/front/routes.json @@ -99,7 +99,7 @@ "state": "worker.card.dms.index", "component": "vn-worker-dms-index", "description": "My documentation", - "acl": ["hr"] + "acl": ["employee"] }, { "url": "/create",