Skip to content

Commit

Permalink
Ensure all correct perms are applied to callable functions and firest…
Browse files Browse the repository at this point in the history
…ore rules. (#1289)

Add new tests and fix existing tests for callable functions and firestore rules.
Comment out unused callable functions.
  • Loading branch information
drieJAC authored Dec 23, 2024
1 parent 9bd841b commit cc8f3a9
Show file tree
Hide file tree
Showing 34 changed files with 1,377 additions and 42 deletions.
17 changes: 12 additions & 5 deletions database/firestore.rules
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ service cloud.firestore {
request.resource.data[formId].status in ["created", "requested"];
}

function dataHasRoleId() {
return request.resource.data.role.id != null;
}

match /databases/{database}/documents {

match /{document=**} {
Expand Down Expand Up @@ -114,7 +118,7 @@ service cloud.firestore {
allow get, create, update: if request.auth.uid == panellistId;

// Allow JAC to read and write
allow read, write: if userIsAuthenticated() && userIsJAC();
allow read, write: if userIsAuthenticated() && userIsJAC() && hasPermission('pa1');
}

match /meta/stats {
Expand Down Expand Up @@ -302,7 +306,7 @@ service cloud.firestore {
allow read: if userIsAuthenticated() && userIsJAC() && hasPermission('e3'); // can update exercise

allow create: if userIsAuthenticated() && userIsJAC() && hasPermission('u6');
allow update: if userIsAuthenticated() && userIsJAC() && hasPermission('u2');
allow update: if userIsAuthenticated() && userIsJAC() && hasPermission('u2') && dataHasRoleId();
allow delete: if userIsAuthenticated() && userIsJAC() && hasPermission('u4');
}

Expand All @@ -316,13 +320,16 @@ service cloud.firestore {

match /roles/{roleId} {
allow read: if userIsAuthenticated() && userIsJAC();
allow write: if userIsAuthenticated() && userIsJAC() && hasPermission('u3');
allow create: if userIsAuthenticated() && userIsJAC() && hasPermission('u5');
allow update: if userIsAuthenticated() && userIsJAC() && hasPermission('u3');
}

match /candidateForms/{formId} {
allow read: if userIsAuthenticated() && userIsJAC();
allow read: if userIsAuthenticated() && userIsJAC() && hasPermission('cf1');
allow read: if userIsAuthenticated() && currentUser() in resource.data.candidateIds;
allow write: if userIsAuthenticated() && userIsJAC() && hasPermission('cf3');
allow create: if userIsAuthenticated() && userIsJAC() && hasPermission('cf2');
allow update: if userIsAuthenticated() && userIsJAC() && hasPermission('cf3');
allow delete: if userIsAuthenticated() && userIsJAC() && hasPermission('cf4');

match /responses/{responseId} {
// jac admins can read and update
Expand Down
4 changes: 4 additions & 0 deletions functions/callableFunctions/adminDisableNewUser.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { db, auth } from '../shared/admin.js';
import { checkArguments } from '../shared/helpers.js';
import initUserRoles from '../actions/userRoles.js';
import initServiceSettings from '../shared/serviceSettings.js';
import { PERMISSIONS, hasPermissions } from '../shared/permissions.js';

const { disableNewUser } = initUserRoles(db, auth);
const { checkFunctionEnabled } = initServiceSettings(db);
Expand All @@ -12,6 +13,9 @@ export default functions.region('europe-west2').https.onCall(async (data, contex
if (!context.auth) {
throw new functions.https.HttpsError('failed-precondition', 'The function must be called while authenticated.');
}

hasPermissions(context.auth.token.rp, [PERMISSIONS.users.permissions.canEnableUsers.value]);

if (!checkArguments({
uid: { required: true },
}, data)) {
Expand Down
3 changes: 3 additions & 0 deletions functions/callableFunctions/adminSyncUserRolePermissions.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import functions from 'firebase-functions';
import { db, auth } from '../shared/admin.js';
import initUserRoles from '../actions/userRoles.js';
import initServiceSettings from '../shared/serviceSettings.js';
import { PERMISSIONS, hasPermissions } from '../shared/permissions.js';

const { adminSyncUserRolePermissions } = initUserRoles(db, auth);
const { checkFunctionEnabled } = initServiceSettings(db);
Expand All @@ -12,6 +13,8 @@ export default functions.region('europe-west2').https.onCall(async (data, contex
throw new functions.https.HttpsError('failed-precondition', 'The function must be called while authenticated.');
}

hasPermissions(context.auth.token.rp, [PERMISSIONS.users.permissions.canEditRolePermissions.value]);

//TODO: add role check here
return await adminSyncUserRolePermissions(context.auth.uid);

Expand Down
6 changes: 6 additions & 0 deletions functions/callableFunctions/createTestApplications.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { firebase, db, auth } from '../shared/admin.js';
import config from '../shared/config.js';
import initApplications from '../actions/applications/applications.js';
import { isProduction } from '../shared/helpers.js';
import { PERMISSIONS, hasPermissions } from '../shared/permissions.js';

const { loadTestApplications, createTestApplications } = initApplications(config, firebase, db, auth);

Expand All @@ -20,6 +21,11 @@ export default functions.runWith(runtimeOptions).region('europe-west2').https.on
if (!context.auth) {
throw new functions.https.HttpsError('failed-precondition', 'The function must be called while authenticated.');
}

hasPermissions(context.auth.token.rp, [
PERMISSIONS.applications.permissions.canCreateTestApplications.value,
]);

if (!(typeof data.exerciseId === 'string') || data.exerciseId.length === 0) {
throw new functions.https.HttpsError('invalid-argument', 'Please specify an exercise id');
}
Expand Down
3 changes: 3 additions & 0 deletions functions/callableFunctions/customReport.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import config from '../shared/config.js';
import { firebase, db } from '../shared/admin.js';
import initCustomReport from '../actions/exercises/customReport.js';
import initServiceSettings from '../shared/serviceSettings.js';
import { PERMISSIONS, hasPermissions } from '../shared/permissions.js';

const { customReport } = initCustomReport(config, firebase, db, auth);
const { checkFunctionEnabled } = initServiceSettings(db);
Expand All @@ -14,5 +15,7 @@ export default functions.region('europe-west2').https.onCall(async (data, contex
throw new functions.https.HttpsError('failed-precondition', 'The function must be called while authenticated.');
}

hasPermissions(context.auth.token.rp, [PERMISSIONS.applications.permissions.canReadApplications.value]);

return customReport(data, context);
});
7 changes: 7 additions & 0 deletions functions/callableFunctions/getUserEmailByID.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { auth, db } from '../shared/admin.js';
import { checkArguments } from '../shared/helpers.js';
import initGetUserEmailByID from '../actions/candidates/getUserEmailByID.js';
import initServiceSettings from '../shared/serviceSettings.js';
import { PERMISSIONS, hasPermissions } from '../shared/permissions.js';

const getUserEmailByID = initGetUserEmailByID(auth);
const { checkFunctionEnabled } = initServiceSettings(db);
Expand All @@ -13,6 +14,12 @@ export default functions.region('europe-west2').https.onCall(async (data, contex
if (!context.auth) {
throw new functions.https.HttpsError('failed-precondition', 'The function must be called while authenticated.');
}

// Function is called when updating a candidate in Admin
hasPermissions(context.auth.token.rp, [
PERMISSIONS.candidates.permissions.canUpdateCandidates.value,
]);

if (!checkArguments({
candidateId: { required: true },
}, data)) {
Expand Down
7 changes: 7 additions & 0 deletions functions/callableFunctions/processNotifications.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import config from '../shared/config.js';
import { db, firebase } from '../shared/admin.js';
import initNotifications from '../actions/notifications.js';
import initServiceSettings from '../shared/serviceSettings.js';
import { PERMISSIONS, hasPermissions } from '../shared/permissions.js';

const { processNotifications } = initNotifications(config, firebase, db);
const { checkFunctionEnabled } = initServiceSettings(db);
Expand All @@ -12,5 +13,11 @@ export default functions.region('europe-west2').https.onCall(async (data, contex
if (!context.auth) {
throw new functions.https.HttpsError('failed-precondition', 'The function must be called while authenticated.');
}

hasPermissions(context.auth.token.rp, [
PERMISSIONS.notifications.permissions.canUpdateNotifications.value,
PERMISSIONS.settings.permissions.canUpdateSettings.value,
]);

return await processNotifications();
});
6 changes: 6 additions & 0 deletions functions/callableFunctions/targetedOutreachReport.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { firebase, db } from '../shared/admin.js';
import { checkArguments } from '../shared/helpers.js';
import initTargetedOutreachReport from '../actions/exercises/targetedOutreachReport.js';
import initServiceSettings from '../shared/serviceSettings.js';
import { PERMISSIONS, hasPermissions } from '../shared/permissions.js';

const { targetedOutreachReport } = initTargetedOutreachReport(firebase, db);
const { checkFunctionEnabled } = initServiceSettings(db);
Expand All @@ -12,6 +13,11 @@ export default functions.region('europe-west2').https.onCall(async (data, contex
if (!context.auth) {
throw new functions.https.HttpsError('failed-precondition', 'The function must be called while authenticated.');
}

hasPermissions(context.auth.token.rp, [
PERMISSIONS.candidates.permissions.canReadCandidates.value,
]);

if (!checkArguments({
nationalInsuranceNumbers: { required: true },
}, data)) {
Expand Down
6 changes: 6 additions & 0 deletions functions/callableFunctions/tasks/createTask.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { firebase, db } from '../../shared/admin.js';
import { checkArguments } from '../../shared/helpers.js';
import initCreateTask from '../../actions/tasks/createTask.js';
import initServiceSettings from '../../shared/serviceSettings.js';
import { PERMISSIONS, hasPermissions } from '../../shared/permissions.js';

const createTask = initCreateTask(config, firebase, db);
const { checkFunctionEnabled } = initServiceSettings(db);
Expand All @@ -16,6 +17,11 @@ export default functions.runWith({
if (!context.auth) {
throw new functions.https.HttpsError('failed-precondition', 'The function must be called while authenticated.');
}

hasPermissions(context.auth.token.rp, [
PERMISSIONS.tasks.permissions.canCreate.value,
]);

if (!checkArguments({
exerciseId: { required: true },
type: { required: true },
Expand Down
6 changes: 6 additions & 0 deletions functions/callableFunctions/tasks/updateTask.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { firebase, db } from '../../shared/admin.js';
import { checkArguments } from '../../shared/helpers.js';
import initUpdateTask from '../../actions/tasks/updateTask.js';
import initServiceSettings from '../../shared/serviceSettings.js';
import { PERMISSIONS, hasPermissions } from '../../shared/permissions.js';

const { updateTask } = initUpdateTask(config, firebase, db);
const { checkFunctionEnabled } = initServiceSettings(db);
Expand All @@ -16,6 +17,11 @@ export default functions.runWith({
if (!context.auth) {
throw new functions.https.HttpsError('failed-precondition', 'The function must be called while authenticated.');
}

hasPermissions(context.auth.token.rp, [
PERMISSIONS.tasks.permissions.canUpdate.value,
]);

if (!checkArguments({
exerciseId: { required: true },
type: { required: true },
Expand Down
8 changes: 0 additions & 8 deletions functions/callableFunctions/verifySlackUser.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,6 @@ export default functions.region('europe-west2').https.onCall(async (data, contex
throw new functions.https.HttpsError('failed-precondition', 'The function must be called while authenticated.');
}

//console.log('Verify Slack User');

// hasPermissions(context.auth.token.rp, [
// PERMISSIONS.applications.permissions.canReadApplications.value,
// PERMISSIONS.applications.permissions.canUpdateApplications.value,
// PERMISSIONS.notifications.permissions.canCreateNotifications.value,
// ]);

if (!checkArguments({
userId: { required: true },
slackMemberId: { required: true },
Expand Down
40 changes: 20 additions & 20 deletions functions/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ import enableCharacterChecks from './callableFunctions/enableCharacterChecks.js'
import initialiseMissingApplicationRecords from './callableFunctions/initialiseMissingApplicationRecords.js';
import exportExerciseData from './callableFunctions/exportExerciseData.js';
import targetedOutreachReport from './callableFunctions/targetedOutreachReport.js';
import transferHandoverData from './callableFunctions/transferHandoverData.js';
//import transferHandoverData from './callableFunctions/transferHandoverData.js';
import exportApplicationContactsData from './callableFunctions/exportApplicationContactsData.js';
import exportApplicationEligibilityIssues from './callableFunctions/exportApplicationEligibilityIssues.js';
import generateHandoverReport from './callableFunctions/generateHandoverReport.js';
Expand All @@ -61,26 +61,26 @@ import scanFile from './callableFunctions/scanFile.js';
import scanAllFiles from './callableFunctions/scanAllFiles.js';
import exportApplicationCharacterIssues from './callableFunctions/exportApplicationCharacterIssues.js';
import getUserEmailByID from './callableFunctions/getUserEmailByID.js';
import getUserByEmail from './callableFunctions/getUserByEmail.js';
//import getUserByEmail from './callableFunctions/getUserByEmail.js';
import updateEmailAddress from './callableFunctions/updateEmailAddress.js';
import ensureEmailVerified from './callableFunctions/ensureEmailVerified.js';
import adminGetUsers from './callableFunctions/adminGetUsers.js';
import adminGetUserRoles from './callableFunctions/adminGetUserRoles.js';
//import adminGetUsers from './callableFunctions/adminGetUsers.js';
//import adminGetUserRoles from './callableFunctions/adminGetUserRoles.js';
import adminDisableUser from './callableFunctions/adminDisableUser.js';
import adminCreateUserRole from './callableFunctions/adminCreateUserRole.js';
import adminUpdateUserRole from './callableFunctions/adminUpdateUserRole.js';
import adminSetUserRole from './callableFunctions/adminSetUserRole.js';
import adminSetDefaultRole from './callableFunctions/adminSetDefaultRole.js';
import adminDisableNewUser from './callableFunctions/adminDisableNewUser.js';
import adminSyncUserRolePermissions from './callableFunctions/adminSyncUserRolePermissions.js';
import createUser from './callableFunctions/createUser.js';
//import createUser from './callableFunctions/createUser.js';
import deleteUsers from './callableFunctions/deleteUsers.js';
import customReport from './callableFunctions/customReport.js';
import refreshApplicationCounts from './callableFunctions/refreshApplicationCounts.js';
import createTestApplications from './callableFunctions/createTestApplications.js';
import deleteApplications from './callableFunctions/deleteApplications.js';
import createTestUsers from './callableFunctions/createTestUsers.js';
import deleteTestUsers from './callableFunctions/deleteTestUsers.js';
//import createTestUsers from './callableFunctions/createTestUsers.js';
//import deleteTestUsers from './callableFunctions/deleteTestUsers.js';
import createTask from './callableFunctions/tasks/createTask.js';
import updateTask from './callableFunctions/tasks/updateTask.js';
import verifyRecaptcha from './callableFunctions/verifyRecaptcha.js';
Expand All @@ -102,9 +102,9 @@ import exportSccSummaryReport from './callableFunctions/exportSccSummaryReport.j
import getMultipleApplicationData from './callableFunctions/getMultipleApplicationData.js';

// Callable - QTs v2
import listQualifyingTests from './callableFunctions/qualifyingTests/v2/listQualifyingTests.js';
import updateQualifyingTestParticipants from './callableFunctions/qualifyingTests/v2/updateQualifyingTestParticipants.js';
import updateQualifyingTestScores from './callableFunctions/qualifyingTests/v2/updateQualifyingTestScores.js';
//import listQualifyingTests from './callableFunctions/qualifyingTests/v2/listQualifyingTests.js';
//import updateQualifyingTestParticipants from './callableFunctions/qualifyingTests/v2/updateQualifyingTestParticipants.js';
//import updateQualifyingTestScores from './callableFunctions/qualifyingTests/v2/updateQualifyingTestScores.js';

// HTTP
import ticketingGithubWebhook from './httpFunctions/ticketingGithubWebhook.js';
Expand Down Expand Up @@ -160,7 +160,7 @@ export {
initialiseMissingApplicationRecords,
exportExerciseData,
targetedOutreachReport,
transferHandoverData,
//transferHandoverData,
exportApplicationContactsData,
exportApplicationEligibilityIssues,
generateHandoverReport,
Expand All @@ -173,26 +173,26 @@ export {
scanAllFiles,
exportApplicationCharacterIssues,
getUserEmailByID,
getUserByEmail,
//getUserByEmail,
updateEmailAddress,
ensureEmailVerified,
adminGetUsers,
adminGetUserRoles,
//adminGetUsers,
//adminGetUserRoles,
adminDisableUser,
adminCreateUserRole,
adminUpdateUserRole,
adminSetUserRole,
adminSetDefaultRole,
adminDisableNewUser,
adminSyncUserRolePermissions,
createUser,
//createUser,
deleteUsers,
customReport,
refreshApplicationCounts,
createTestApplications,
deleteApplications,
createTestUsers,
deleteTestUsers,
//createTestUsers,
//deleteTestUsers,
createTask,
updateTask,
verifyRecaptcha,
Expand All @@ -214,9 +214,9 @@ export {
exportSccSummaryReport,

// Callable - QTs v2
listQualifyingTests,
updateQualifyingTestParticipants,
updateQualifyingTestScores,
//listQualifyingTests,
//updateQualifyingTestParticipants,
//updateQualifyingTestScores,

// HTTP
ticketingGithubWebhook
Expand Down
26 changes: 18 additions & 8 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit cc8f3a9

Please sign in to comment.