From 094e3e936ddac5407fd3f2354d624e6986b0bc4b Mon Sep 17 00:00:00 2001 From: Giovanni Cangiani Date: Wed, 17 Apr 2024 16:45:38 +0200 Subject: [PATCH 01/14] [fix] when directory is a symlink --- scripts/run_docker.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/run_docker.sh b/scripts/run_docker.sh index c256ff141..630af08bb 100755 --- a/scripts/run_docker.sh +++ b/scripts/run_docker.sh @@ -3,7 +3,7 @@ # The script must be called from the root of the github tree, else it returns an error. # This script currently only works on Linux due to differences in network management on Windows/macOS. -if [[ $(git rev-parse --show-toplevel) != $(pwd) ]]; then +if [[ $(git rev-parse --show-toplevel) != $(readlink -fn $(pwd)) ]]; then echo "ERROR: This script must be started from the root of the git repo"; exit 1; fi From 8dfd2fcf36c4c241a77cc667764e0b49048335d6 Mon Sep 17 00:00:00 2001 From: Giovanni Cangiani Date: Wed, 17 Apr 2024 16:47:06 +0200 Subject: [PATCH 02/14] [feat] chunked voter sciper upload --- web/backend/src/authManager.ts | 9 +++ web/backend/src/controllers/users.ts | 32 ++++++--- .../form/components/utils/useChangeAction.tsx | 67 ++++++++++++------- 3 files changed, 76 insertions(+), 32 deletions(-) diff --git a/web/backend/src/authManager.ts b/web/backend/src/authManager.ts index 0c7fc2e88..8137068a5 100644 --- a/web/backend/src/authManager.ts +++ b/web/backend/src/authManager.ts @@ -56,6 +56,15 @@ export async function addPolicy(userID: string, subject: string, permission: str await authEnforcer.addPolicy(userID, subject, permission); await authEnforcer.loadPolicy(); } + +export async function addListPolicy(userIDs: string[], subject: string, permission: string) { + const promises = userIDs.map( (userID) => authEnforcer.addPolicy(userID, subject, permission)); + // TODO: check results... + const results = await Promise.all(promises); + // Reload ACLs + await authEnforcer.loadPolicy(); +} + export async function assignUserPermissionToOwnElection(userID: string, ElectionID: string) { return authEnforcer.addPolicy(userID, ElectionID, PERMISSIONS.ACTIONS.OWN); } diff --git a/web/backend/src/controllers/users.ts b/web/backend/src/controllers/users.ts index 2b0a6f71b..4c79ab8b5 100644 --- a/web/backend/src/controllers/users.ts +++ b/web/backend/src/controllers/users.ts @@ -1,6 +1,6 @@ import express from 'express'; -import { addPolicy, initEnforcer, isAuthorized, PERMISSIONS } from '../authManager'; +import { addPolicy, addListPolicy, initEnforcer, isAuthorized, PERMISSIONS } from '../authManager'; export const usersRouter = express.Router(); @@ -34,14 +34,28 @@ usersRouter.post('/add_role', (req, res, next) => { } } - addPolicy(req.body.userId, req.body.subject, req.body.permission) - .then(() => { - res.set(200).send(); - next(); - }) - .catch((e) => { - res.status(400).send(`Error while adding to roles: ${e}`); - }); + if (Object.hasOwn(req.body, 'userId')) { + addPolicy(req.body.userId, req.body.subject, req.body.permission) + .then(() => { + res.set(200).send(); + next(); + }) + .catch((e) => { + res.status(400).send(`Error while adding to roles: ${e}`); + }); + } else if(Object.hasOwn(req.body, 'userIds')) { + addListPolicy(req.body.userIds, req.body.subject, req.body.permission) + .then(() => { + res.set(200).send(); + next(); + }) + .catch((e) => { + res.status(400).send(`Error while adding to roles: ${e}`); + }); + } else { + res.status(400).send(`Error while adding to roles: bad request, both userId and userIds are missing`); + } + // Call https://search-api.epfl.ch/api/ldap?q=228271, if the answer is // empty then sciper unknown, otherwise add it in userDB diff --git a/web/frontend/src/pages/form/components/utils/useChangeAction.tsx b/web/frontend/src/pages/form/components/utils/useChangeAction.tsx index d2fd4c2c2..c17a2555f 100644 --- a/web/frontend/src/pages/form/components/utils/useChangeAction.tsx +++ b/web/frontend/src/pages/form/components/utils/useChangeAction.tsx @@ -28,6 +28,7 @@ import ShuffleButton from '../ActionButtons/ShuffleButton'; import VoteButton from '../ActionButtons/VoteButton'; import handleLogin from 'pages/session/HandleLogin'; + const useChangeAction = ( status: Status, formID: ID, @@ -74,6 +75,20 @@ const useChangeAction = ( return authorization.has(subject) && authorization.get(subject).indexOf(action) !== -1; } + async function sendVoters(providedScipers, formID) { + const chunkSize = 1000; + for (let i = 0; i < providedScipers.length; i += chunkSize) { + const chunk = providedScipers.slice(i, i + chunkSize); + const request = { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ userIds: chunk, subject: formID, permission: 'vote' }), + }; + await sendFetchRequest(ENDPOINT_ADD_ROLE, request, setIsPosting); + } + } + + const POLLING_INTERVAL = 1000; const MAX_ATTEMPTS = 20; @@ -315,31 +330,37 @@ const useChangeAction = ( useEffect(() => { if (userConfirmedAddVoters.length > 0) { const newUsersArray = []; - for (const sciperStr of userConfirmedAddVoters.split('\n')) { - try { - const sciper = parseInt(sciperStr, 10); - if (sciper < 100000 || sciper > 999999) { - console.error(`SCIPER is out of range. ${sciper} is not between 100000 and 999999`); - } else { - const request = { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ userId: sciper, subject: formID, permission: 'vote' }), - }; - sendFetchRequest(ENDPOINT_ADD_ROLE, request, setIsPosting) - .catch(console.error) - .then(() => { - newUsersArray.push(sciper); - setNewVoters(newUsersArray.join('\n')); - setShowModalAddVotersSuccess(true); - }); - } - } catch (e) { - console.error(`While adding voter: ${e}`); + const badScipers = []; + + const providedScipers=userConfirmedAddVoters.split('\n'); + setUserConfirmedAddVoters(''); + + for (const sciperStr of providedScipers) { + const sciper = parseInt(sciperStr, 10); + if (sciper < 100000 || sciper > 999999) { + console.error(`SCIPER is out of range. ${sciper} is not between 100000 and 999999`); + badScipers.push(sciper); } } - setUserConfirmedAddVoters(''); - setShowModalAddVoters(false); + if (badScipers.length > 0) { + console.error("There are bad scipers. Stopping here."); + // TODO error dialog + // setUserConfirmedAddVoters(''); + + return; + } + + try { + sendVoters(providedScipers, formID) + .catch(console.error) + .then(() => { + setShowModalAddVotersSuccess(true); + }); + } catch (e) { + console.error(`While adding voter: ${e}`); + setUserConfirmedAddVoters('Error uploading scipers'); + setShowModalAddVoters(false); + } } // setUserConfirmedAddVoters(false); }, [formID, sendFetchRequest, userConfirmedAddVoters]); From b9060ec4fd88187bffd4566367e437b7e04b1669 Mon Sep 17 00:00:00 2001 From: Giovanni Cangiani Date: Wed, 17 Apr 2024 17:03:51 +0200 Subject: [PATCH 03/14] [doc] explain why we add sendVoters async function --- .../src/pages/form/components/utils/useChangeAction.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/web/frontend/src/pages/form/components/utils/useChangeAction.tsx b/web/frontend/src/pages/form/components/utils/useChangeAction.tsx index c17a2555f..d9b81ebe1 100644 --- a/web/frontend/src/pages/form/components/utils/useChangeAction.tsx +++ b/web/frontend/src/pages/form/components/utils/useChangeAction.tsx @@ -75,6 +75,10 @@ const useChangeAction = ( return authorization.has(subject) && authorization.get(subject).indexOf(action) !== -1; } + // requests to ENDPOINT_ADD_ROLE cannot be done in parallel because on the + // backend, auths are reloaded from the DB each time there is an update. + // While auths are reloaded, they cannot be checked in a predictable way. + // See isAuthorized, addPolicy, and addListPolicy in backend/src/authManager.ts async function sendVoters(providedScipers, formID) { const chunkSize = 1000; for (let i = 0; i < providedScipers.length; i += chunkSize) { From 5c76450cdaa9552f090bc283829df6bed62f7938 Mon Sep 17 00:00:00 2001 From: Carine Dengler Date: Thu, 18 Apr 2024 18:58:29 +0200 Subject: [PATCH 04/14] style: fixed linting --- .../form/components/utils/useChangeAction.tsx | 46 +++++++++---------- 1 file changed, 21 insertions(+), 25 deletions(-) diff --git a/web/frontend/src/pages/form/components/utils/useChangeAction.tsx b/web/frontend/src/pages/form/components/utils/useChangeAction.tsx index d9b81ebe1..138c3e858 100644 --- a/web/frontend/src/pages/form/components/utils/useChangeAction.tsx +++ b/web/frontend/src/pages/form/components/utils/useChangeAction.tsx @@ -28,7 +28,6 @@ import ShuffleButton from '../ActionButtons/ShuffleButton'; import VoteButton from '../ActionButtons/VoteButton'; import handleLogin from 'pages/session/HandleLogin'; - const useChangeAction = ( status: Status, formID: ID, @@ -52,7 +51,7 @@ const useChangeAction = ( const [showModalDelete, setShowModalDelete] = useState(false); const [showModalAddVoters, setShowModalAddVoters] = useState(false); const [showModalAddVotersSucccess, setShowModalAddVotersSuccess] = useState(false); - const [newVoters, setNewVoters] = useState(''); + const [newVoters] = useState(''); const [userConfirmedProxySetup, setUserConfirmedProxySetup] = useState(false); const [userConfirmedClosing, setUserConfirmedClosing] = useState(false); @@ -75,24 +74,6 @@ const useChangeAction = ( return authorization.has(subject) && authorization.get(subject).indexOf(action) !== -1; } - // requests to ENDPOINT_ADD_ROLE cannot be done in parallel because on the - // backend, auths are reloaded from the DB each time there is an update. - // While auths are reloaded, they cannot be checked in a predictable way. - // See isAuthorized, addPolicy, and addListPolicy in backend/src/authManager.ts - async function sendVoters(providedScipers, formID) { - const chunkSize = 1000; - for (let i = 0; i < providedScipers.length; i += chunkSize) { - const chunk = providedScipers.slice(i, i + chunkSize); - const request = { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ userIds: chunk, subject: formID, permission: 'vote' }), - }; - await sendFetchRequest(ENDPOINT_ADD_ROLE, request, setIsPosting); - } - } - - const POLLING_INTERVAL = 1000; const MAX_ATTEMPTS = 20; @@ -332,11 +313,26 @@ const useChangeAction = ( }, [userConfirmedDeleting]); useEffect(() => { + // requests to ENDPOINT_ADD_ROLE cannot be done in parallel because on the + // backend, auths are reloaded from the DB each time there is an update. + // While auths are reloaded, they cannot be checked in a predictable way. + // See isAuthorized, addPolicy, and addListPolicy in backend/src/authManager.ts + async function sendVoters(providedScipers) { + const chunkSize = 1000; + for (let i = 0; i < providedScipers.length; i += chunkSize) { + const chunk = providedScipers.slice(i, i + chunkSize); + const request = { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ userIds: chunk, subject: formID, permission: 'vote' }), + }; + await sendFetchRequest(ENDPOINT_ADD_ROLE, request, setIsPosting); + } + } if (userConfirmedAddVoters.length > 0) { - const newUsersArray = []; const badScipers = []; - const providedScipers=userConfirmedAddVoters.split('\n'); + const providedScipers = userConfirmedAddVoters.split('\n'); setUserConfirmedAddVoters(''); for (const sciperStr of providedScipers) { @@ -347,15 +343,15 @@ const useChangeAction = ( } } if (badScipers.length > 0) { - console.error("There are bad scipers. Stopping here."); + console.error('There are bad scipers. Stopping here.'); // TODO error dialog - // setUserConfirmedAddVoters(''); + // setUserConfirmedAddVoters(''); return; } try { - sendVoters(providedScipers, formID) + sendVoters(providedScipers) .catch(console.error) .then(() => { setShowModalAddVotersSuccess(true); From 857299f2f76a00f2bd8a561ee03e47b38fcf92d6 Mon Sep 17 00:00:00 2001 From: Carine Dengler Date: Tue, 23 Apr 2024 15:50:38 +0200 Subject: [PATCH 05/14] feat: add error messages --- .../form/components/utils/useChangeAction.tsx | 77 ++++++++++--------- 1 file changed, 39 insertions(+), 38 deletions(-) diff --git a/web/frontend/src/pages/form/components/utils/useChangeAction.tsx b/web/frontend/src/pages/form/components/utils/useChangeAction.tsx index 138c3e858..0d6d06e7c 100644 --- a/web/frontend/src/pages/form/components/utils/useChangeAction.tsx +++ b/web/frontend/src/pages/form/components/utils/useChangeAction.tsx @@ -313,57 +313,58 @@ const useChangeAction = ( }, [userConfirmedDeleting]); useEffect(() => { - // requests to ENDPOINT_ADD_ROLE cannot be done in parallel because on the - // backend, auths are reloaded from the DB each time there is an update. - // While auths are reloaded, they cannot be checked in a predictable way. - // See isAuthorized, addPolicy, and addListPolicy in backend/src/authManager.ts - async function sendVoters(providedScipers) { - const chunkSize = 1000; - for (let i = 0; i < providedScipers.length; i += chunkSize) { - const chunk = providedScipers.slice(i, i + chunkSize); - const request = { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ userIds: chunk, subject: formID, permission: 'vote' }), - }; - await sendFetchRequest(ENDPOINT_ADD_ROLE, request, setIsPosting); - } - } if (userConfirmedAddVoters.length > 0) { - const badScipers = []; + let sciperErrs = ''; const providedScipers = userConfirmedAddVoters.split('\n'); setUserConfirmedAddVoters(''); for (const sciperStr of providedScipers) { const sciper = parseInt(sciperStr, 10); + if (isNaN(sciper)) { + sciperErrs += `'${sciperStr}' is not a number; `; + } if (sciper < 100000 || sciper > 999999) { - console.error(`SCIPER is out of range. ${sciper} is not between 100000 and 999999`); - badScipers.push(sciper); + sciperErrs += `${sciper} is out of range (100000-999999); `; } } - if (badScipers.length > 0) { - console.error('There are bad scipers. Stopping here.'); - // TODO error dialog - // setUserConfirmedAddVoters(''); - + if (sciperErrs.length > 0) { + console.error('Invalid SCIPER numbers found. No request will be send.'); + setTextModalError( + `Invalid SCIPER numbers found. No request has been send. Please fix the following errors: ${sciperErrs}` + ); + setShowModalError(true); return; } - - try { - sendVoters(providedScipers) - .catch(console.error) - .then(() => { - setShowModalAddVotersSuccess(true); - }); - } catch (e) { - console.error(`While adding voter: ${e}`); - setUserConfirmedAddVoters('Error uploading scipers'); - setShowModalAddVoters(false); - } + // requests to ENDPOINT_ADD_ROLE cannot be done in parallel because on the + // backend, auths are reloaded from the DB each time there is an update. + // While auths are reloaded, they cannot be checked in a predictable way. + // See isAuthorized, addPolicy, and addListPolicy in backend/src/authManager.ts + (async () => { + try { + const chunkSize = 1000; + for (let i = 0; i < providedScipers.length; i += chunkSize) { + await sendFetchRequest( + ENDPOINT_ADD_ROLE, + { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + userIds: providedScipers.slice(i, i + chunkSize), + subject: formID, + permission: 'vote', + }), + }, + setIsPosting + ); + } + } catch (e) { + console.error(`While adding voter: ${e}`); + setShowModalAddVoters(false); + } + })(); } - // setUserConfirmedAddVoters(false); - }, [formID, sendFetchRequest, userConfirmedAddVoters]); + }, [formID, sendFetchRequest, userConfirmedAddVoters, t, setTextModalError, setShowModalError]); useEffect(() => { if (userConfirmedProxySetup) { From 7248aa0be0c9cbacb193ce49ad246aa8a26db1b1 Mon Sep 17 00:00:00 2001 From: Carine Dengler Date: Wed, 24 Apr 2024 18:36:46 +0200 Subject: [PATCH 06/14] feat: add loading animation when adding voters --- web/frontend/src/language/de.json | 3 ++- web/frontend/src/language/en.json | 3 ++- web/frontend/src/language/fr.json | 3 ++- .../ActionButtons/AddVotersButton.tsx | 11 +++++++-- .../form/components/utils/useChangeAction.tsx | 24 ++++++++++++++++--- web/frontend/src/types/form.ts | 1 + 6 files changed, 37 insertions(+), 8 deletions(-) diff --git a/web/frontend/src/language/de.json b/web/frontend/src/language/de.json index 9b4531e77..644bc5634 100644 --- a/web/frontend/src/language/de.json +++ b/web/frontend/src/language/de.json @@ -293,6 +293,7 @@ "footerBuild": "build:", "footerBuildTime": "in:", "voteNotVoter": "Wählen nicht erlaubt.", - "voteNotVoterDescription": "Sie sind nicht wahlberechtigt in dieser Wahl. Falls Sie denken, dass ein Fehler vorliegt, wenden Sie sich bitte an die verantwortliche Stelle." + "voteNotVoterDescription": "Sie sind nicht wahlberechtigt in dieser Wahl. Falls Sie denken, dass ein Fehler vorliegt, wenden Sie sich bitte an die verantwortliche Stelle.", + "addVotersLoading": "WählerInnen werden hinzugefügt..." } } diff --git a/web/frontend/src/language/en.json b/web/frontend/src/language/en.json index 89e8bfe5f..5b17c02b5 100644 --- a/web/frontend/src/language/en.json +++ b/web/frontend/src/language/en.json @@ -294,6 +294,7 @@ "footerBuild": "build:", "footerBuildTime": "in:", "voteNotVoter": "Voting not allowed.", - "voteNotVoterDescription": "You are not allowed to vote in this form. If you believe this is an error, please contact the responsible of the service." + "voteNotVoterDescription": "You are not allowed to vote in this form. If you believe this is an error, please contact the responsible of the service.", + "addVotersLoading": "Adding voters..." } } diff --git a/web/frontend/src/language/fr.json b/web/frontend/src/language/fr.json index cb79184cf..af7f33a93 100644 --- a/web/frontend/src/language/fr.json +++ b/web/frontend/src/language/fr.json @@ -293,6 +293,7 @@ "footerBuild": "build:", "footerBuildTime": "en:", "voteNotVoter": "Interdit de voter.", - "voteNotVoterDescription": "Vous n'avez pas le droit de voter dans cette élection. Si vous pensez qu'il s'agit d'une erreur, veuillez contacter le/la reponsable de service." + "voteNotVoterDescription": "Vous n'avez pas le droit de voter dans cette élection. Si vous pensez qu'il s'agit d'une erreur, veuillez contacter le/la reponsable de service.", + "addVotersLoading": "Ajout d'électeur·rice·s..." } } diff --git a/web/frontend/src/pages/form/components/ActionButtons/AddVotersButton.tsx b/web/frontend/src/pages/form/components/ActionButtons/AddVotersButton.tsx index 72e22f4a1..a0d6ab530 100644 --- a/web/frontend/src/pages/form/components/ActionButtons/AddVotersButton.tsx +++ b/web/frontend/src/pages/form/components/ActionButtons/AddVotersButton.tsx @@ -3,12 +3,14 @@ import { useTranslation } from 'react-i18next'; import { isManager } from './../../../../utils/auth'; import { AuthContext } from 'index'; import { useContext } from 'react'; +import IndigoSpinnerIcon from '../IndigoSpinnerIcon'; +import { OngoingAction } from 'types/form'; -const AddVotersButton = ({ handleAddVoters, formID }) => { +const AddVotersButton = ({ handleAddVoters, formID, ongoingAction }) => { const { t } = useTranslation(); const { authorization, isLogged } = useContext(AuthContext); - return ( + return ongoingAction !== OngoingAction.AddVoters ? ( isManager(formID, authorization, isLogged) && ( ) + ) : ( +
+ + {t('addVotersLoading')} +
); }; export default AddVotersButton; diff --git a/web/frontend/src/pages/form/components/utils/useChangeAction.tsx b/web/frontend/src/pages/form/components/utils/useChangeAction.tsx index 0d6d06e7c..ef6596253 100644 --- a/web/frontend/src/pages/form/components/utils/useChangeAction.tsx +++ b/web/frontend/src/pages/form/components/utils/useChangeAction.tsx @@ -343,6 +343,7 @@ const useChangeAction = ( (async () => { try { const chunkSize = 1000; + setOngoingAction(OngoingAction.AddVoters); for (let i = 0; i < providedScipers.length; i += chunkSize) { await sendFetchRequest( ENDPOINT_ADD_ROLE, @@ -362,9 +363,18 @@ const useChangeAction = ( console.error(`While adding voter: ${e}`); setShowModalAddVoters(false); } + setOngoingAction(OngoingAction.None); })(); } - }, [formID, sendFetchRequest, userConfirmedAddVoters, t, setTextModalError, setShowModalError]); + }, [ + formID, + sendFetchRequest, + userConfirmedAddVoters, + t, + setTextModalError, + setShowModalError, + setOngoingAction, + ]); useEffect(() => { if (userConfirmedProxySetup) { @@ -537,7 +547,11 @@ const useChangeAction = ( formID={formID} /> - + ); case Status.Open: @@ -557,7 +571,11 @@ const useChangeAction = ( /> - + ); case Status.Closed: diff --git a/web/frontend/src/types/form.ts b/web/frontend/src/types/form.ts index 086ae2f75..d28e2bf87 100644 --- a/web/frontend/src/types/form.ts +++ b/web/frontend/src/types/form.ts @@ -42,6 +42,7 @@ export const enum OngoingAction { Decrypting, Combining, Canceling, + AddVoters, } interface FormInfo { From e648bc2641fff0a201f26e8d6730a12ec76d2d3d Mon Sep 17 00:00:00 2001 From: Carine Dengler Date: Wed, 24 Apr 2024 19:01:02 +0200 Subject: [PATCH 07/14] feat: use translations for error messages --- web/frontend/src/language/de.json | 5 ++++- web/frontend/src/language/en.json | 5 ++++- web/frontend/src/language/fr.json | 5 ++++- .../src/pages/form/components/utils/useChangeAction.tsx | 9 +++------ 4 files changed, 15 insertions(+), 9 deletions(-) diff --git a/web/frontend/src/language/de.json b/web/frontend/src/language/de.json index 644bc5634..5004f3152 100644 --- a/web/frontend/src/language/de.json +++ b/web/frontend/src/language/de.json @@ -294,6 +294,9 @@ "footerBuildTime": "in:", "voteNotVoter": "Wählen nicht erlaubt.", "voteNotVoterDescription": "Sie sind nicht wahlberechtigt in dieser Wahl. Falls Sie denken, dass ein Fehler vorliegt, wenden Sie sich bitte an die verantwortliche Stelle.", - "addVotersLoading": "WählerInnen werden hinzugefügt..." + "addVotersLoading": "WählerInnen werden hinzugefügt...", + "sciperNaN": "'{{sciperStr}}' ist keine Zahl; ", + "sciperOutOfRange": "{{sciper}} ist nicht in dem erlaubten Bereich (100000-999999); ", + "invalidScipersFound": "Ungültige SCIPERs wurden gefunden. Es wurde keine Anfrage gesendet. Bitte korrigieren Sie folgende Fehler: {{sciperErrs}}" } } diff --git a/web/frontend/src/language/en.json b/web/frontend/src/language/en.json index 5b17c02b5..4d9c24485 100644 --- a/web/frontend/src/language/en.json +++ b/web/frontend/src/language/en.json @@ -295,6 +295,9 @@ "footerBuildTime": "in:", "voteNotVoter": "Voting not allowed.", "voteNotVoterDescription": "You are not allowed to vote in this form. If you believe this is an error, please contact the responsible of the service.", - "addVotersLoading": "Adding voters..." + "addVotersLoading": "Adding voters...", + "sciperNaN": "'{{sciperStr}}' is not a number; ", + "sciperOutOfRange": "{{sciper}} is out of range (100000-999999); ", + "invalidScipersFound": "Invalid SCIPER numbers found. No request has been send. Please fix the following errors: {{sciperErrs}}" } } diff --git a/web/frontend/src/language/fr.json b/web/frontend/src/language/fr.json index af7f33a93..3bb797126 100644 --- a/web/frontend/src/language/fr.json +++ b/web/frontend/src/language/fr.json @@ -294,6 +294,9 @@ "footerBuildTime": "en:", "voteNotVoter": "Interdit de voter.", "voteNotVoterDescription": "Vous n'avez pas le droit de voter dans cette élection. Si vous pensez qu'il s'agit d'une erreur, veuillez contacter le/la reponsable de service.", - "addVotersLoading": "Ajout d'électeur·rice·s..." + "addVotersLoading": "Ajout d'électeur·rice·s...", + "sciperNaN": "'{{sciperStr}}' n'est pas une chiffre; ", + "sciperOutOfRange": "{{sciper}} n'est pas dans les valeurs acceptées (100000-999999); ", + "invalidScipersFound": "Des SCIPERs invalides ont été trouvés. Aucune requête n'a été envoyée. Veuillez corriger les erreurs suivants: {{sciperErrs}}" } } diff --git a/web/frontend/src/pages/form/components/utils/useChangeAction.tsx b/web/frontend/src/pages/form/components/utils/useChangeAction.tsx index ef6596253..91bab6633 100644 --- a/web/frontend/src/pages/form/components/utils/useChangeAction.tsx +++ b/web/frontend/src/pages/form/components/utils/useChangeAction.tsx @@ -322,17 +322,14 @@ const useChangeAction = ( for (const sciperStr of providedScipers) { const sciper = parseInt(sciperStr, 10); if (isNaN(sciper)) { - sciperErrs += `'${sciperStr}' is not a number; `; + sciperErrs += t('sciperNaN', { sciperStr: sciperStr }); } if (sciper < 100000 || sciper > 999999) { - sciperErrs += `${sciper} is out of range (100000-999999); `; + sciperErrs += t('sciperOutOfRange', { sciper: sciper }); } } if (sciperErrs.length > 0) { - console.error('Invalid SCIPER numbers found. No request will be send.'); - setTextModalError( - `Invalid SCIPER numbers found. No request has been send. Please fix the following errors: ${sciperErrs}` - ); + setTextModalError(t('invalidScipersFound', { sciperErrs: sciperErrs })); setShowModalError(true); return; } From 71e887b6dead3909a98a3c3b971d6b1dceaeee49 Mon Sep 17 00:00:00 2001 From: Carine Dengler Date: Tue, 30 Apr 2024 17:08:39 +0200 Subject: [PATCH 08/14] fix: clean-up error handling --- web/backend/src/authManager.ts | 13 ++++++---- web/backend/src/controllers/users.ts | 38 ++++++++++++++-------------- 2 files changed, 27 insertions(+), 24 deletions(-) diff --git a/web/backend/src/authManager.ts b/web/backend/src/authManager.ts index 8137068a5..1f8457683 100644 --- a/web/backend/src/authManager.ts +++ b/web/backend/src/authManager.ts @@ -58,11 +58,14 @@ export async function addPolicy(userID: string, subject: string, permission: str } export async function addListPolicy(userIDs: string[], subject: string, permission: string) { - const promises = userIDs.map( (userID) => authEnforcer.addPolicy(userID, subject, permission)); - // TODO: check results... - const results = await Promise.all(promises); - // Reload ACLs - await authEnforcer.loadPolicy(); + const promises = userIDs.map((userID) => authEnforcer.addPolicy(userID, subject, permission)); + try { + await Promise.all(promises); + } catch(error) { + // At least one policy update has failed, but we need to reload ACLs anyway for the succeeding ones + await authEnforcer.loadPolicy(); + throw new Error(`Failed to add policies for all users: ${error}`); + } } export async function assignUserPermissionToOwnElection(userID: string, ElectionID: string) { diff --git a/web/backend/src/controllers/users.ts b/web/backend/src/controllers/users.ts index 4c79ab8b5..8abbb323f 100644 --- a/web/backend/src/controllers/users.ts +++ b/web/backend/src/controllers/users.ts @@ -34,26 +34,26 @@ usersRouter.post('/add_role', (req, res, next) => { } } - if (Object.hasOwn(req.body, 'userId')) { - addPolicy(req.body.userId, req.body.subject, req.body.permission) - .then(() => { - res.set(200).send(); - next(); - }) - .catch((e) => { - res.status(400).send(`Error while adding to roles: ${e}`); - }); - } else if(Object.hasOwn(req.body, 'userIds')) { - addListPolicy(req.body.userIds, req.body.subject, req.body.permission) - .then(() => { - res.set(200).send(); - next(); - }) - .catch((e) => { - res.status(400).send(`Error while adding to roles: ${e}`); - }); + if ('userId' in req.body) { + try { + addPolicy(req.body.userId, req.body.subject, req.body.permission) + } catch (error) { + res.status(400).send(`Error while adding single user to roles: ${error}`); + return; + } + res.set(200).send(); + } else if ('userIds' in req.body) { + try { + addListPolicy(req.body.userIds, req.body.subject, req.body.permission) + } catch (error) { + res.status(400).send(`Error while adding multiple users to roles: ${error}`); + return; + } + res.set(200).send(); } else { - res.status(400).send(`Error while adding to roles: bad request, both userId and userIds are missing`); + res + .status(400) + .send(`Error: at least one of 'userId' or 'userIds' must be send in the request`); } From 2610d4f5801e9f35e6bc17a63cafaa7b2adc62a4 Mon Sep 17 00:00:00 2001 From: Carine Dengler Date: Tue, 30 Apr 2024 17:09:07 +0200 Subject: [PATCH 09/14] fix: remove outdated comment --- web/backend/src/controllers/users.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/web/backend/src/controllers/users.ts b/web/backend/src/controllers/users.ts index 8abbb323f..1dd50677a 100644 --- a/web/backend/src/controllers/users.ts +++ b/web/backend/src/controllers/users.ts @@ -57,8 +57,6 @@ usersRouter.post('/add_role', (req, res, next) => { } - // Call https://search-api.epfl.ch/api/ldap?q=228271, if the answer is - // empty then sciper unknown, otherwise add it in userDB }); // This call (only for admins) allow an admin to remove a role to a user. From 1b90a30636ad0677721d81ef0c85d94e68ab4128 Mon Sep 17 00:00:00 2001 From: Carine Dengler Date: Tue, 30 Apr 2024 17:11:22 +0200 Subject: [PATCH 10/14] fix: add missing call --- web/backend/src/controllers/users.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/web/backend/src/controllers/users.ts b/web/backend/src/controllers/users.ts index 1dd50677a..4950dbe68 100644 --- a/web/backend/src/controllers/users.ts +++ b/web/backend/src/controllers/users.ts @@ -36,27 +36,27 @@ usersRouter.post('/add_role', (req, res, next) => { if ('userId' in req.body) { try { - addPolicy(req.body.userId, req.body.subject, req.body.permission) + addPolicy(req.body.userId, req.body.subject, req.body.permission); } catch (error) { res.status(400).send(`Error while adding single user to roles: ${error}`); return; } res.set(200).send(); + next(); } else if ('userIds' in req.body) { try { - addListPolicy(req.body.userIds, req.body.subject, req.body.permission) + addListPolicy(req.body.userIds, req.body.subject, req.body.permission); } catch (error) { res.status(400).send(`Error while adding multiple users to roles: ${error}`); return; } res.set(200).send(); + next(); } else { res .status(400) .send(`Error: at least one of 'userId' or 'userIds' must be send in the request`); } - - }); // This call (only for admins) allow an admin to remove a role to a user. From ca99a06027131b6b78dbb50c4aecc2926ff8cea1 Mon Sep 17 00:00:00 2001 From: Carine Dengler Date: Tue, 30 Apr 2024 17:12:05 +0200 Subject: [PATCH 11/14] style: apply linting --- web/backend/src/authManager.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/backend/src/authManager.ts b/web/backend/src/authManager.ts index 1f8457683..5f49962af 100644 --- a/web/backend/src/authManager.ts +++ b/web/backend/src/authManager.ts @@ -61,7 +61,7 @@ export async function addListPolicy(userIDs: string[], subject: string, permissi const promises = userIDs.map((userID) => authEnforcer.addPolicy(userID, subject, permission)); try { await Promise.all(promises); - } catch(error) { + } catch (error) { // At least one policy update has failed, but we need to reload ACLs anyway for the succeeding ones await authEnforcer.loadPolicy(); throw new Error(`Failed to add policies for all users: ${error}`); From 015e8e2a7b1abe348561a96e857a8e954b521afe Mon Sep 17 00:00:00 2001 From: Carine Dengler Date: Wed, 1 May 2024 21:20:00 +0200 Subject: [PATCH 12/14] fix: fix SCIPER verification and add it to adding policy functions --- web/backend/src/authManager.ts | 3 +++ web/backend/src/controllers/users.ts | 11 ++++++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/web/backend/src/authManager.ts b/web/backend/src/authManager.ts index 5f49962af..9b271eec8 100644 --- a/web/backend/src/authManager.ts +++ b/web/backend/src/authManager.ts @@ -99,6 +99,9 @@ export function setMapAuthorization(list: string[][]): Map // the range between 100000 and 999999, an error is thrown. export function readSCIPER(s: string): number { const n = parseInt(s, 10); + if (isNaN(n)) { + throw new Error(`${s} is not a number`); + } if (n < 100000 || n > 999999) { throw new Error(`SCIPER is out of range. ${n} is not between 100000 and 999999`); } diff --git a/web/backend/src/controllers/users.ts b/web/backend/src/controllers/users.ts index 4950dbe68..5951782d0 100644 --- a/web/backend/src/controllers/users.ts +++ b/web/backend/src/controllers/users.ts @@ -1,6 +1,13 @@ import express from 'express'; -import { addPolicy, addListPolicy, initEnforcer, isAuthorized, PERMISSIONS } from '../authManager'; +import { + addPolicy, + addListPolicy, + initEnforcer, + isAuthorized, + PERMISSIONS, + readSCIPER, +} from '../authManager'; export const usersRouter = express.Router(); @@ -36,6 +43,7 @@ usersRouter.post('/add_role', (req, res, next) => { if ('userId' in req.body) { try { + readSCIPER(req.body.userId); addPolicy(req.body.userId, req.body.subject, req.body.permission); } catch (error) { res.status(400).send(`Error while adding single user to roles: ${error}`); @@ -45,6 +53,7 @@ usersRouter.post('/add_role', (req, res, next) => { next(); } else if ('userIds' in req.body) { try { + req.body.userIds.every(readSCIPER); addListPolicy(req.body.userIds, req.body.subject, req.body.permission); } catch (error) { res.status(400).send(`Error while adding multiple users to roles: ${error}`); From 3f2ed9d2be6ab21c3d3eeda6e70750097020e120 Mon Sep 17 00:00:00 2001 From: Carine Dengler Date: Mon, 6 May 2024 17:12:45 +0200 Subject: [PATCH 13/14] fix: use Number.isNaN --- web/backend/src/authManager.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/backend/src/authManager.ts b/web/backend/src/authManager.ts index 9b271eec8..b2cd19250 100644 --- a/web/backend/src/authManager.ts +++ b/web/backend/src/authManager.ts @@ -99,7 +99,7 @@ export function setMapAuthorization(list: string[][]): Map // the range between 100000 and 999999, an error is thrown. export function readSCIPER(s: string): number { const n = parseInt(s, 10); - if (isNaN(n)) { + if (Number.isNaN(n)) { throw new Error(`${s} is not a number`); } if (n < 100000 || n > 999999) { From 6f748b270c85b015caae5309dc62b08d1fa61bd7 Mon Sep 17 00:00:00 2001 From: Carine Dengler Date: Mon, 6 May 2024 17:13:11 +0200 Subject: [PATCH 14/14] fix: add missing await statements --- web/backend/src/controllers/users.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/web/backend/src/controllers/users.ts b/web/backend/src/controllers/users.ts index 5951782d0..1ca5328cc 100644 --- a/web/backend/src/controllers/users.ts +++ b/web/backend/src/controllers/users.ts @@ -29,7 +29,7 @@ usersRouter.get('/user_rights', (req, res) => { }); // This call (only for admins) allows an admin to add a role to a voter. -usersRouter.post('/add_role', (req, res, next) => { +usersRouter.post('/add_role', async (req, res, next) => { if (!isAuthorized(req.session.userId, PERMISSIONS.SUBJECTS.ROLES, PERMISSIONS.ACTIONS.ADD)) { res.status(400).send('Unauthorized - only admins allowed'); return; @@ -44,7 +44,7 @@ usersRouter.post('/add_role', (req, res, next) => { if ('userId' in req.body) { try { readSCIPER(req.body.userId); - addPolicy(req.body.userId, req.body.subject, req.body.permission); + await addPolicy(req.body.userId, req.body.subject, req.body.permission); } catch (error) { res.status(400).send(`Error while adding single user to roles: ${error}`); return; @@ -54,7 +54,7 @@ usersRouter.post('/add_role', (req, res, next) => { } else if ('userIds' in req.body) { try { req.body.userIds.every(readSCIPER); - addListPolicy(req.body.userIds, req.body.subject, req.body.permission); + await addListPolicy(req.body.userIds, req.body.subject, req.body.permission); } catch (error) { res.status(400).send(`Error while adding multiple users to roles: ${error}`); return;