From b37c25788544f93806febaef0102dc37ab68a702 Mon Sep 17 00:00:00 2001 From: alexm Date: Mon, 31 Oct 2022 14:13:06 +0100 Subject: [PATCH] feat(canEdit): use checkAccess --- .../00-editTrackedACL.sql | 4 +- loopback/server/boot/acl.js | 22 ++ modules/ticket/back/methods/sale/canEdit.js | 80 ++-- .../back/methods/sale/specs/canEdit.spec.js | 366 +++++++++++------- .../back/methods/sale/specs/reserve.spec.js | 4 +- .../ticket/specs/componentUpdate.spec.js | 8 +- 6 files changed, 281 insertions(+), 203 deletions(-) rename db/changes/{10491-august => 10500-november}/00-editTrackedACL.sql (52%) create mode 100644 loopback/server/boot/acl.js diff --git a/db/changes/10491-august/00-editTrackedACL.sql b/db/changes/10500-november/00-editTrackedACL.sql similarity index 52% rename from db/changes/10491-august/00-editTrackedACL.sql rename to db/changes/10500-november/00-editTrackedACL.sql index 97394fffe..c66169424 100644 --- a/db/changes/10491-august/00-editTrackedACL.sql +++ b/db/changes/10500-november/00-editTrackedACL.sql @@ -1,3 +1,5 @@ INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`) VALUES - ('Sale', 'editTracked', 'WRITE', 'ALLOW', 'ROLE', 'production'); + ('Sale', 'editTracked', 'WRITE', 'ALLOW', 'ROLE', 'production'), + ('Sale', 'editFloramondo', 'WRITE', 'ALLOW', 'ROLE', 'salesAssistant'), + ('Ticket', 'editWeekly', 'WRITE', 'DENY', 'ROLE', '$authenticated'); diff --git a/loopback/server/boot/acl.js b/loopback/server/boot/acl.js new file mode 100644 index 000000000..a23007833 --- /dev/null +++ b/loopback/server/boot/acl.js @@ -0,0 +1,22 @@ + +module.exports = function(app) { + app.models.ACL.checkAccess = async(ctx, modelId, property, accessType = '*') => { + const models = app.models; + const context = { + accessToken: ctx.req.accessToken, + model: models[modelId], + property: property, + modelId: modelId, + accessType: accessType, + sharedMethod: { + name: property, + aliases: [], + sharedClass: true + } + }; + + const acl = await models.ACL.checkAccessForContext(context); + + return acl.permission == 'ALLOW'; + }; +}; diff --git a/modules/ticket/back/methods/sale/canEdit.js b/modules/ticket/back/methods/sale/canEdit.js index 7a8523fb7..afbf70638 100644 --- a/modules/ticket/back/methods/sale/canEdit.js +++ b/modules/ticket/back/methods/sale/canEdit.js @@ -1,5 +1,4 @@ const UserError = require('vn-loopback/util/user-error'); -const loopBackCtx = require('vn-loopback/server/server'); module.exports = Self => { Self.remoteMethodCtx('canEdit', { @@ -7,7 +6,7 @@ module.exports = Self => { accessType: 'READ', accepts: [{ arg: 'sales', - type: ['object'], + type: ['number'], required: true }], returns: { @@ -16,7 +15,7 @@ module.exports = Self => { }, http: { path: `/canEdit`, - verb: 'get' + verb: 'GET' } }); @@ -27,64 +26,39 @@ module.exports = Self => { if (typeof options == 'object') Object.assign(myOptions, options); - console.log(ctx.req.accessToken); - const token = ctx.req.accessToken; - let canEditTracked = await models.ACL.checkAccessForToken(token, models.Sale, null, 'refund'); - // const newCtx = ctx; - // newCtx.property = 'refund'; - // newCtx.accessType = 'WRITE'; - // newCtx.methodNames = ['refund']; - // newCtx.model = await models.Sale; + const salesData = await models.Sale.find({ + fields: ['id', 'itemFk', 'ticketFk'], + where: {id: {inq: sales}}, + include: + { + relation: 'item', + scope: { + fields: ['id', 'isFloramondo'], + } + } + }, myOptions); - // let canEditTracked = await models.ACL.checkAccessForContext(newCtx); - console.log(canEditTracked); + const ticketId = salesData[0].ticketFk; - // let canEditTracked2 = await models.ACL.checkPermission('USER', 'developer', 'Sale', 'editTracked', 'READ'); - /* const array = ['editTracked']; - const AccessContext = loopBackCtx.AccessContext; - const toFind = { - principals: [{ - type: 'ROLE', - id: 'employee' - }], - model: 'Sale', - property: 'editTracked', - methodNames: ['editTracked'], - accessType: 'WRITE' - }; - const newContext = new AccessContext(toFind); - newContext.methodNames = ['editTracked']; + const isTicketEditable = await models.Ticket.isEditable(ctx, ticketId, myOptions); + if (!isTicketEditable) + throw new UserError(`The sales of this ticket can't be modified`); - let canEditTracked3 = await models.ACL.checkAccessForContext(newContext); + const hasSaleTracking = await models.SaleTracking.findOne({where: {saleFk: {inq: sales}}}, myOptions); + const hasSaleCloned = await models.SaleCloned.findOne({where: {saleClonedFk: {inq: sales}}}, myOptions); + const isTicketWeekly = await models.TicketWeekly.findOne({where: {ticketFk: ticketId}}, myOptions); + const hasSaleFloramondo = salesData.find(sale => sale.item().isFloramondo); - let canEditTracked4 = await models.ACL.checkAccessForContext({ - principals: [{ - type: 'ROLE', - id: 'developer' - }], - model: 'Sale', - property: 'editTracked', - methodName: 'editTracked', - methodNames: ['editTracked'], - accessType: 'WRITE' - }); - // console.log(canEditTracked); - // canEditTracked = await models.ACL.resolvePermission(canEditTracked); - // let canEditCloned = await models.ACL.checkPermission('ROLE', 'employee', 'Sale', 'editCloned', '*'); - // let canEditWeekly = await models.ACL.checkPermission('ROLE', 'employee', 'Ticket', 'editWeekly', '*'); + const canEditTracked = await models.ACL.checkAccess(ctx, 'Sale', 'editTracked'); + const canEditCloned = await models.ACL.checkAccess(ctx, 'Sale', 'editCloned'); + const canEditWeekly = await models.ACL.checkAccess(ctx, 'Ticket', 'editWeekly'); + const canEditFloramondo = await models.ACL.checkAccess(ctx, 'Sale', 'editFloramondo'); - // console.log(canEditTracked, canEditTracked2); - console.log('DENY: ', canEditTracked3.permission); - console.log('ALLOW: ', canEditTracked4.permission); const shouldEditTracked = canEditTracked || !hasSaleTracking; const shouldEditCloned = canEditCloned || !hasSaleCloned; const shouldEditWeekly = canEditWeekly || !isTicketWeekly; + const shouldEditFloramondo = canEditFloramondo || !hasSaleFloramondo; - const canEdit = shouldEditTracked && shouldEditCloned && shouldEditWeekly; - - if (canEdit) - return true; - - return false;*/ + return shouldEditTracked && shouldEditCloned && shouldEditWeekly && shouldEditFloramondo; }; }; diff --git a/modules/ticket/back/methods/sale/specs/canEdit.spec.js b/modules/ticket/back/methods/sale/specs/canEdit.spec.js index 1522ee7a3..4f66dcc87 100644 --- a/modules/ticket/back/methods/sale/specs/canEdit.spec.js +++ b/modules/ticket/back/methods/sale/specs/canEdit.spec.js @@ -1,179 +1,253 @@ const models = require('vn-loopback/server/server').models; describe('sale canEdit()', () => { - it('should return true if the role is production regardless of the saleTrackings', async() => { - const tx = await models.Sale.beginTransaction({}); + const employeeId = 1; - try { - const options = {transaction: tx}; + describe('sale editTracked', () => { + it('should return true if the role is production regardless of the saleTrackings', async() => { + const tx = await models.Sale.beginTransaction({}); - const productionUserID = 49; - const ctx = {req: {accessToken: {userId: productionUserID}}}; + try { + const options = {transaction: tx}; - const sales = [25]; + const productionUserID = 49; + const ctx = {req: {accessToken: {userId: productionUserID}}}; - const result = await models.Sale.canEdit(ctx, sales, options); + const sales = [25]; - expect(result).toEqual(true); + const result = await models.Sale.canEdit(ctx, sales, options); - await tx.rollback(); - } catch (e) { - await tx.rollback(); - throw e; - } - }); + expect(result).toEqual(true); - it('should return true if the role is not production and none of the sales has saleTracking', async() => { - const tx = await models.Sale.beginTransaction({}); - - try { - const options = {transaction: tx}; - - const salesPersonUserID = 18; - const ctx = {req: {accessToken: {userId: salesPersonUserID}}}; - - const sales = [10]; - - const result = await models.Sale.canEdit(ctx, sales, options); - - expect(result).toEqual(true); - - await tx.rollback(); - } catch (e) { - await tx.rollback(); - throw e; - } - }); - - it('should return false if any of the sales has a saleTracking record', async() => { - const tx = await models.Sale.beginTransaction({}); - - try { - const options = {transaction: tx}; - - const buyerId = 35; - const ctx = {req: {accessToken: {userId: buyerId}}}; - - const sales = [31]; - - const result = await models.Sale.canEdit(ctx, sales, options); - - expect(result).toEqual(false); - - await tx.rollback(); - } catch (e) { - await tx.rollback(); - throw e; - } - }); - - it('should return false if any of the sales is cloned', async() => { - const tx = await models.Sale.beginTransaction({}); - - try { - const options = {transaction: tx}; - - const buyerId = 35; - const ctx = {req: {accessToken: {userId: buyerId}}}; - - const sales = [27]; - - const result = await models.Sale.canEdit(ctx, sales, options); - - expect(result).toEqual(false); - - await tx.rollback(); - } catch (e) { - await tx.rollback(); - throw e; - } - }); - - it('should return true if any of the sales is cloned and has the correct role', async() => { - const tx = await models.Sale.beginTransaction({}); - const roleEnabled = await models.ACL.findOne({ - where: { - model: 'Sale', - property: 'editCloned' + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; } }); - if (!roleEnabled || !roleEnabled.principalId) return; - try { - const options = {transaction: tx}; + it('should return true if the role is not production and none of the sales has saleTracking', async() => { + const tx = await models.Sale.beginTransaction({}); - const roleId = await models.Role.findOne({ - where: { - name: roleEnabled.principalId - } - }); - const ctx = {req: {accessToken: {userId: roleId}}}; + try { + const options = {transaction: tx}; - const sales = [27]; + const salesPersonUserID = 18; + const ctx = {req: {accessToken: {userId: salesPersonUserID}}}; - const result = await models.Sale.canEdit(ctx, sales, options); + const sales = [10]; - expect(result).toEqual(true); + const result = await models.Sale.canEdit(ctx, sales, options); - await tx.rollback(); - } catch (e) { - await tx.rollback(); - throw e; - } - }); + expect(result).toEqual(true); - it('should return false if any of the sales is of ticket weekly', async() => { - const tx = await models.Sale.beginTransaction({}); - - try { - const options = {transaction: tx}; - - const employeeId = 1; - const ctx = {req: {accessToken: {userId: employeeId}}}; - - const sales = [33]; - - const result = await models.Sale.canEdit(ctx, sales, options); - - expect(result).toEqual(false); - - await tx.rollback(); - } catch (e) { - await tx.rollback(); - throw e; - } - }); - - it('should return true if any of the sales is of ticketWeekly and has the correct role', async() => { - const tx = await models.Sale.beginTransaction({}); - const roleEnabled = await models.ACL.findOne({ - where: { - model: 'Sale', - property: 'editWeekly' + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; } }); - if (!roleEnabled || !roleEnabled.principalId) return; - try { - const options = {transaction: tx}; + it('should return false if any of the sales has a saleTracking record', async() => { + const tx = await models.Sale.beginTransaction({}); - const roleId = await models.Role.findOne({ + try { + const options = {transaction: tx}; + + const buyerId = 35; + const ctx = {req: {accessToken: {userId: buyerId}}}; + + const sales = [31]; + + const result = await models.Sale.canEdit(ctx, sales, options); + + expect(result).toEqual(false); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); + }); + + describe('sale editCloned', () => { + it('should return false if any of the sales is cloned', async() => { + const tx = await models.Sale.beginTransaction({}); + + try { + const options = {transaction: tx}; + + const buyerId = 35; + const ctx = {req: {accessToken: {userId: buyerId}}}; + + const sales = [27]; + + const result = await models.Sale.canEdit(ctx, sales, options); + + expect(result).toEqual(false); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); + + it('should return true if any of the sales is cloned and has the correct role', async() => { + const tx = await models.Sale.beginTransaction({}); + const roleEnabled = await models.ACL.findOne({ where: { - name: roleEnabled.principalId + model: 'Sale', + property: 'editCloned', + permission: 'ALLOW' } }); - const ctx = {req: {accessToken: {userId: roleId}}}; + if (!roleEnabled || !roleEnabled.principalId) return await tx.rollback(); + try { + const options = {transaction: tx}; + + const role = await models.Role.findOne({ + where: { + name: roleEnabled.principalId + } + }); + const ctx = {req: {accessToken: {userId: role.id}}}; + + const sales = [27]; + + const result = await models.Sale.canEdit(ctx, sales, options); + + expect(result).toEqual(true); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); + }); + + describe('ticket editWeekly', () => { + it('should return false if any of the sales is of ticket weekly', async() => { + const tx = await models.Sale.beginTransaction({}); + + try { + const options = {transaction: tx}; + + const ctx = {req: {accessToken: {userId: employeeId}}}; + + const sales = [33]; + + const result = await models.Sale.canEdit(ctx, sales, options); + + expect(result).toEqual(false); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); + + it('should return true if any of the sales is of ticketWeekly and has the correct role', async() => { + const tx = await models.Sale.beginTransaction({}); + const roleEnabled = await models.ACL.findOne({ + where: { + model: 'Ticket', + property: 'editWeekly', + permission: 'ALLOW' + } + }); + + if (!roleEnabled || !roleEnabled.principalId) return await tx.rollback(); + try { + const options = {transaction: tx}; + + const role = await models.Role.findOne({ + where: { + name: roleEnabled.principalId + } + }); + const ctx = {req: {accessToken: {userId: role.id}}}; + + const sales = [33]; + + const result = await models.Sale.canEdit(ctx, sales, options); + + expect(result).toEqual(true); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); + }); + + describe('sale editFloramondo', () => { + it('should return false if any of the sales isFloramondo', async() => { + const tx = await models.Sale.beginTransaction({}); const sales = [33]; - const result = await models.Sale.canEdit(ctx, sales, options); + try { + const options = {transaction: tx}; - expect(result).toEqual(true); + const ctx = {req: {accessToken: {userId: employeeId}}}; - await tx.rollback(); - } catch (e) { - await tx.rollback(); - throw e; - } + // For test + const saleToEdit = await models.Sale.findById(sales[0], null, options); + await saleToEdit.updateAttribute('itemFk', 9, options); + + const result = await models.Sale.canEdit(ctx, sales, options); + + expect(result).toEqual(false); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); + + it('should return true if any of the sales is of isFloramondo and has the correct role', async() => { + const tx = await models.Sale.beginTransaction({}); + const sales = [32]; + + const roleEnabled = await models.ACL.findOne({ + where: { + model: 'Sale', + property: 'editFloramondo', + permission: 'ALLOW' + } + }); + + if (!roleEnabled || !roleEnabled.principalId) return await tx.rollback(); + + try { + const options = {transaction: tx}; + + const role = await models.Role.findOne({ + where: { + name: roleEnabled.principalId + } + }); + const ctx = {req: {accessToken: {userId: role.id}}}; + + // For test + const saleToEdit = await models.Sale.findById(sales[0], null, options); + await saleToEdit.updateAttribute('itemFk', 9, options); + + const result = await models.Sale.canEdit(ctx, sales, options); + + expect(result).toEqual(true); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); }); }); diff --git a/modules/ticket/back/methods/sale/specs/reserve.spec.js b/modules/ticket/back/methods/sale/specs/reserve.spec.js index 7c2d43715..7ab79f9c0 100644 --- a/modules/ticket/back/methods/sale/specs/reserve.spec.js +++ b/modules/ticket/back/methods/sale/specs/reserve.spec.js @@ -1,6 +1,6 @@ const models = require('vn-loopback/server/server').models; -fdescribe('sale reserve()', () => { +describe('sale reserve()', () => { const ctx = { req: { accessToken: {userId: 1}, @@ -31,7 +31,7 @@ fdescribe('sale reserve()', () => { expect(error).toEqual(new Error(`The sales of this ticket can't be modified`)); }); - fit('should update the given sales of a ticket to reserved', async() => { + it('should update the given sales of a ticket to reserved', async() => { const tx = await models.Sale.beginTransaction({}); try { diff --git a/modules/ticket/back/methods/ticket/specs/componentUpdate.spec.js b/modules/ticket/back/methods/ticket/specs/componentUpdate.spec.js index 2aa2a07c4..49128ded8 100644 --- a/modules/ticket/back/methods/ticket/specs/componentUpdate.spec.js +++ b/modules/ticket/back/methods/ticket/specs/componentUpdate.spec.js @@ -1,4 +1,5 @@ const models = require('vn-loopback/server/server').models; +const LoopBackContext = require('loopback-context'); describe('ticket componentUpdate()', () => { const userID = 1101; @@ -175,10 +176,15 @@ describe('ticket componentUpdate()', () => { } } }; + + spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({ + active: ctx.req + }); + const oldTicket = await models.Ticket.findById(ticketID, null, options); + await models.Ticket.componentUpdate(ctx, options); const [newTicketID] = await models.Ticket.rawSql('SELECT MAX(id) as id FROM ticket', null, options); - const oldTicket = await models.Ticket.findById(ticketID, null, options); const newTicket = await models.Ticket.findById(newTicketID.id, null, options); const newTicketSale = await models.Sale.findOne({where: {ticketFk: args.id}}, options);