Skip to content

Commit

Permalink
fix: do not show 'Add voters' button to non-owner
Browse files Browse the repository at this point in the history
  • Loading branch information
PascalinDe committed Jan 25, 2024
1 parent 6498f8b commit e4746f0
Show file tree
Hide file tree
Showing 6 changed files with 101 additions and 35 deletions.
Original file line number Diff line number Diff line change
@@ -1,18 +1,16 @@
import { DocumentAddIcon } from '@heroicons/react/outline';
import { useTranslation } from 'react-i18next';
import { isManager } from './utils';
import { AuthContext } from 'index';
import { useContext } from 'react';
import { useTranslation } from 'react-i18next';

const SUBJECT_ELECTION = 'election';
const ACTION_CREATE = 'create';

const AddVotersButton = ({ handleAddVoters }) => {
const authCtx = useContext(AuthContext);
const AddVotersButton = ({ handleAddVoters, formID }) => {
const { t } = useTranslation();
const { authorization, isLogged } = useContext(AuthContext);

return (
authCtx.isAllowed(SUBJECT_ELECTION, ACTION_CREATE) && (
<button onClick={handleAddVoters}>
isManager(formID, authorization, isLogged) && (
<button data-testid="addVotersButton" onClick={handleAddVoters}>
<div className="whitespace-nowrap inline-flex items-center justify-center px-4 py-1 mr-2 border border-gray-300 text-sm rounded-full font-medium text-gray-700 hover:text-red-500">
<DocumentAddIcon className="-ml-1 mr-2 h-5 w-5" aria-hidden="true" />
{t('addVoters')}
Expand Down
11 changes: 11 additions & 0 deletions web/frontend/src/pages/form/components/ActionButtons/utils.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { ID } from './../../../../types/configuration';

export function isManager(formID: ID, authorization: Map<String, String[]>, isLogged: boolean) {
return (
isLogged && // must be logged in
authorization.has('election') &&
authorization.get('election').includes('create') && // must be able to create elections
authorization.has(formID) &&
authorization.get(formID).includes('own') // must own the election
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -504,7 +504,7 @@ const useChangeAction = (
<>
<OpenButton status={status} handleOpen={handleOpen} ongoingAction={ongoingAction} />
<DeleteButton handleDelete={handleDelete} />
<AddVotersButton handleAddVoters={handleAddVoters} />
<AddVotersButton handleAddVoters={handleAddVoters} formID={formID} />
</>
);
case Status.Open:
Expand All @@ -518,7 +518,7 @@ const useChangeAction = (
/>
<VoteButton status={status} formID={formID} />
<DeleteButton handleDelete={handleDelete} />
<AddVotersButton handleAddVoters={handleAddVoters} />
<AddVotersButton handleAddVoters={handleAddVoters} formID={formID} />
</>
);
case Status.Closed:
Expand Down
61 changes: 55 additions & 6 deletions web/frontend/tests/forms.spec.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,37 @@
import { expect, test } from '@playwright/test';
import { assertHasFooter, assertHasNavBar, initI18n, setUp } from './shared';
import { mockPersonalInfo, mockProxies } from './mocks/api';
import { assertHasFooter, assertHasNavBar, initI18n, logIn, setUp } from './shared';
import {
SCIPER_ADMIN,
SCIPER_OTHER_ADMIN,
SCIPER_USER,
mockPersonalInfo,
mockProxies,
} from './mocks/api';
import { FORMID, mockDKGActors, mockFormsFormID } from './mocks/evoting';

initI18n();

// main elements

test.beforeEach(async ({ page }) => {
// mock empty list per default
await mockFormsFormID(page, 0);
async function setUpMocks(
page: page,
formStatus: number,
dkgActorsStatus: number,
initialized?: boolean
) {
// the nodes must have been initialized if they changed state
initialized = initialized || dkgActorsStatus > 0;
await mockFormsFormID(page, formStatus);
for (const i of [0, 1, 2, 3]) {
await mockProxies(page, i);
}
await mockDKGActors(page, 0, true);
await mockDKGActors(page, dkgActorsStatus, initialized);
await mockPersonalInfo(page);
}

test.beforeEach(async ({ page }) => {
// mock empty list per default
setUpMocks(page, 0, 0, false);
await setUp(page, `/forms/${FORMID}`);
});

Expand All @@ -25,3 +42,35 @@ test('Assert navigation bar is present', async ({ page }) => {
test('Assert footer is present', async ({ page }) => {
await assertHasFooter(page);
});

async function assertIsOnlyVisibleToOwner(page: page, locator: locator) {
await test.step('Assert is hidden to unauthenticated user', async () => {
await expect(locator).toBeHidden();
});
await test.step('Assert is hidden to authenticated non-admin user', async () => {
await logIn(page, SCIPER_USER);
await expect(locator).toBeHidden();
});
await test.step('Assert is hidden to non-owner admin', async () => {
await logIn(page, SCIPER_ADMIN);
await expect(locator).toBeHidden();
});
await test.step('Assert is visible to owner admin', async () => {
await logIn(page, SCIPER_OTHER_ADMIN);
await expect(locator).toBeVisible();
});
}

async function assertIsOnlyVisibleToOwnerStates(page: page, locator: locator, states: Array) {
for (const i of states) {
await test.step(`Assert is only visible to owner in state ${i}`, async () => {
await setUpMocks(page, i, 6);
await page.reload({ waitUntil: 'networkidle' });
await assertIsOnlyVisibleToOwner(page, locator);
});
}
}

test('Assert "Add voters" button is only visible to owner', async ({ page }) => {
await assertIsOnlyVisibleToOwnerStates(page, page.getByTestId('addVotersButton'), [0, 1]);
});
2 changes: 1 addition & 1 deletion web/frontend/tests/json/api/personal_info/123456.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,6 @@
"fdf8bfb702e8883e330a2b303b24212b6fc16df5a53a097998b77ba74632dc72": ["own"],
"ed26713245824d44ee46ec90507ef521962f2313706934cdfe76ff1823738109": ["vote"],
"9f50ad723805a6419ba1a9f83dd0aa582f3e13b94f14727cd0c8c01744e0dba2": ["vote", "own"],
"b63bcb854121051f2d8cff04bf0ac9b524b534b704509a16a423448bde3321b4": ["own"]
"b63bcb854121051f2d8cff04bf0ac9b524b534b704509a16a423448bde3321b4": ["vote"]
}
}
44 changes: 26 additions & 18 deletions web/frontend/tests/mocks/evoting.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,27 +62,35 @@ export async function mockFormsFormID(page: page, formStatus: number) {
});
}

export async function mockDKGActors(page: page, formStatus: number, initialized?: boolean) {
// the nodes must have been initialized if the form changed state
initialized = initialized || formStatus > 0;
export async function mockDKGActors(page: page, dkgActorsStatus: number, initialized: boolean) {
for (const worker of [Worker0, Worker1, Worker2, Worker3]) {
await page.route(`${worker.Proxy}/evoting/services/dkg/actors/${FORMID}`, async (route) => {
let dkgActorsFile = '';
switch (formStatus) {
case 0:
dkgActorsFile = initialized ? 'initialized.json' : 'uninitialized.json';
break;
case 1:
dkgActorsFile = 'setup.json';
break;
case 6:
dkgActorsFile = 'certified.json';
break;
if (route.request().method() === 'PUT') {
await route.fulfill({
status: 200,
headers: {
'Access-Control-Allow-Headers': '*',
'Access-Control-Allow-Origin': '*',
},
});
} else {
let dkgActorsFile = '';
switch (dkgActorsStatus) {
case 0:
dkgActorsFile = initialized ? 'initialized.json' : 'uninitialized.json';
break;
// case 1:
// dkgActorsFile = 'setup.json';
// break;
case 6:
dkgActorsFile = worker === Worker0 ? 'setup.json' : 'certified.json'; // one node is set up, the remaining nodes are certified
break;
}
await route.fulfill({
status: initialized ? 200 : 400,
path: `./tests/json/evoting/dkgActors/${dkgActorsFile}`,
});
}
await route.fulfill({
status: initialized ? 200 : 400,
path: `./tests/json/evoting/dkgActors/${dkgActorsFile}`,
});
});
}
}

0 comments on commit e4746f0

Please sign in to comment.