Skip to content

Commit 4bed47f

Browse files
authored
Merge pull request #160 from loneil/feature/submissionPermissions
New submission permission middleware
2 parents 1bf3195 + a4a12a8 commit 4bed47f

9 files changed

Lines changed: 471 additions & 93 deletions

File tree

app/app.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,12 @@ log.addLevel('debug', 1500, { fg: 'cyan' });
3232

3333
// Skip if running tests
3434
if (process.env.NODE_ENV !== 'test') {
35+
const morganOpts = {
36+
// Skip logging kube-probe requests
37+
skip: (req) => req.headers['user-agent'] && req.headers['user-agent'].includes('kube-probe')
38+
};
3539
// Add Morgan endpoint logging
36-
app.use(morgan(config.get('server.morganFormat')));
40+
app.use(morgan(config.get('server.morganFormat'), morganOpts));
3741
initializeConnections();
3842
}
3943

app/frontend/src/components/bcgov/BCGovHeader.vue

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
<h1 v-if="!formSubmitMode" data-test="btn-header-title" class="font-weight-bold title d-none d-md-flex pl-4">
1616
{{ appTitle }}
1717
</h1>
18-
<v-chip v-if="!formSubmitMode" class="d-none d-md-flex ma-2" color="secondary" text-color="white">Beta</v-chip>
1918
<v-spacer />
2019
<BaseAuthButton />
2120
</v-toolbar>

app/src/forms/auth/middleware/userAccess.js

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
const Problem = require('api-problem');
2-
const service = require('../service');
2+
33
const keycloak = require('../../../components/keycloak');
4+
const Permissions = require('../../common/constants').Permissions;
5+
const service = require('../service');
46

57
const getToken = req => {
68
try {
@@ -74,6 +76,59 @@ const hasFormPermissions = (permissions) => {
7476
};
7577
};
7678

79+
const hasSubmissionPermissions = (permissions) => {
80+
return async (req, res, next) => {
81+
if (!Array.isArray(permissions)) {
82+
permissions = [permissions];
83+
}
84+
85+
// Get the provided submission ID whether in a param or query (precedence to param)
86+
const submissionId = req.params.formSubmissionId || req.query.formSubmissionId;
87+
if (!submissionId) {
88+
// No submission provided to this route that secures based on form... that's a problem!
89+
return next(new Problem(401, { detail: 'Submission Id not found on request.' }));
90+
}
91+
92+
// Get the submission results so we know what form this submission is for
93+
const submissionForm = await service.getSubmissionForm(submissionId);
94+
95+
// Deleted submissions are inaccessible
96+
if (submissionForm.submission.deleted) {
97+
return next(new Problem(401, { detail: 'You do not have access to this submission.' }));
98+
}
99+
100+
// Public (annonymous) forms are publicly viewable
101+
const publicAllowed = submissionForm.form.identityProviders.find(p => p.code === 'public') !== undefined;
102+
if (permissions.length === 1 && permissions.includes(Permissions.SUBMISSION_READ) && publicAllowed) {
103+
return next();
104+
}
105+
106+
// Does the user have permissions for this submission due to their FORM permissions
107+
if (req.currentUser) {
108+
let formFromCurrentUser = req.currentUser.forms.find(f => f.formId === submissionForm.form.id);
109+
if (formFromCurrentUser) {
110+
111+
// Do they have the submission permissions being requested on this FORM
112+
const intersection = permissions.filter(p => {
113+
return formFromCurrentUser.permissions.includes(p);
114+
});
115+
if (intersection.length === permissions.length) {
116+
return next();
117+
}
118+
}
119+
}
120+
121+
// check against the submission level permissions assigned to the user...
122+
const submissionPermission = await service.checkSubmissionPermission(req.currentUser, submissionId, permissions);
123+
if (submissionPermission) return next();
124+
125+
// no access to this submission...
126+
return next(new Problem(401, { detail: 'You do not have access to this submission.' }));
127+
};
128+
};
129+
130+
77131

78132
module.exports.currentUser = currentUser;
79133
module.exports.hasFormPermissions = hasFormPermissions;
134+
module.exports.hasSubmissionPermissions = hasSubmissionPermissions;

app/src/forms/auth/service.js

Lines changed: 44 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
const { PublicFormAccess, User, UserFormAccess } = require('../common/models');
1+
const { Form, FormSubmissionUserPermissions, PublicFormAccess, SubmissionMetadata, User, UserFormAccess } = require('../common/models');
22
const { queryUtils } = require('../common/utils');
33

44
const FORM_SUBMITTER = require('../common/constants').Permissions.FORM_SUBMITTER;
55

6-
const {transaction} = require('objection');
7-
const {v4: uuidv4} = require('uuid');
6+
const { transaction } = require('objection');
7+
const { v4: uuidv4 } = require('uuid');
88

99
const service = {
1010

@@ -13,14 +13,15 @@ const service = {
1313
try {
1414
trx = await transaction.start(User.knex());
1515

16-
const obj = Object.assign({},{
16+
const obj = Object.assign({}, {
1717
id: uuidv4(),
1818
keycloakId: data.keycloakId,
1919
username: data.username,
2020
fullName: data.fullName,
2121
email: data.email,
2222
firstName: data.firstName,
23-
lastName: data.lastName});
23+
lastName: data.lastName
24+
});
2425

2526
await User.query(trx).insert(obj);
2627
await trx.commit();
@@ -50,7 +51,8 @@ const service = {
5051
fullName: data.fullName,
5152
email: data.email,
5253
firstName: data.firstName,
53-
lastName: data.lastName};
54+
lastName: data.lastName
55+
};
5456

5557
await User.query(trx).patchAndFetchById(obj.id, update);
5658
await trx.commit();
@@ -102,7 +104,7 @@ const service = {
102104

103105
getUserId: async (userInfo) => {
104106
if (userInfo.public) {
105-
return Object.assign({}, {...userInfo, id: 'public' });
107+
return Object.assign({}, { ...userInfo, id: 'public' });
106108
}
107109

108110
const obj = Object.assign({}, userInfo);
@@ -120,7 +122,7 @@ const service = {
120122
user = await service.updateUser(user.id, obj);
121123
}
122124
// return with the db id...
123-
return Object.assign({}, {...userInfo, id: user.id });
125+
return Object.assign({}, { ...userInfo, id: user.id });
124126
},
125127

126128
getUserForms: async (userInfo, params = {}) => {
@@ -212,8 +214,41 @@ const service = {
212214
const forms = await service.getUserForms(user, params); // get forms for user (filtered by params)...
213215
params.active = false;
214216
const deletedForms = await service.getUserForms(user, params); // get forms for user (filtered by params)...
215-
return Object.assign({}, {...user, forms: forms, deletedForms: deletedForms});
217+
return Object.assign({}, { ...user, forms: forms, deletedForms: deletedForms });
218+
},
219+
220+
// -------------------------------------------------------------------------------------------------------------
221+
// Submission Data
222+
// -------------------------------------------------------------------------------------------------------------
223+
checkSubmissionPermission: async (currentUser, submissionId, permissions) => {
224+
if (currentUser.public) return false;
225+
return FormSubmissionUserPermissions.query()
226+
.modify('filterSubmissionId', submissionId)
227+
.modify('filterUserId', currentUser.id)
228+
.modify('filterByPermissions', permissions)
229+
.first();
230+
},
231+
232+
getSubmissionForm: async (submissionId) => {
233+
// Get this submission data for the form Id
234+
const meta = await SubmissionMetadata.query()
235+
.where('submissionId', submissionId)
236+
.first()
237+
.throwIfNotFound();
238+
239+
// Get the form with IDP info
240+
const form = await Form.query()
241+
.findById(meta.formId)
242+
.allowGraph('identityProviders')
243+
.withGraphFetched('identityProviders(orderDefault)')
244+
.throwIfNotFound();
245+
246+
return {
247+
submission: meta,
248+
form: form
249+
};
216250
}
251+
// ---------------------------------------------------------------------------------------------/Submission Data
217252

218253
};
219254

app/src/forms/form/service.js

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -281,26 +281,6 @@ const service = {
281281
}
282282
},
283283

284-
updateSubmission: async (formSubmissionId, data, currentUser) => {
285-
let trx;
286-
try {
287-
const obj = await service.readSubmission(formSubmissionId);
288-
trx = await transaction.start(FormSubmission.knex());
289-
290-
// TODO: check if we can update this submission
291-
// TODO: we may have to update permissions for users (draft = false, then no delete?)
292-
// TODO: deal with attachments?
293-
294-
await FormSubmission.query(trx).patchAndFetchById(formSubmissionId, { draft: data.draft, submission: data.submission, updatedBy: currentUser.username });
295-
await trx.commit();
296-
const result = await service.readSubmission(obj.id);
297-
return result;
298-
} catch (err) {
299-
if (trx) await trx.rollback();
300-
throw err;
301-
}
302-
},
303-
304284
readSubmission: async (id) => {
305285
return FormSubmission.query()
306286
.findById(id)

app/src/forms/submission/routes.js

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,34 +2,36 @@ const routes = require('express').Router();
22

33
const controller = require('./controller');
44
const currentUser = require('../auth/middleware/userAccess').currentUser;
5+
const hasSubmissionPermissions = require('../auth/middleware/userAccess').hasSubmissionPermissions;
6+
const P = require('../common/constants').Permissions;
57

68
routes.use(currentUser);
79

8-
routes.get('/:formSubmissionId', async (req, res, next) => {
10+
routes.get('/:formSubmissionId', currentUser, hasSubmissionPermissions(P.SUBMISSION_READ), async (req, res, next) => {
911
await controller.read(req, res, next);
1012
});
1113

12-
routes.put('/:formSubmissionId', async (req, res, next) => {
14+
routes.put('/:formSubmissionId', currentUser, hasSubmissionPermissions(P.SUBMISSION_UPDATE), async (req, res, next) => {
1315
await controller.update(req, res, next);
1416
});
1517

16-
routes.get('/:formSubmissionId/notes', async (req, res, next) => {
18+
routes.get('/:formSubmissionId/notes', currentUser, hasSubmissionPermissions(P.SUBMISSION_READ), async (req, res, next) => {
1719
await controller.getNotes(req, res, next);
1820
});
1921

20-
routes.post('/:formSubmissionId/notes', async (req, res, next) => {
22+
routes.post('/:formSubmissionId/notes', currentUser, hasSubmissionPermissions(P.SUBMISSION_UPDATE), async (req, res, next) => {
2123
await controller.addNote(req, res, next);
2224
});
2325

24-
routes.get('/:formSubmissionId/status', async (req, res, next) => {
26+
routes.get('/:formSubmissionId/status', currentUser, hasSubmissionPermissions(P.SUBMISSION_READ), async (req, res, next) => {
2527
await controller.getStatus(req, res, next);
2628
});
2729

28-
routes.post('/:formSubmissionId/status', async (req, res, next) => {
30+
routes.post('/:formSubmissionId/status', currentUser, hasSubmissionPermissions(P.SUBMISSION_UPDATE), async (req, res, next) => {
2931
await controller.addStatus(req, res, next);
3032
});
3133

32-
routes.post('/:formSubmissionId/email', async (req, res, next) => {
34+
routes.post('/:formSubmissionId/email', currentUser, hasSubmissionPermissions(P.SUBMISSION_READ), async (req, res, next) => {
3335
await controller.email(req, res, next);
3436
});
3537

app/src/forms/submission/service.js

Lines changed: 3 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
1-
const { Form, FormVersion, FormSubmission, FormSubmissionStatus, FormSubmissionUserPermissions, Note, SubmissionMetadata, UserFormAccess } = require('../common/models');
1+
const { Form, FormVersion, FormSubmission, FormSubmissionStatus, Note, SubmissionMetadata } = require('../common/models');
22

3-
const Permissions = require('../common/constants').Permissions;
4-
5-
const Problem = require('api-problem');
63
const { transaction } = require('objection');
74
const { v4: uuidv4 } = require('uuid');
85

@@ -38,60 +35,15 @@ const service = {
3835
});
3936
},
4037

41-
read: async (formSubmissionId, currentUser, permissions = [Permissions.SUBMISSION_READ]) => {
42-
const result = await service._fetchSubmissionData(formSubmissionId);
43-
44-
const checkFormSubmissionsPermission = async () => {
45-
if (currentUser.public) return false;
46-
return UserFormAccess.query()
47-
.modify('filterFormId', result.form.id)
48-
.modify('filterUserId', currentUser.id)
49-
.modify('filterByAccess', null, null, permissions)
50-
.first();
51-
};
52-
53-
const checkSubmissionPermission = async () => {
54-
if (currentUser.public) return false;
55-
return FormSubmissionUserPermissions.query()
56-
.modify('filterSubmissionId', formSubmissionId)
57-
.modify('filterUserId', currentUser.id)
58-
.modify('filterByPermissions', permissions)
59-
.first();
60-
};
61-
62-
const isDeleted = result.submission.deleted;
63-
const isDraft = result.submission.draft;
64-
const publicAllowed = result.form.identityProviders.find(p => p.code === 'public') !== undefined;
65-
const idpAllowed = result.form.identityProviders.find(p => p.code === currentUser.idp) !== undefined;
66-
67-
// check against the public and user's identity provider permissions...
68-
if (!isDraft && !isDeleted) {
69-
if (publicAllowed || idpAllowed) return result;
70-
}
71-
72-
// check against the form level permissions assigned to the user...
73-
const formSubmissionsPermission = await checkFormSubmissionsPermission();
74-
if (!isDeleted && formSubmissionsPermission) return result;
75-
76-
// check against the submission level permissions assigned to the user...
77-
const submissionPermission = await checkSubmissionPermission();
78-
if (submissionPermission) return result;
79-
80-
// no access to this submission...
81-
82-
throw new Problem(401, 'You do not have access to this submission.');
38+
read: async (formSubmissionId) => {
39+
return await service._fetchSubmissionData(formSubmissionId);
8340
},
8441

8542
update: async (formSubmissionId, data, currentUser) => {
8643
let trx;
8744
try {
88-
await service.read(formSubmissionId, currentUser, [Permissions.SUBMISSION_UPDATE]);
89-
9045
trx = await transaction.start(FormSubmission.knex());
9146

92-
// TODO: check if we can update this submission
93-
// TODO: we may have to update permissions for users (draft = false, then no delete?)
94-
9547
await FormSubmission.query(trx).patchAndFetchById(formSubmissionId, { draft: data.draft, submission: data.submission, updatedBy: currentUser.username });
9648
await trx.commit();
9749
const result = await service.read(formSubmissionId);

0 commit comments

Comments
 (0)