diff --git a/sci-log-db/src/__tests__/acceptance/basesnippet.controller.acceptance.ts b/sci-log-db/src/__tests__/acceptance/basesnippet.controller.acceptance.ts index fd1bbf92..8e292774 100644 --- a/sci-log-db/src/__tests__/acceptance/basesnippet.controller.acceptance.ts +++ b/sci-log-db/src/__tests__/acceptance/basesnippet.controller.acceptance.ts @@ -4,10 +4,13 @@ import {SciLogDbApplication} from '../..'; import { clearDatabase, createAdminToken, + createAUser, + createToken, createUserToken, setupApplication, } from './test-helper'; import _ from 'lodash'; +import {arrayOfUniqueFrom} from '../../utils/misc'; describe('Basesnippet', function (this: Suite) { this.timeout(5000); @@ -16,7 +19,6 @@ describe('Basesnippet', function (this: Suite) { let token: string; let adminToken: string; let baseSnippetId: string; - let nonVisibleSnippetId: string; const baseSnippet = { ownerGroup: 'basesnippetAcceptance', createACL: ['basesnippetAcceptance'], @@ -60,11 +62,14 @@ describe('Basesnippet', function (this: Suite) { .expect(200) .then( result => ( - expect(result.body).to.containEql(baseSnippet), + expect(result.body).to.containEql(_.omit(baseSnippet, 'updateACL')), expect(result.body.snippetType).to.be.eql('base'), expect(result.body.readACL).to.be.eql(['basesnippetAcceptance']), expect(result.body.createACL).to.be.eql(['basesnippetAcceptance']), - expect(result.body.updateACL).to.be.eql(['basesnippetAcceptance']), + expect(result.body.updateACL).to.be.eql([ + 'basesnippetAcceptance', + 'test@loopback.io', + ]), expect(result.body.deleteACL).to.be.eql(['basesnippetAcceptance']), expect(result.body.adminACL).to.be.eql(['admin']), (baseSnippetId = result.body.id) @@ -110,7 +115,13 @@ describe('Basesnippet', function (this: Suite) { .then( result => ( expect(result.body.length).to.be.eql(1), - expect(result.body[0]).to.containEql(baseSnippet) + expect(result.body[0]).to.containEql( + _.omit(baseSnippet, 'updateACL'), + ), + expect(result.body[0].updateACL).to.be.eql([ + 'basesnippetAcceptance', + 'test@loopback.io', + ]) ), ) .catch(err => { @@ -131,7 +142,15 @@ describe('Basesnippet', function (this: Suite) { .set('Authorization', 'Bearer ' + token) .set('Content-Type', 'application/json') .expect(200) - .then(result => expect(result.body).to.containEql(baseSnippet)) + .then( + result => ( + expect(result.body).to.containEql(_.omit(baseSnippet, 'updateACL')), + expect(result.body.updateACL).to.be.eql([ + 'basesnippetAcceptance', + 'test@loopback.io', + ]) + ), + ) .catch(err => { throw err; }); @@ -461,11 +480,16 @@ describe('Basesnippet', function (this: Suite) { .expect(200) .then( result => ( - expect(result.body).to.containEql(_.omit(baseSnippet, 'ownerGroup')), + expect(result.body).to.containEql( + _.omit(baseSnippet, ['ownerGroup', 'updateACL']), + ), expect(result.body.snippetType).to.be.eql('base'), expect(result.body.readACL).to.be.eql(['basesnippetAcceptance']), expect(result.body.createACL).to.be.eql(['basesnippetAcceptance']), - expect(result.body.updateACL).to.be.eql(['basesnippetAcceptance']), + expect(result.body.updateACL).to.be.eql([ + 'basesnippetAcceptance', + 'test@loopback.io', + ]), expect(result.body.deleteACL).to.be.eql(['basesnippetAcceptance']), expect(result.body.shareACL).to.be.eql(['basesnippetAcceptance']), expect(result.body.adminACL).to.be.eql(['admin']) @@ -494,7 +518,7 @@ describe('Basesnippet', function (this: Suite) { .expect(404); }); - it('post a basesnippet with authentication and parentId from existing snippet should return 200 and have ownergroup with priority on parent ACLS', async () => { + it('post a basesnippet with authentication and parentId from existing snippet should return 200 and append to parentACL', async () => { await client .post('/basesnippets') .set('Authorization', 'Bearer ' + token) @@ -503,11 +527,19 @@ describe('Basesnippet', function (this: Suite) { .expect(200) .then( result => ( - expect(result.body).to.containEql(baseSnippet), + expect(result.body).to.containEql( + _.omit(baseSnippet, ['createACL', 'updateACL']), + ), expect(result.body.snippetType).to.be.eql('base'), expect(result.body.readACL).to.be.eql(['basesnippetAcceptance']), - expect(result.body.createACL).to.be.eql(['basesnippetAcceptance']), - expect(result.body.updateACL).to.be.eql(['basesnippetAcceptance']), + expect(result.body.createACL).to.be.eql([ + 'basesnippetAcceptance', + 'aNewCreateACL', + ]), + expect(result.body.updateACL).to.be.eql([ + 'basesnippetAcceptance', + 'test@loopback.io', + ]), expect(result.body.deleteACL).to.be.eql(['basesnippetAcceptance']), expect(result.body.shareACL).to.be.eql(['basesnippetAcceptance']), expect(result.body.adminACL).to.be.eql(['admin']) @@ -518,7 +550,7 @@ describe('Basesnippet', function (this: Suite) { }); }); - it('post a basesnippet with authentication and parentId from existing snippet setting explict ACLS should return 200 and have set ACLS with priority on ownergroup and parentACLs', async () => { + it('post a basesnippet with authentication and parentId from existing snippet setting explict ACLS should return 200 and have set ACLS merging parent and child', async () => { await client .post('/basesnippets') .set('Authorization', 'Bearer ' + token) @@ -533,17 +565,31 @@ describe('Basesnippet', function (this: Suite) { .then( result => ( expect(result.body).to.containEql({ - ..._.omit(baseSnippet, ['ownerGroup', 'readACL', 'updateACL']), + ..._.omit(baseSnippet, [ + 'ownerGroup', + 'readACL', + 'updateACL', + 'createACL', + ]), ownerGroup: 'aReadACL', }), expect(result.body.snippetType).to.be.eql('base'), - expect(result.body.readACL).to.be.eql(['aReadACL']), - expect(result.body.createACL).to.be.eql(['basesnippetAcceptance']), - expect(result.body.updateACL).to.be.eql(['anUpdateACL']), + expect(result.body.readACL).to.be.eql([ + 'aReadACL', + 'basesnippetAcceptance', + ]), + expect(result.body.createACL).to.be.eql([ + 'basesnippetAcceptance', + 'aNewCreateACL', + ]), + expect(result.body.updateACL).to.be.eql([ + 'anUpdateACL', + 'basesnippetAcceptance', + 'test@loopback.io', + ]), expect(result.body.deleteACL).to.be.eql(['basesnippetAcceptance']), expect(result.body.shareACL).to.be.eql(['basesnippetAcceptance']), - expect(result.body.adminACL).to.be.eql(['admin']), - (nonVisibleSnippetId = result.body.id) + expect(result.body.adminACL).to.be.eql(['admin']) ), ) .catch(err => { @@ -552,11 +598,18 @@ describe('Basesnippet', function (this: Suite) { }); it('get snippet with ID with token having changed readACL should return 404', async () => { + const unAuthUser = await createAUser(app, ['unAuthorised'], { + email: 'unauth.com', + firstName: 'un', + lastName: 'Auth', + roles: [], + }); + const unAuthToken = await createToken(client, unAuthUser); await client - .get(`/basesnippets/${nonVisibleSnippetId}`) - .set('Authorization', 'Bearer ' + token) + .get(`/basesnippets/${baseSnippetId}`) + .set('Authorization', 'Bearer ' + unAuthToken) .set('Content-Type', 'application/json') - .expect(404); + .expect(403); }); it('get snippet with token and ownerGroup filter should be greater than one', async () => { @@ -750,14 +803,20 @@ describe('Basesnippet', function (this: Suite) { .then( result => ( expect(result.body.ownerGroup).to.be.eql('basesnippetAcceptance'), - expect(result.body.accessGroups).to.be.eql([ - 'basesnippetAcceptance', - 'someNew', - ]), - expect(result.body.readACL).to.be.eql([ - 'basesnippetAcceptance', - 'someNew', - ]) + expect(result.body.accessGroups).to.be.eql( + arrayOfUniqueFrom( + 'basesnippetAcceptance', + 'someNew', + t.input.accessGroups, + ), + ), + expect(result.body.readACL).to.be.eql( + arrayOfUniqueFrom( + 'basesnippetAcceptance', + 'someNew', + t.input.accessGroups, + ), + ) ), ) .catch(err => { @@ -823,26 +882,6 @@ describe('Basesnippet', function (this: Suite) { }); }); - it(`patch snippet by id with non-authorised user should return 404`, async () => { - const bs = await client - .post('/basesnippets') - .set('Authorization', 'Bearer ' + token) - .set('Content-Type', 'application/json') - .send({ - ..._.omit(baseSnippet, 'updateACL'), - updateACL: ['nonAuthorised'], - }); - await client - .patch(`/basesnippets/${bs.body.id}`) - .set('Authorization', 'Bearer ' + token) - .set('Content-Type', 'application/json') - .send({name: 'something'}) - .expect(404) - .catch(err => { - throw err; - }); - }); - [404, 204].forEach(t => { it(`delete snippet should return ${t}`, async () => { await client diff --git a/sci-log-db/src/__tests__/acceptance/file.controller.acceptance.ts b/sci-log-db/src/__tests__/acceptance/file.controller.acceptance.ts index 77e0c6dc..bfdd1a39 100644 --- a/sci-log-db/src/__tests__/acceptance/file.controller.acceptance.ts +++ b/sci-log-db/src/__tests__/acceptance/file.controller.acceptance.ts @@ -2,6 +2,7 @@ import {Client, expect} from '@loopback/testlab'; import {Suite} from 'mocha'; import {SciLogDbApplication} from '../..'; import {clearDatabase, createUserToken, setupApplication} from './test-helper'; +import _ from 'lodash'; describe('File controller services', function (this: Suite) { this.timeout(1000); @@ -50,8 +51,12 @@ describe('File controller services', function (this: Suite) { .expect(200) .then( result => ( - expect(result.body).to.containEql(fileSnippet), + expect(result.body).to.containEql(_.omit(fileSnippet, 'updateACL')), expect(result.body.snippetType).to.be.eql('image'), + expect(result.body.updateACL).to.be.eql([ + 'filesnippetAcceptance', + 'test@loopback.io', + ]), (fileSnippetId = result.body.id) ), ) @@ -95,7 +100,13 @@ describe('File controller services', function (this: Suite) { .then( result => ( expect(result.body.length).to.be.eql(1), - expect(result.body[0]).to.containEql(fileSnippet) + expect(result.body[0]).to.containEql( + _.omit(fileSnippet, 'updateACL'), + ), + expect(result.body[0].updateACL).to.be.eql([ + 'filesnippetAcceptance', + 'test@loopback.io', + ]) ), ) .catch(err => { @@ -116,7 +127,15 @@ describe('File controller services', function (this: Suite) { .set('Authorization', 'Bearer ' + token) .set('Content-Type', 'application/json') .expect(200) - .then(result => expect(result.body).to.containEql(fileSnippet)) + .then( + result => ( + expect(result.body).to.containEql(_.omit(fileSnippet, 'updateACL')), + expect(result.body.updateACL).to.be.eql([ + 'filesnippetAcceptance', + 'test@loopback.io', + ]) + ), + ) .catch(err => { throw err; }); diff --git a/sci-log-db/src/__tests__/acceptance/location.controller.acceptance.ts b/sci-log-db/src/__tests__/acceptance/location.controller.acceptance.ts index b9cc4e10..6614cb66 100644 --- a/sci-log-db/src/__tests__/acceptance/location.controller.acceptance.ts +++ b/sci-log-db/src/__tests__/acceptance/location.controller.acceptance.ts @@ -61,8 +61,17 @@ describe('Location', function (this: Suite) { .expect(200) .then( result => ( - expect(result.body).to.containEql(locationSnippet), - expect(result.body.readACL).to.be.eql([locationSnippet.ownerGroup]), + expect(result.body).to.containEql( + _.omit(locationSnippet, ['readACL', 'deleteACL']), + ), + expect(result.body.readACL).to.be.eql([ + locationSnippet.ownerGroup, + 'any-authenticated-user', + ]), + expect(result.body.deleteACL).to.be.eql([ + locationSnippet.ownerGroup, + 'admin', + ]), (locationSnippetId = result.body.id) ), ) @@ -106,7 +115,17 @@ describe('Location', function (this: Suite) { .then( result => ( expect(result.body.length).to.be.eql(1), - expect(result.body[0]).to.containEql(locationSnippet) + expect(result.body[0]).to.containEql( + _.omit(locationSnippet, ['readACL', 'deleteACL']), + ), + expect(result.body[0].readACL).to.be.eql([ + locationSnippet.ownerGroup, + 'any-authenticated-user', + ]), + expect(result.body[0].deleteACL).to.be.eql([ + locationSnippet.ownerGroup, + 'admin', + ]) ), ) .catch(err => { @@ -127,7 +146,21 @@ describe('Location', function (this: Suite) { .set('Authorization', 'Bearer ' + token) .set('Content-Type', 'application/json') .expect(200) - .then(result => expect(result.body).to.containEql(locationSnippet)) + .then( + result => ( + expect(result.body).to.containEql( + _.omit(locationSnippet, ['readACL', 'deleteACL']), + ), + expect(result.body.readACL).to.be.eql([ + locationSnippet.ownerGroup, + 'any-authenticated-user', + ]), + expect(result.body.deleteACL).to.be.eql([ + locationSnippet.ownerGroup, + 'admin', + ]) + ), + ) .catch(err => { throw err; }); diff --git a/sci-log-db/src/__tests__/acceptance/paragraph.controller.acceptance.ts b/sci-log-db/src/__tests__/acceptance/paragraph.controller.acceptance.ts index 57345f04..3ab19d1c 100644 --- a/sci-log-db/src/__tests__/acceptance/paragraph.controller.acceptance.ts +++ b/sci-log-db/src/__tests__/acceptance/paragraph.controller.acceptance.ts @@ -2,6 +2,7 @@ import {Client, expect} from '@loopback/testlab'; import {Suite} from 'mocha'; import {SciLogDbApplication} from '../..'; import {clearDatabase, createUserToken, setupApplication} from './test-helper'; +import _ from 'lodash'; describe('Paragraph', function (this: Suite) { this.timeout(5000); @@ -50,9 +51,15 @@ describe('Paragraph', function (this: Suite) { .expect(200) .then( result => ( - expect(result.body).to.containEql(paragraphSnippet), + expect(result.body).to.containEql( + _.omit(paragraphSnippet, 'updateACL'), + ), expect(result.body.snippetType).to.be.eql('paragraph'), expect(result.body.linkType).to.be.eql('paragraph'), + expect(result.body.updateACL).to.be.eql([ + 'paragraphAcceptance', + 'test@loopback.io', + ]), (paragraphSnippetId = result.body.id) ), ) @@ -96,8 +103,14 @@ describe('Paragraph', function (this: Suite) { .then( result => ( expect(result.body.length).to.be.eql(1), - expect(result.body[0]).to.containEql(paragraphSnippet), - expect(result.body[0]).not.to.have.key('htmlTextcontent') + expect(result.body[0]).to.containEql( + _.omit(paragraphSnippet, 'updateACL'), + ), + expect(result.body[0]).not.to.have.key('htmlTextcontent'), + expect(result.body[0].updateACL).to.be.eql([ + 'paragraphAcceptance', + 'test@loopback.io', + ]) ), ) .catch(err => { @@ -118,7 +131,17 @@ describe('Paragraph', function (this: Suite) { .set('Authorization', 'Bearer ' + token) .set('Content-Type', 'application/json') .expect(200) - .then(result => expect(result.body).to.containEql(paragraphSnippet)) + .then( + result => ( + expect(result.body).to.containEql( + _.omit(paragraphSnippet, 'updateACL'), + ), + expect(result.body.updateACL).to.be.eql([ + 'paragraphAcceptance', + 'test@loopback.io', + ]) + ), + ) .catch(err => { throw err; }); diff --git a/sci-log-db/src/__tests__/acceptance/task.controller.acceptance.ts b/sci-log-db/src/__tests__/acceptance/task.controller.acceptance.ts index f70723b3..52914d15 100644 --- a/sci-log-db/src/__tests__/acceptance/task.controller.acceptance.ts +++ b/sci-log-db/src/__tests__/acceptance/task.controller.acceptance.ts @@ -2,6 +2,7 @@ import {Client, expect} from '@loopback/testlab'; import {Suite} from 'mocha'; import {SciLogDbApplication} from '../..'; import {clearDatabase, createUserToken, setupApplication} from './test-helper'; +import _ from 'lodash'; describe('TaskRepositorySnippet', function (this: Suite) { this.timeout(5000); @@ -49,8 +50,12 @@ describe('TaskRepositorySnippet', function (this: Suite) { .expect(200) .then( result => ( - expect(result.body).to.containEql(taskSnippet), + expect(result.body).to.containEql(_.omit(taskSnippet, 'updateACL')), expect(result.body.snippetType).to.containEql('task'), + expect(result.body.updateACL).to.be.eql([ + 'taskAcceptance', + 'test@loopback.io', + ]), (taskSnippetId = result.body.id) ), ) @@ -94,7 +99,13 @@ describe('TaskRepositorySnippet', function (this: Suite) { .then( result => ( expect(result.body.length).to.be.eql(1), - expect(result.body[0]).to.containEql(taskSnippet) + expect(result.body[0]).to.containEql( + _.omit(taskSnippet, 'updateACL'), + ), + expect(result.body[0].updateACL).to.be.eql([ + 'taskAcceptance', + 'test@loopback.io', + ]) ), ) .catch(err => { @@ -115,7 +126,15 @@ describe('TaskRepositorySnippet', function (this: Suite) { .set('Authorization', 'Bearer ' + token) .set('Content-Type', 'application/json') .expect(200) - .then(result => expect(result.body).to.containEql(taskSnippet)) + .then( + result => ( + expect(result.body).to.containEql(_.omit(taskSnippet, 'updateACL')), + expect(result.body.updateACL).to.be.eql([ + 'taskAcceptance', + 'test@loopback.io', + ]) + ), + ) .catch(err => { throw err; }); diff --git a/sci-log-db/src/__tests__/acceptance/test-helper.ts b/sci-log-db/src/__tests__/acceptance/test-helper.ts index e887b27c..a4d00b13 100644 --- a/sci-log-db/src/__tests__/acceptance/test-helper.ts +++ b/sci-log-db/src/__tests__/acceptance/test-helper.ts @@ -82,7 +82,7 @@ export async function createAUser( return newUser; } -async function createToken(client: Client, user: User) { +export async function createToken(client: Client, user: User) { const loginResponse = await client .post('/users/login') .send({principal: user.email, password: userPassword}); diff --git a/sci-log-db/src/mixins/basesnippet.repository-mixin.ts b/sci-log-db/src/mixins/basesnippet.repository-mixin.ts index 06bbd417..c3f087d1 100644 --- a/sci-log-db/src/mixins/basesnippet.repository-mixin.ts +++ b/sci-log-db/src/mixins/basesnippet.repository-mixin.ts @@ -146,7 +146,7 @@ function UpdateAndDeleteRepositoryMixin< acl => basesnippet[acl as keyof typeof basesnippet], ) ) - await self['aclDefaultOnCreation'](merge); + await self['aclDefaultOnCreation'](merge, false); return this.getChanged(merge, snippet); } diff --git a/sci-log-db/src/repositories/autoadd.repository.base.ts b/sci-log-db/src/repositories/autoadd.repository.base.ts index d1365786..9789e3ff 100644 --- a/sci-log-db/src/repositories/autoadd.repository.base.ts +++ b/sci-log-db/src/repositories/autoadd.repository.base.ts @@ -85,32 +85,37 @@ export class AutoAddRepository< ownerGroup?: string; accessGroups?: string[]; }, + isNewInstance = true, ) { - const emptyAcls = this.acls.filter(acl => !data[acl as keyof Basesnippet]); - if (emptyAcls) { - const parent = await this.getParent(data); - if (data.snippetType === 'location') - await this.addToACLIfNotEmpty( - emptyAcls, - data, - this.defaultLocationACL.bind(this), - ); - else if (data.snippetType === 'logbook') { - const users = await this.getUnxGroupsEmail( - (parent as Location).location ?? '', - ); - await this.addToACLIfNotEmpty( - emptyAcls, - data, - _.partial(this.defaultLogbookACL.bind(this), users), - ); - } else - await this.addToACLIfNotEmpty( - emptyAcls, - data, - _.partial(this.defaultAllButLocationLogbookACL.bind(this), parent), - ); - } + const parent = await this.getParent(data); + const acls = [...this.acls]; + if (data.snippetType === 'location') + await this.addToACLIfNotEmpty( + acls, + data, + this.defaultLocationACL.bind(this), + ); + else if (data.snippetType === 'logbook') { + const users = await this.getUnxGroupsEmail( + (parent as Location).location ?? '', + ); + await this.addToACLIfNotEmpty( + acls, + data, + _.partial(this.defaultLogbookACL.bind(this), users), + ); + } else + await this.addToACLIfNotEmpty( + acls, + data, + _.partial( + this.defaultAllButLocationLogbookACL.bind(this), + parent, + _, + _, + isNewInstance, + ), + ); delete data.ownerGroup; delete data.accessGroups; } @@ -126,8 +131,13 @@ export class AutoAddRepository< await callableFunction(k, data), [], ) as string[]; - if (aclValue.length > 0) - (data[k as keyof Basesnippet] as string[]) = aclValue; + if (aclValue.length > 0) { + const keyWithType = k as keyof Basesnippet; + (data[keyWithType] as string[]) = arrayOfUniqueFrom( + data[keyWithType], + aclValue, + ); + } }), ); } @@ -173,12 +183,18 @@ export class AutoAddRepository< private defaultAllButLocationLogbookACL( parent: Basesnippet, aclType: string, - data: {accessGroups?: string[]}, + data: { + accessGroups?: string[]; + createdBy?: string[]; + }, + isNewInstance = true, ) { if (aclType === 'shareACL') return arrayOfUniqueFrom(parent.shareACL, parent.readACL); if (aclType === 'readACL') return arrayOfUniqueFrom(parent.readACL, data.accessGroups); + if (aclType === 'updateACL' && isNewInstance) + return arrayOfUniqueFrom(parent.updateACL, data.createdBy); return parent[aclType as keyof Basesnippet]; }