Skip to content

Commit 8ca3317

Browse files
authored
Add dataset.read, entity.read, and entity.create/update verbs (getodk#808)
* Add read verbs to datasets and entities * Adding entity create/update verbs in raw sql migration * Missed some tests
1 parent 67375de commit 8ca3317

File tree

4 files changed

+120
-58
lines changed

4 files changed

+120
-58
lines changed
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// Copyright 2023 ODK Central Developers
2+
// See the NOTICE file at the top-level directory of this distribution and at
3+
// https://github.com/getodk/central-backend/blob/master/NOTICE.
4+
// This file is part of ODK Central. It is subject to the license terms in
5+
// the LICENSE file found in the top-level directory of this distribution and at
6+
// https://www.apache.org/licenses/LICENSE-2.0. No part of ODK Central,
7+
// including this file, may be copied, modified, propagated, or distributed
8+
// except according to the terms contained in the LICENSE file.
9+
10+
const up = (db) => db.raw(`
11+
UPDATE roles
12+
SET verbs = verbs || '["dataset.read", "entity.read"]'::jsonb
13+
WHERE system in ('admin', 'manager', 'viewer')
14+
`).then(() => db.raw(`
15+
UPDATE roles
16+
SET verbs = verbs || '["entity.create", "entity.update"]'::jsonb
17+
WHERE system in ('admin', 'manager')
18+
`));
19+
20+
const down = (db) => db.raw(`
21+
UPDATE roles
22+
SET verbs = (verbs - 'dataset.read' - 'entity.read')
23+
WHERE system in ('admin', 'manager', 'viewer')
24+
`).then(() => db.raw(`
25+
UPDATE roles
26+
SET verbs = (verbs - 'entity.create' - 'entity.update')
27+
WHERE system in ('admin', 'manager')
28+
`));
29+
30+
module.exports = { up, down };
31+

lib/resources/datasets.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ module.exports = (service, endpoint) => {
2323
service.get('/projects/:projectId/datasets/:name', endpoint(({ Datasets, Projects }, { params, auth }) =>
2424
Projects.getById(params.projectId)
2525
.then(getOrNotFound)
26-
.then((project) => auth.canOrReject('dataset.list', project))
26+
.then((project) => auth.canOrReject('dataset.read', project))
2727
.then(() => Datasets.getDatasetMetadata(params.name, params.projectId)
2828
.then(getOrNotFound))));
2929

test/integration/api/projects.js

Lines changed: 49 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ describe('api: /projects', () => {
6969
.set('X-Extended-Metadata', 'true')
7070
.expect(200)
7171
.then(({ body }) => {
72-
body.verbs.length.should.be.greaterThan(39);
72+
body.verbs.length.should.equal(46);
7373
body.should.be.a.Project();
7474
body.name.should.equal('Default Project');
7575
}))))));
@@ -370,7 +370,7 @@ describe('api: /projects', () => {
370370
.expect(200)
371371
.then(({ body }) => {
372372
body.verbs.should.be.an.Array();
373-
body.verbs.length.should.be.greaterThan(39);
373+
body.verbs.length.should.equal(46);
374374
body.verbs.should.containDeep([ 'user.password.invalidate', 'project.delete' ]);
375375
}))));
376376

@@ -381,7 +381,7 @@ describe('api: /projects', () => {
381381
.expect(200)
382382
.then(({ body }) => {
383383
body.verbs.should.be.an.Array();
384-
body.verbs.length.should.be.lessThan(28);
384+
body.verbs.length.should.equal(31);
385385
body.verbs.should.containDeep([ 'assignment.create', 'project.delete', 'dataset.list' ]);
386386
body.verbs.should.not.containDeep([ 'project.create' ]);
387387
}))));
@@ -398,13 +398,11 @@ describe('api: /projects', () => {
398398
.expect(200)
399399
.then(({ body }) => {
400400
body.verbs.should.eqlInAnyOrder([
401-
// eslint-disable-next-line no-multi-spaces
402-
'project.read', // from role(s): formfill
403-
// eslint-disable-next-line no-multi-spaces
404-
'form.list', // from role(s): formfill
405-
// eslint-disable-next-line no-multi-spaces
406-
'form.read', // from role(s): formfill
407-
'submission.create', // from role(s): formfill
401+
// following verbs from role: formfill
402+
'project.read',
403+
'form.list',
404+
'form.read',
405+
'submission.create',
408406
]);
409407
}))))));
410408

@@ -422,21 +420,19 @@ describe('api: /projects', () => {
422420
.expect(200)
423421
.then(({ body }) => {
424422
body.verbs.should.eqlInAnyOrder([
425-
// eslint-disable-next-line no-multi-spaces
426-
'project.read', // from role(s): formfill, viewer
427-
// eslint-disable-next-line no-multi-spaces
428-
'form.list', // from role(s): formfill, viewer
429-
// eslint-disable-next-line no-multi-spaces
430-
'form.read', // from role(s): formfill, viewer
431-
// eslint-disable-next-line no-multi-spaces
432-
'submission.read', // from role(s): viewer
433-
// eslint-disable-next-line no-multi-spaces
434-
'submission.list', // from role(s): viewer
435-
'submission.create', // from role(s): formfill
436-
// eslint-disable-next-line no-multi-spaces
437-
'dataset.list', // from role(s): viewer
438-
// eslint-disable-next-line no-multi-spaces
439-
'entity.list', // from role(s): viewer
423+
// following roles from formfill + viewer:
424+
'project.read',
425+
'form.list',
426+
'form.read',
427+
// following roles from formfill only:
428+
'submission.create',
429+
// following roles from viewer only:
430+
'submission.read',
431+
'submission.list',
432+
'dataset.list',
433+
'dataset.read',
434+
'entity.list',
435+
'entity.read'
440436
]);
441437
}))))));
442438
});
@@ -1330,7 +1326,7 @@ describe('api: /projects?forms=true', () => {
13301326
body.length.should.equal(1);
13311327
body[0].should.be.a.Project();
13321328
const { formList, verbs } = body[0];
1333-
verbs.length.should.equal(42);
1329+
verbs.length.should.equal(46);
13341330
formList.length.should.equal(2);
13351331
const form = formList[0];
13361332
form.should.be.a.ExtendedForm();
@@ -1394,7 +1390,7 @@ describe('api: /projects?forms=true', () => {
13941390
body.length.should.equal(2);
13951391
// First project
13961392
body[0].formList.length.should.equal(2);
1397-
body[0].verbs.length.should.equal(27);
1393+
body[0].verbs.length.should.equal(31);
13981394
// Second project
13991395
body[1].formList.length.should.equal(1);
14001396
body[1].verbs.length.should.be.lessThan(5); // 4 for data collector
@@ -1439,21 +1435,19 @@ describe('api: /projects?forms=true', () => {
14391435
body.length.should.equal(1);
14401436
const { verbs } = body[0];
14411437
verbs.should.eqlInAnyOrder([
1442-
// eslint-disable-next-line no-multi-spaces
1443-
'form.list', // from role(s): formfill, viewer
1444-
// eslint-disable-next-line no-multi-spaces
1445-
'form.read', // from role(s): formfill, viewer
1446-
// eslint-disable-next-line no-multi-spaces
1447-
'project.read', // from role(s): formfill, viewer
1448-
'submission.create', // from role(s): formfill
1449-
// eslint-disable-next-line no-multi-spaces
1450-
'submission.list', // from role(s): viewer
1451-
// eslint-disable-next-line no-multi-spaces
1452-
'submission.read', // from role(s): viewer
1453-
// eslint-disable-next-line no-multi-spaces
1454-
'dataset.list', // from role(s): viewer
1455-
// eslint-disable-next-line no-multi-spaces
1456-
'entity.list' // from role(s): viewer
1438+
// following roles from formfill + viewer:
1439+
'project.read',
1440+
'form.list',
1441+
'form.read',
1442+
// following roles from formfill only:
1443+
'submission.create',
1444+
// following roles from viewer only:
1445+
'submission.read',
1446+
'submission.list',
1447+
'dataset.list',
1448+
'dataset.read',
1449+
'entity.list',
1450+
'entity.read'
14571451
]);
14581452
}))))));
14591453
});
@@ -1484,21 +1478,19 @@ describe('api: /projects?forms=true', () => {
14841478
const project = body[0];
14851479
project.id.should.equal(1);
14861480
project.verbs.should.eqlInAnyOrder([
1487-
// eslint-disable-next-line no-multi-spaces
1488-
'project.read', // from role(s): viewer
1489-
// eslint-disable-next-line no-multi-spaces
1490-
'form.list', // from role(s): viewer
1491-
// eslint-disable-next-line no-multi-spaces
1492-
'form.read', // from role(s): viewer, app-user
1493-
// eslint-disable-next-line no-multi-spaces
1494-
'submission.read', // from role(s): viewer
1495-
// eslint-disable-next-line no-multi-spaces
1496-
'submission.list', // from role(s): viewer
1497-
'submission.create', // from role(s): app-user
1498-
// eslint-disable-next-line no-multi-spaces
1499-
'dataset.list', // from role(s): viewer
1500-
// eslint-disable-next-line no-multi-spaces
1501-
'entity.list' // from role(s): viewer
1481+
// following roles from app-user + viewer:
1482+
'form.read',
1483+
// following roles from app-user only:
1484+
'submission.create',
1485+
// following roles from viewer only:
1486+
'project.read',
1487+
'form.list',
1488+
'submission.read',
1489+
'submission.list',
1490+
'dataset.list',
1491+
'dataset.read',
1492+
'entity.list',
1493+
'entity.read'
15021494
]);
15031495
}))))));
15041496
});

test/integration/other/migrations.js

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ const upToMigration = (toName, inclusive = true) => withTestDatabase(async (migr
3232
});
3333
const up = () => withTestDatabase((migrator) =>
3434
migrator.migrate.up({ directory: migrationsDir }));
35+
const down = () => withTestDatabase((migrator) =>
36+
migrator.migrate.down({ directory: migrationsDir }));
3537

3638
// NOTE/TODO: figure out something else here D:
3739
// Skipping these migrations because after adding a new description
@@ -460,3 +462,40 @@ describe('database migrations: 20230123-01-remove-google-backups', function() {
460462
deleted[0].system.should.equal('initbkup');
461463
}));
462464
});
465+
466+
// eslint-disable-next-line func-names
467+
describe('database migrations: 20230324-01-edit-dataset-verbs.js', function () {
468+
this.timeout(10000);
469+
470+
it('should add dataset/entity read verbs with raw sql', testServiceFullTrx(async (service, container) => {
471+
await upToMigration('20230324-01-edit-dataset-verbs.js', false);
472+
await populateUsers(container);
473+
await populateForms(container);
474+
475+
let admin = await container.one(sql`select verbs from roles where system = 'admin'`);
476+
admin.verbs.should.not.containDeep(['dataset.read', 'entity.read', 'entity.create', 'entity.update']);
477+
admin.verbs.length.should.equal(43);
478+
479+
let viewer = await container.one(sql`select verbs from roles where system = 'viewer'`);
480+
viewer.verbs.should.not.containDeep(['dataset.read', 'entity.read', 'entity.create', 'entity.update']);
481+
viewer.verbs.length.should.equal(7);
482+
483+
await up();
484+
485+
admin = await container.one(sql`select verbs from roles where system = 'admin'`);
486+
admin.verbs.should.containDeep(['dataset.read', 'entity.read', 'entity.create', 'entity.update']);
487+
admin.verbs.length.should.equal(47);
488+
489+
viewer = await container.one(sql`select verbs from roles where system = 'viewer'`);
490+
viewer.verbs.should.containDeep(['dataset.read', 'entity.read']);
491+
viewer.verbs.should.not.containDeep(['entity.create', 'entity.update']);
492+
viewer.verbs.length.should.equal(9);
493+
494+
await down();
495+
496+
admin = await container.one(sql`select verbs from roles where system = 'admin'`);
497+
admin.verbs.length.should.equal(43);
498+
viewer = await container.one(sql`select verbs from roles where system = 'viewer'`);
499+
viewer.verbs.length.should.equal(7);
500+
}));
501+
});

0 commit comments

Comments
 (0)