Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Non-voter user should not see the ballot #117

Merged
merged 2 commits into from
Feb 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions web/frontend/src/language/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@
"operationFailure": "Der Vorgang ist fehlgeschlagen. Versuchen Sie, die Seite zu aktualisieren.",
"shuffleFail": "Die Zufallsmischung ist fehlgeschlagen.",
"voteImpossible": "Unmöglich abstimmen",
"notFoundVoteImpossible": "Zurück zur Formulartabelle",
"returnToFormTable": "Zurück zur Formulartabelle",
"voteImpossibleDescription": "Das Formular ist nicht mehr zur Abstimmung geöffnet.",
"yes": "Ja",
"no": "Nein",
Expand Down Expand Up @@ -289,6 +289,8 @@
"footerUnknown": "?",
"footerVersion": "version:",
"footerBuild": "build:",
"footerBuildTime": "in:"
"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."
}
}
6 changes: 4 additions & 2 deletions web/frontend/src/language/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@
"operationFailure": "The operation failed. Try refreshing the page.",
"shuffleFail": "The shuffle operation failed.",
"voteImpossible": "Vote Impossible",
"notFoundVoteImpossible": "Go back to form table",
"returnToFormTable": "Go back to form table",
"voteImpossibleDescription": "The form is not open for voting anymore.",
"yes": "Yes",
"no": "No",
Expand Down Expand Up @@ -290,6 +290,8 @@
"footerUnknown": "?",
"footerVersion": "version:",
"footerBuild": "build:",
"footerBuildTime": "in:"
"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."
}
}
6 changes: 4 additions & 2 deletions web/frontend/src/language/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@
"operationFailure": "L'opération a échoué. Essayez de rafraichir la page.",
"shuffleFail": "L'opération de mélange a échoué",
"voteImpossible": "Vote Impossible",
"notFoundVoteImpossible": "Retournez à l'onglet des sondages",
"returnToFormTable": "Retournez à l'onglet des sondages",
"voteImpossibleDescription": "Le sondage n'est plus ouvert au vote.",
"yes": "Oui",
"no": "Non",
Expand Down Expand Up @@ -289,6 +289,8 @@
"footerUnknown": "?",
"footerVersion": "version:",
"footerBuild": "build:",
"footerBuildTime": "en:"
"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."
}
}
14 changes: 10 additions & 4 deletions web/frontend/src/pages/ballot/Show.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { FC, useState } from 'react';
import { FC, useContext, useState } from 'react';
import { AuthContext } from 'index';
import { isVoter } from './../../utils/auth';
import { useTranslation } from 'react-i18next';
import { useParams } from 'react-router-dom';
import kyber from '@dedis/kyber';
Expand All @@ -17,7 +19,7 @@ import { useConfiguration } from 'components/utils/useConfiguration';
import { Status } from 'types/form';
import { ballotIsValid } from './components/ValidateAnswers';
import BallotDisplay from './components/BallotDisplay';
import FormClosed from './components/FormClosed';
import FormNotAvailable from './components/FormNotAvailable';
import Loading from 'pages/Loading';
import RedirectToModal from 'components/modal/RedirectToModal';
import { default as i18n } from 'i18next';
Expand All @@ -39,6 +41,7 @@ const Ballot: FC = () => {
const [castVoteLoading, setCastVoteLoading] = useState(false);

const navigate = useNavigate();
const { authorization, isLogged } = useContext(AuthContext);

const hexToBytes = (hex: string) => {
const bytes: number[] = [];
Expand Down Expand Up @@ -113,6 +116,8 @@ const Ballot: FC = () => {
event.currentTarget.disabled = true;
};

const userIsVoter = isVoter(formID, authorization, isLogged);

return (
<>
<RedirectToModal
Expand All @@ -127,7 +132,7 @@ const Ballot: FC = () => {
<Loading />
) : (
<>
{status === Status.Open && (
{status === Status.Open && userIsVoter && (
<div className="w-[60rem] font-sans px-4 pt-8 pb-4">
<div className="pb-2">
<h2 className="text-2xl font-bold leading-7 text-gray-900 sm:text-3xl sm:truncate">
Expand Down Expand Up @@ -165,7 +170,8 @@ const Ballot: FC = () => {
</div>
</div>
)}
{status !== Status.Open && <FormClosed />}
{!userIsVoter && <FormNotAvailable isVoter={false} />}
{status !== Status.Open && <FormNotAvailable isVoter={true} />}
</>
)}
</>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { useTranslation } from 'react-i18next';
import { Link } from 'react-router-dom';
import { ROUTE_FORM_INDEX } from 'Routes';

export default function FormClosed() {
export default function FormNotAvailable(props) {
const { t } = useTranslation();

return (
Expand All @@ -13,15 +13,17 @@ export default function FormClosed() {
<div className="sm:ml-6">
<div className=" sm:border-gray-200 sm:pl-6">
<h1 className="text-4xl font-extrabold text-gray-900 tracking-tight sm:text-5xl">
{t('voteImpossible')}
{props.isVoter ? t('voteImpossible') : t('voteNotVoter')}
</h1>
<p className="mt-1 text-base text-gray-500">{t('voteImpossibleDescription')}</p>
<p className="mt-1 text-base text-gray-500">
{props.isVoter ? t('voteImpossibleDescription') : t('voteNotVoterDescription')}
</p>
</div>
<div className="mt-10 flex space-x-3 sm:border-l sm:border-transparent sm:pl-6">
<Link
to={ROUTE_FORM_INDEX}
className="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
{t('notFoundVoteImpossible')}
{t('returnToFormTable')}
</Link>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { DocumentAddIcon } from '@heroicons/react/outline';
import { useTranslation } from 'react-i18next';
import { isManager } from './utils';
import { isManager } from './../../../../utils/auth';
import { AuthContext } from 'index';
import { useContext } from 'react';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { useContext } from 'react';
import { useTranslation } from 'react-i18next';
import { OngoingAction, Status } from 'types/form';
import ActionButton from './ActionButton';
import { isManager } from './utils';
import { isManager } from './../../../../utils/auth';

const CancelButton = ({ status, handleCancel, ongoingAction, formID }) => {
const { authorization, isLogged } = useContext(AuthContext);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { useContext } from 'react';
import { useTranslation } from 'react-i18next';
import { OngoingAction, Status } from 'types/form';
import ActionButton from './ActionButton';
import { isManager } from './utils';
import { isManager } from './../../../../utils/auth';

const CloseButton = ({ status, handleClose, ongoingAction, formID }) => {
const { authorization, isLogged } = useContext(AuthContext);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { useContext } from 'react';
import { useTranslation } from 'react-i18next';
import { OngoingAction, Status } from 'types/form';
import ActionButton from './ActionButton';
import { isManager } from './utils';
import { isManager } from './../../../../utils/auth';

const CombineButton = ({ status, handleCombine, ongoingAction, formID }) => {
const { t } = useTranslation();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { useContext } from 'react';
import { useTranslation } from 'react-i18next';
import { OngoingAction, Status } from 'types/form';
import ActionButton from './ActionButton';
import { isManager } from './utils';
import { isManager } from './../../../../utils/auth';

const DecryptButton = ({ status, handleDecrypt, ongoingAction, formID }) => {
const { authorization, isLogged } = useContext(AuthContext);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { TrashIcon } from '@heroicons/react/outline';
import { AuthContext } from 'index';
import { useContext } from 'react';
import { useTranslation } from 'react-i18next';
import { isManager } from './utils';
import { isManager } from './../../../../utils/auth';

const DeleteButton = ({ handleDelete, formID }) => {
const { t } = useTranslation();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { useContext } from 'react';
import { useTranslation } from 'react-i18next';
import { OngoingAction, Status } from 'types/form';
import ActionButton from './ActionButton';
import { isManager } from './utils';
import { isManager } from './../../../../utils/auth';

const InitializeButton = ({ status, handleInitialize, ongoingAction, formID }) => {
const { authorization, isLogged } = useContext(AuthContext);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { useContext } from 'react';
import { useTranslation } from 'react-i18next';
import { OngoingAction, Status } from 'types/form';
import ActionButton from './ActionButton';
import { isManager } from './utils';
import { isManager } from './../../../../utils/auth';

const OpenButton = ({ status, handleOpen, ongoingAction, formID }) => {
const { t } = useTranslation();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { useContext } from 'react';
import { useTranslation } from 'react-i18next';
import { OngoingAction, Status } from 'types/form';
import ActionButton from './ActionButton';
import { isManager } from './utils';
import { isManager } from './../../../../utils/auth';

const SetupButton = ({ status, handleSetup, ongoingAction, formID }) => {
const { t } = useTranslation();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { useContext } from 'react';
import { useTranslation } from 'react-i18next';
import { OngoingAction, Status } from 'types/form';
import ActionButton from './ActionButton';
import { isManager } from './utils';
import { isManager } from './../../../../utils/auth';

const ShuffleButton = ({ status, handleShuffle, ongoingAction, formID }) => {
const { t } = useTranslation();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ID } from './../../../../types/configuration';
import { ID } from './../types/configuration';

export function isManager(formID: ID, authorization: Map<String, String[]>, isLogged: boolean) {
return (
Expand All @@ -9,3 +9,11 @@ export function isManager(formID: ID, authorization: Map<String, String[]>, isLo
authorization.get(formID).includes('own') // must own the election
);
}

export function isVoter(formID: ID, authorization: Map<String, String[]>, isLogged: boolean) {
return (
isLogged && // must be logged in
authorization.has(formID) &&
authorization.get(formID).includes('vote') // must be able to vote in the election
);
}
49 changes: 49 additions & 0 deletions web/frontend/tests/ballot.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { expect, test } from '@playwright/test';
import { default as i18n } from 'i18next';
import { assertHasFooter, assertHasNavBar, initI18n, logIn, setUp } from './shared';
import { FORMID } from './mocks/shared';
import { SCIPER_ADMIN, SCIPER_OTHER_USER, SCIPER_USER, mockPersonalInfo } from './mocks/api';
import { mockFormsFormID } from './mocks/evoting';

initI18n();

test.beforeEach(async ({ page }) => {
await mockFormsFormID(page, 1);
await logIn(page, SCIPER_ADMIN);
await setUp(page, `/ballot/show/${FORMID}`);
});

test('Assert navigation bar is present', async ({ page }) => {
await assertHasNavBar(page);
});

test('Assert footer is present', async ({ page }) => {
await assertHasFooter(page);
});

test('Assert ballot form is correctly handled for anonymous users, non-voter users and voter users', async ({
page,
}) => {
const castVoteButton = await page.getByRole('button', { name: i18n.t('castVote') });
await test.step('Assert anonymous is redirected to login page', async () => {
await mockPersonalInfo(page);
await page.reload({ waitUntil: 'networkidle' });
await expect(page).toHaveURL('/login');
});
await test.step('Assert non-voter gets page that they are not allowed to vote', async () => {
await logIn(page, SCIPER_OTHER_USER);
await page.goto(`/ballot/show/${FORMID}`, { waitUntil: 'networkidle' });
await expect(page).toHaveURL(`/ballot/show/${FORMID}`);
await expect(castVoteButton).toBeHidden();
await expect(page.getByText(i18n.t('voteNotVoter'))).toBeVisible();
await expect(page.getByText(i18n.t('voteNotVoterDescription'))).toBeVisible();
});
await test.step('Assert voter gets ballot', async () => {
await logIn(page, SCIPER_USER);
await page.goto(`/ballot/show/${FORMID}`, { waitUntil: 'networkidle' });
await expect(page).toHaveURL(`/ballot/show/${FORMID}`);
await expect(castVoteButton).toBeVisible();
await expect(page.getByText(i18n.t('vote'))).toBeVisible();
await expect(page.getByText(i18n.t('voteExplanation'))).toBeVisible();
});
});
Loading