Skip to content

Commit 0f99cbf

Browse files
authored
Merge pull request #201 from dedis/d-voting_frontend_ghita
Readjustement of the introduction image and first version of casbin authorization mechanism
2 parents 9891200 + 2478fd1 commit 0f99cbf

File tree

9 files changed

+3033
-85
lines changed

9 files changed

+3033
-85
lines changed

web/backend/model.conf

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# This file define the model of the authorization mechanism
2+
[request_definition]
3+
r = sub, obj, act
4+
5+
[policy_definition]
6+
p = sub, obj, act
7+
8+
[policy_effect]
9+
e = some(where (p.eft == allow))
10+
11+
[matchers]
12+
m = r.sub == p.sub && r.obj == p.obj && r.act == p.act

web/backend/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
"dependencies": {
2020
"@dedis/kyber": "^3.4.4",
2121
"axios": "^0.24.0",
22+
"casbin": "^5.19.1",
2223
"cookie-parser": "^1.4.6",
2324
"crypto": "^1.0.1",
2425
"express": "^4.17.2",

web/backend/policy.csv

+63
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
p, 330383, roles, list
2+
p, 330383, election, create
3+
p, 330383, roles, remove
4+
p, 330383, roles, add
5+
p, 330383, proxies, post
6+
p, 330383, proxies, put
7+
p, 330383, proxies, delete
8+
9+
p, 228271, roles, list
10+
p, 228271, election, create
11+
p, 228271, roles, remove
12+
p, 228271, roles, add
13+
p, 228271, proxies, post
14+
p, 228271, proxies, put
15+
p, 228271, proxies, delete
16+
17+
p, 330361, roles, list
18+
p, 330361, election, create
19+
p, 330361, roles, remove
20+
p, 330361, roles, add
21+
p, 330361, proxies, post
22+
p, 330361, proxies, put
23+
p, 330361, proxies, delete
24+
25+
p, 175129, roles, list
26+
p, 175129, election, create
27+
p, 175129, roles, remove
28+
p, 175129, roles, add
29+
p, 175129, proxies, post
30+
p, 175129, proxies, put
31+
p, 175129, proxies, delete
32+
33+
p, 324610, roles, list
34+
p, 324610, election, create
35+
p, 324610, roles, remove
36+
p, 324610, roles, add
37+
p, 324610, proxies, post
38+
p, 324610, proxies, put
39+
p, 324610, proxies, delete
40+
41+
p, 330382, roles, list
42+
p, 330382, election, create
43+
p, 330382, roles, remove
44+
p, 330382, roles, add
45+
p, 330382, proxies, post
46+
p, 330382, proxies, put
47+
p, 330382, proxies, delete
48+
49+
p, 315822, roles, list
50+
p, 315822, election, create
51+
p, 315822, roles, remove
52+
p, 315822, roles, add
53+
p, 315822, proxies, post
54+
p, 315822, proxies, put
55+
p, 315822, proxies, delete
56+
57+
p, 321016, roles, list
58+
p, 321016, election, create
59+
p, 321016, roles, remove
60+
p, 321016, roles, add
61+
p, 321016, proxies, post
62+
p, 321016, proxies, put
63+
p, 321016, proxies, delete

web/backend/src/Server.ts

+107-77
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,20 @@ import crypto from 'crypto';
88
import lmdb, { RangeOptions } from 'lmdb';
99
import xss from 'xss';
1010
import createMemoryStore from 'memorystore';
11+
import { Enforcer, newEnforcer } from 'casbin';
1112

1213
const MemoryStore = createMemoryStore(session);
13-
14+
const SUBJECT_ROLES = 'roles';
15+
const SUBJECT_PROXIES = 'proxies';
16+
const SUBJECT_ELECTION = 'election';
17+
18+
const ACTION_LIST = 'list';
19+
const ACTION_ACTION_REMOVE = 'remove';
20+
const ACTION_ADD = 'add';
21+
const ACTION_PUT = 'put';
22+
const ACTION_POST = 'post';
23+
const ACTION_DELETE = 'delete';
24+
const ACTION_CREATE = 'create';
1425
// store is used to store the session
1526
const store = new MemoryStore({
1627
checkPeriod: 86400000, // prune expired entries every 24h
@@ -25,6 +36,26 @@ const app = express();
2536

2637
app.use(morgan('tiny'));
2738

39+
let enf: Enforcer;
40+
41+
const enforcerLoading = newEnforcer('model.conf', 'policy.csv');
42+
const port = process.env.PORT || 5000;
43+
44+
Promise.all([enforcerLoading])
45+
.then((res) => {
46+
[enf] = res;
47+
console.log(`🛡 Casbin loaded`);
48+
app.listen(port);
49+
console.log(`🚀 App is listening on port ${port}`);
50+
})
51+
.catch((err) => {
52+
console.error('❌ failed to start:', err);
53+
});
54+
55+
function isAuthorized(sciper: number | undefined, subject: string, action: string): boolean {
56+
return enf.enforceSync(sciper, subject, action);
57+
}
58+
2859
declare module 'express-session' {
2960
export interface SessionData {
3061
userid: number;
@@ -71,6 +102,7 @@ app.use(express.urlencoded({ extended: true }));
71102

72103
// This endpoint allows anyone to get a "default" proxy. Clients can still use
73104
// the proxy of their choice thought.
105+
74106
app.get('/api/config/proxy', (req, res) => {
75107
res.status(200).send(process.env.DELA_NODE_URL);
76108
});
@@ -151,52 +183,64 @@ app.post('/api/logout', (req, res) => {
151183
});
152184
});
153185

186+
// This function helps us convert the double list of the authorization
187+
// returned by the casbin function getFilteredPolicy to a map that link
188+
// an object to the action authorized
189+
// list[0] contains the policies so list[i][0] is the sciper
190+
// list[i][1] is the subject and list[i][2] is the action
191+
function setMapAuthorization(list: string[][]): Map<String, Array<String>> {
192+
const m = new Map<String, Array<String>>();
193+
for (let i = 0; i < list.length; i += 1) {
194+
const subject = list[i][1];
195+
const action = list[i][2];
196+
if (m.has(subject)) {
197+
m.get(subject)?.push(action);
198+
} else {
199+
m.set(subject, [action]);
200+
}
201+
}
202+
console.log(m);
203+
return m;
204+
}
205+
154206
// As the user is logged on the app via this express but must also be logged in
155207
// the react. This endpoint serves to send to the client (actually to react)
156208
// the information of the current user.
157209
app.get('/api/personal_info', (req, res) => {
158-
res.set('Access-Control-Allow-Origin', '*');
159-
if (req.session.userid) {
160-
res.json({
161-
sciper: req.session.userid,
162-
lastname: req.session.lastname,
163-
firstname: req.session.firstname,
164-
role: req.session.role,
165-
islogged: true,
166-
});
167-
} else {
168-
res.json({
169-
sciper: 0,
170-
lastname: '',
171-
firstname: '',
172-
role: '',
173-
islogged: false,
174-
});
175-
}
210+
enf.getFilteredPolicy(0, String(req.session.userid)).then((list) => {
211+
res.set('Access-Control-Allow-Origin', '*');
212+
if (req.session.userid) {
213+
res.json({
214+
sciper: req.session.userid,
215+
lastname: req.session.lastname,
216+
firstname: req.session.firstname,
217+
role: req.session.role,
218+
islogged: true,
219+
authorization: Object.fromEntries(setMapAuthorization(list)),
220+
});
221+
} else {
222+
res.json({
223+
sciper: 0,
224+
lastname: '',
225+
firstname: '',
226+
role: '',
227+
islogged: false,
228+
authorization: {},
229+
});
230+
}
231+
});
176232
});
177233

178-
function isAuthorized(roles: string[], req: express.Request): boolean {
179-
if (!req.session || !req.session.userid) {
180-
return false;
181-
}
182-
183-
const { role } = req.session;
184-
185-
return roles.includes(role as string);
186-
}
187-
188234
// ---
189235
// Users role
190236
// ---
191-
192237
// This call allow a user that is admin to get the list of the people that have
193238
// a special role (not a voter).
194239
app.get('/api/user_rights', (req, res) => {
195-
if (!isAuthorized(['admin'], req)) {
240+
if (!isAuthorized(req.session.userid, SUBJECT_ROLES, ACTION_LIST)) {
196241
res.status(400).send('Unauthorized - only admins allowed');
197242
return;
198243
}
199-
200244
const opts: RangeOptions = {};
201245
const users = Array.from(
202246
usersDB.getRange(opts).map(({ key, value }) => ({ id: '0', sciper: key, role: value }))
@@ -206,13 +250,11 @@ app.get('/api/user_rights', (req, res) => {
206250

207251
// This call (only for admins) allow an admin to add a role to a voter.
208252
app.post('/api/add_role', (req, res) => {
209-
if (!isAuthorized(['admin'], req)) {
253+
if (!isAuthorized(req.session.userid, SUBJECT_ROLES, ACTION_ADD)) {
210254
res.status(400).send('Unauthorized - only admins allowed');
211255
return;
212256
}
213257

214-
// {sciper: xxx, role: xxx}
215-
216258
const { sciper } = req.body;
217259
const { role } = req.body;
218260

@@ -224,13 +266,12 @@ app.post('/api/add_role', (req, res) => {
224266

225267
// This call (only for admins) allow an admin to remove a role to a user.
226268
app.post('/api/remove_role', (req, res) => {
227-
if (!isAuthorized(['admin'], req)) {
269+
if (!isAuthorized(req.session.userid, SUBJECT_ROLES, ACTION_ACTION_REMOVE)) {
228270
res.status(400).send('Unauthorized - only admins allowed');
229271
return;
230272
}
231273

232274
const { sciper } = req.body;
233-
234275
usersDB
235276
.remove(sciper)
236277
.then(() => {
@@ -256,40 +297,12 @@ app.post('/api/remove_role', (req, res) => {
256297
// ---
257298
// Proxies
258299
// ---
259-
260300
const proxiesDB = lmdb.open<string, string>({ path: `${process.env.DB_PATH}proxies` });
261-
262-
app.get('/api/proxies', (req, res) => {
263-
const output = new Map<string, string>();
264-
proxiesDB.getRange({}).forEach((entry) => {
265-
output.set(entry.key, entry.value);
266-
});
267-
268-
res.status(200).json({ Proxies: Object.fromEntries(output) });
269-
});
270-
271-
app.get('/api/proxies/:nodeAddr', (req, res) => {
272-
const { nodeAddr } = req.params;
273-
274-
const proxy = proxiesDB.get(decodeURIComponent(nodeAddr));
275-
276-
if (proxy === undefined) {
277-
res.status(404).send('not found');
278-
return;
279-
}
280-
281-
res.status(200).json({
282-
NodeAddr: nodeAddr,
283-
Proxy: proxy,
284-
});
285-
});
286-
287301
app.post('/api/proxies', (req, res) => {
288-
if (!isAuthorized(['admin', 'operator'], req)) {
302+
if (!isAuthorized(req.session.userid, SUBJECT_PROXIES, ACTION_POST)) {
289303
res.status(400).send('Unauthorized - only admins and operators allowed');
290304
return;
291305
}
292-
293306
try {
294307
const bodydata = req.body;
295308
proxiesDB.put(bodydata.NodeAddr, bodydata.Proxy);
@@ -301,7 +314,7 @@ app.post('/api/proxies', (req, res) => {
301314
});
302315

303316
app.put('/api/proxies/:nodeAddr', (req, res) => {
304-
if (!isAuthorized(['admin', 'operator'], req)) {
317+
if (!isAuthorized(req.session.userid, SUBJECT_PROXIES, ACTION_PUT)) {
305318
res.status(400).send('Unauthorized - only admins and operators allowed');
306319
return;
307320
}
@@ -316,7 +329,6 @@ app.put('/api/proxies/:nodeAddr', (req, res) => {
316329
res.status(404).send('not found');
317330
return;
318331
}
319-
320332
try {
321333
const bodydata = req.body;
322334
if (bodydata.Proxy === undefined) {
@@ -333,7 +345,7 @@ app.put('/api/proxies/:nodeAddr', (req, res) => {
333345
});
334346

335347
app.delete('/api/proxies/:nodeAddr', (req, res) => {
336-
if (!isAuthorized(['admin', 'operator'], req)) {
348+
if (!isAuthorized(req.session.userid, SUBJECT_PROXIES, ACTION_DELETE)) {
337349
res.status(400).send('Unauthorized - only admins and operators allowed');
338350
return;
339351
}
@@ -358,6 +370,31 @@ app.delete('/api/proxies/:nodeAddr', (req, res) => {
358370
}
359371
});
360372

373+
app.get('/api/proxies', (req, res) => {
374+
const output = new Map<string, string>();
375+
proxiesDB.getRange({}).forEach((entry) => {
376+
output.set(entry.key, entry.value);
377+
});
378+
379+
res.status(200).json({ Proxies: Object.fromEntries(output) });
380+
});
381+
382+
app.get('/api/proxies/:nodeAddr', (req, res) => {
383+
const { nodeAddr } = req.params;
384+
385+
const proxy = proxiesDB.get(decodeURIComponent(nodeAddr));
386+
387+
if (proxy === undefined) {
388+
res.status(404).send('not found');
389+
return;
390+
}
391+
392+
res.status(200).json({
393+
NodeAddr: nodeAddr,
394+
Proxy: proxy,
395+
});
396+
});
397+
361398
// ---
362399
// end of proxies
363400
// ---
@@ -461,12 +498,10 @@ function sendToDela(dataStr: string, req: express.Request, res: express.Response
461498

462499
// Secure /api/evoting to admins and operators
463500
app.use('/api/evoting/*', (req, res, next) => {
464-
if (!isAuthorized(['admin', 'operator'], req)) {
465-
console.log('role is:', req.session.role);
501+
if (!isAuthorized(req.session.userid, SUBJECT_ELECTION, ACTION_CREATE)) {
466502
res.status(400).send('Unauthorized - only admins and operators allowed');
467503
return;
468504
}
469-
470505
next();
471506
});
472507

@@ -557,8 +592,3 @@ app.get('*', (req, res) => {
557592
const url = new URL(req.url, `http://${req.headers.host}`);
558593
res.status(404).send(`not found ${xss(url.toString())}`);
559594
});
560-
561-
const port = process.env.PORT || 5000;
562-
app.listen(port);
563-
564-
console.log(`App is listening on port ${port}`);

0 commit comments

Comments
 (0)