-
Notifications
You must be signed in to change notification settings - Fork 8
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
Readjustement of the introduction image and first version of casbin authorization mechanism #201
Conversation
Pull Request Test Coverage Report for Build 3517567483Warning: This coverage report may be inaccurate.This pull request's base commit is no longer the HEAD commit of its target branch. This means it includes changes from outside the original pull request, including, potentially, unrelated coverage changes.
Details
💛 - Coveralls |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
web/backend/src/Server.ts
Outdated
// function isAuthorized(roles: string[], req: express.Request): boolean { | ||
// return true; | ||
// if (!req.session || !req.session.userid) { | ||
// return false; | ||
// } | ||
|
||
const { role } = req.session; | ||
// const { role } = req.session; | ||
|
||
return roles.includes(role as string); | ||
} | ||
// return roles.includes(role as string); | ||
// } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
feel free to delete code that is not relevant anymore. Commented out code is generally frowned upon 😄
web/backend/src/Server.ts
Outdated
usersDB.getRange(opts).map(({ key, value }) => ({ id: '0', sciper: key, role: value })) | ||
); | ||
res.json(users); | ||
app.get('/api/user_rights', async (req, res) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
are you sure that express supports async handlers natively?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's my mistake, I forgot to delete it. I was just trying some things while testing.
web/backend/src/Server.ts
Outdated
); | ||
res.json(users); | ||
app.get('/api/user_rights', async (req, res) => { | ||
e.then((enforcer) => |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if you have an async function, you don't need to do .then
and .catch
, you can just use await
where you would use a then, and try/catch
instead of .catch
.
see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await
web/backend/src/Server.ts
Outdated
.catch((erreur) => console.log('error', erreur)) | ||
).catch((erreur1) => console.log('error', erreur1)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
avoid french words in the codebase 😁
also, you can reuse the same variable name, as it's scoped just to that function.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the view, it is fixed.
- Updates the initialisation of Casbin so that we start only when the enforcer is ready - Uses enforceSync, which makes the checking mechanism was easier - Adds a utility function isAuthorized
Uses enforceSync to check authorizations
…voting into d-voting_frontend_ghita
Promise.all([enforcerLoading]) | ||
.then((res) => { | ||
[enf] = res; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you need a Promise.all
if you're looking at just one promise resolving?
Why can't you do the following ?
Promise.all([enforcerLoading]) | |
.then((res) => { | |
[enf] = res; | |
enforcerLoading | |
.then((enf) => { |
I may be missing something here, so do let me know if I'm wrong 😁
web/backend/src/Server.ts
Outdated
let enf: Enforcer; | ||
|
||
const enforcerLoading = newEnforcer('model.conf', 'policy.csv'); | ||
const e = newEnforcer('model.conf', 'policy.csv'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this looks unused
@@ -26,6 +26,27 @@ const app = express(); | |||
|
|||
app.use(morgan('tiny')); | |||
|
|||
let enf: Enforcer; | |||
|
|||
const enforcerLoading = newEnforcer('model.conf', 'policy.csv'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
do you need this to be in the global scope, or could you put it (along with the promise resolution below) in an initialization function ?
web/backend/src/Server.ts
Outdated
} | ||
|
||
next(); | ||
e.then((enforcer) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is there are a reason not to use isAuthorized
here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just forget it. It is fixed !
web/backend/src/Server.ts
Outdated
e.then((enforcer) => { | ||
enforcer | ||
.enforce(req.session.userid, 'election', 'create') | ||
.then((isOk) => { | ||
if (isOk) { | ||
next(); | ||
} else { | ||
res.status(400).send('Unauthorized - only admins and operators allowed'); | ||
} | ||
}) | ||
.catch((error) => console.log('error', error)); | ||
}).catch((error1) => console.log('error', error1)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As per our discussion today, this should work:
e.then((enforcer) => { | |
enforcer | |
.enforce(req.session.userid, 'election', 'create') | |
.then((isOk) => { | |
if (isOk) { | |
next(); | |
} else { | |
res.status(400).send('Unauthorized - only admins and operators allowed'); | |
} | |
}) | |
.catch((error) => console.log('error', error)); | |
}).catch((error1) => console.log('error', error1)); | |
e.then((enforcer) => { | |
return enforcer | |
.enforce(req.session.userid, 'election', 'create') | |
.then((isOk) => { | |
if (isOk) { | |
next(); | |
} else { | |
res.status(400).send('Unauthorized - only admins and operators allowed'); | |
} | |
}) | |
}).catch((error) => console.log('error', error)); |
No need to do a double-catch 😁
And since you're directly returning a value, you could also do this (notice the absence of {}
and return
):
e.then((enforcer) => { | |
enforcer | |
.enforce(req.session.userid, 'election', 'create') | |
.then((isOk) => { | |
if (isOk) { | |
next(); | |
} else { | |
res.status(400).send('Unauthorized - only admins and operators allowed'); | |
} | |
}) | |
.catch((error) => console.log('error', error)); | |
}).catch((error1) => console.log('error', error1)); | |
e.then((enforcer) => | |
enforcer | |
.enforce(req.session.userid, 'election', 'create') | |
.then((isOk) => { | |
if (isOk) { | |
next(); | |
} else { | |
res.status(400).send('Unauthorized - only admins and operators allowed'); | |
} | |
})).catch((error) => console.log('error', error)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I modify that using the proposition of Noemien.
web/backend/src/Server.ts
Outdated
// const port = process.env.PORT || 5000; | ||
// app.listen(port); | ||
|
||
console.log(`App is listening on port ${port}`); | ||
// console.log(`App is listening on port ${port}`); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
feel free to delete code, no need to comment it out 😁
web/frontend/src/layout/NavBar.tsx
Outdated
@@ -177,7 +181,7 @@ const LeftSideNavBar = ({ authCtx, t }) => ( | |||
className={'text-black text-lg hover:text-indigo-700'}> | |||
{t('navBarStatus')} | |||
</NavLink> | |||
{authCtx.role === 'admin' && authCtx.isLogged && ( | |||
{authCtx.isLogged && authCtx.authorization.get('roles').indexOf('list') > -1 && ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you could define and use the constants on the front-end as well.
web/frontend/src/layout/NavBar.tsx
Outdated
authCtx.authorization.has('election') && | ||
authCtx.authorization.get('election').indexOf('create') > -1 && ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
and here
web/frontend/src/layout/NavBar.tsx
Outdated
authCtx.authorization.has('election') && | ||
authCtx.authorization.get('election').indexOf('create') > -1 && ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
and here
web/frontend/src/layout/NavBar.tsx
Outdated
@@ -67,7 +67,7 @@ const MobileMenu = ({ authCtx, handleLogout, fctx, t }) => ( | |||
</Popover.Button> | |||
</NavLink> | |||
} | |||
{authCtx.isLogged && (authCtx.role === 'admin' || authCtx.role === 'operator') && ( | |||
{authCtx.isLogged && authCtx.authorization.get('roles').indexOf('add') > -1 && ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
and here
web/backend/src/Server.ts
Outdated
for (let i = 0; i < list.length; i += 1) { | ||
for (let j = 1; j < list[0].length; j += +2) { | ||
console.log(list[i][j]); | ||
if (m.has(list[i][j])) { | ||
m.get(list[i][j])?.push(list[i][j + 1]); | ||
} else { | ||
m.set(list[i][j], [list[i][j + 1]]); | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this kind of complex operations probably deserves its own function, that you can write a short test for.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
outside the scope of this PR: would it make sense to memoize this for performance reasons?
web/backend/src/Server.ts
Outdated
for (let j = 1; j < list[0].length; j += +2) { | ||
console.log(list[i][j]); | ||
if (m.has(list[i][j])) { | ||
m.get(list[i][j])?.push(list[i][j + 1]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
do you really need the ?
if you already have called m.has
?
conversely, why not just do m.get
and do the push if the result is not null ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Even if I check m.has
I still have the error (Object is possibly 'undefined') This why I used ?
. However it can be a good way to test the result of m.get
. I was just wondering how it is better than using ?
web/frontend/src/layout/NavBar.tsx
Outdated
{authCtx.isLogged && | ||
authCtx.authorization.has('election') && | ||
authCtx.authorization.get('election').indexOf('create') > -1 && ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was thinking that here (and elsewhere) you're mixing up the details of the implementation (authorization is a map, permissions an array) and the actual concern (verifying access).
Could you add a helper function that will deal with these details ?
So that here (and elsewhere) you can just call something like hasAuthorization(authCtx, ROLES, LIST)
?
This will make it easier to refactor (if needed, in the future) the actual implementation details.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for this review. It is quite a good idea. I implement it !
web/backend/src/Server.ts
Outdated
@@ -154,49 +185,58 @@ app.post('/api/logout', (req, res) => { | |||
// As the user is logged on the app via this express but must also be logged in | |||
// the react. This endpoint serves to send to the client (actually to react) | |||
// the information of the current user. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The comment is for the statement bellow, not the setMapAuthorization
function, which must also have its own comment describing what it does.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This function is hard to understand without any explanation. You should at least describe what contains list: string[][])
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added some comments and while doing it I noticed that the iteration on j isn't necessary.
web/backend/src/Server.ts
Outdated
function setMapAuthorization(list: string[][]) { | ||
const m = new Map<String, Array<String>>(); | ||
for (let i = 0; i < list.length; i += 1) { | ||
for (let j = 1; j < list[0].length; j += +2) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you should explain (1) what is contained in list[0]
, and (2) why you are doing += 2
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I delete that.
web/backend/src/Server.ts
Outdated
const m = new Map<String, Array<String>>(); | ||
for (let i = 0; i < list.length; i += 1) { | ||
for (let j = 1; j < list[0].length; j += +2) { | ||
console.log(list[i][j]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you probably used it for debugging. To be removed.
web/backend/src/Server.ts
Outdated
app.get('/api/personal_info', (req, res) => { | ||
let m = new Map<String, Array<String>>(); | ||
enf.getFilteredPolicy(0, String(req.session.userid)).then((list) => { | ||
m = setMapAuthorization(list); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the user is not logged you can avoid calling setMapAuthorization
and return an empty map.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed !
web/backend/src/Server.ts
Outdated
|
||
const MemoryStore = createMemoryStore(session); | ||
|
||
const ROLES = 'roles'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is way too generic. You should prefix the variable name to indicate how the variable is used:
SUBJECT_ROLES = 'roles';
SUBJECT_PROXIES = 'proxies';
...
ACTION_LIST = 'list';
ACTION_REMOVE = 'remove';
...
web/frontend/src/layout/NavBar.tsx
Outdated
@@ -24,6 +24,17 @@ import { Popover, Transition } from '@headlessui/react'; | |||
import { LoginIcon, LogoutIcon, MenuIcon, XIcon } from '@heroicons/react/outline'; | |||
import { PlusIcon } from '@heroicons/react/solid'; | |||
|
|||
const ELECTION = 'election'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You should prefix the variable name:
SUBJECT_ELECTION = ...
...
ACTION_CREATE = ...
web/frontend/src/layout/NavBar.tsx
Outdated
|
||
function hasAuthorization(authCtx, subject: string, action: string): boolean { | ||
return ( | ||
authCtx.authorization.has(subject) && authCtx.authorization.get(subject).indexOf(action) > -1 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
authCtx.authorization.has(subject) && authCtx.authorization.get(subject).indexOf(action) > -1 | |
authCtx.authorization.has(subject) && authCtx.authorization.get(subject).indexOf(action) !== -1 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not absolutely perfect, but sooooo much better, great job ! 👍
I've some minor remarks, but at this point it looks nearly mergeable to me :)
// list[0] contains the policies so list[i][0] is the sciper | ||
// list[i][1] is the object and list[i][2] is the action | ||
function setMapAuthorization(list: string[][]) { | ||
const m = new Map<String, Array<String>>(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
do you want to use String
the object or just string
the plain data type?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When searching which one use, I found that it is better to avoid bugs to use string
web/backend/src/Server.ts
Outdated
// an object to the action authorized | ||
// list[0] contains the policies so list[i][0] is the sciper | ||
// list[i][1] is the object and list[i][2] is the action | ||
function setMapAuthorization(list: string[][]) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
in typescript it's good to explicitly indicate the return type.
function setMapAuthorization(list: string[][]) { | |
function setMapAuthorization(list: string[][]): Map<string, Array<string>> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done !
@@ -207,7 +215,7 @@ app.get('/api/personal_info', (req, res) => { | |||
firstname: req.session.firstname, | |||
role: req.session.role, | |||
islogged: true, | |||
authorization: Object.fromEntries(m), | |||
authorization: Object.fromEntries(setMapAuthorization(list)), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
much cleaner :)
web/backend/src/Server.ts
Outdated
console.log(m); | ||
return m; | ||
} | ||
|
||
// As the user is logged on the app via this express but must also be logged in | ||
// the react. This endpoint serves to send to the client (actually to react) | ||
// the information of the current user. | ||
app.get('/api/personal_info', (req, res) => { | ||
const m = new Map<String, Array<String>>(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
do you still need this variable?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I decide to delete it
web/backend/src/Server.ts
Outdated
function setMapAuthorization(list: string[][]) { | ||
const m = new Map<String, Array<String>>(); | ||
for (let i = 0; i < list.length; i += 1) { | ||
if (m.has(list[i][1])) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The comment is already a great improvement. 👍
Now, the next step is self-explanatory code.. ;)
You could do:
const [subject, action] = list[i][1], list[i][2];
if (m.has(subject)) {
...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the review ! Done
web/backend/src/Server.ts
Outdated
const m = new Map<String, Array<String>>(); | ||
for (let i = 0; i < list.length; i += 1) { | ||
if (m.has(list[i][1])) { | ||
m.get(list[i][1])?.push(list[i][2]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure I understand the ?
. Is it to prevent adding multiple times the same action ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's necessary, however I check that m.has(list[i][1]
, to make sure that m.get(list[i][1]
is not undefined.
web/backend/src/Server.ts
Outdated
firstname: '', | ||
role: '', | ||
islogged: false, | ||
authorization: Object.fromEntries(m), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you can just use authorization: new Map<String, Array<String>>(),
or even just authorization: {},
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed !
Kudos, SonarCloud Quality Gate passed! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good to go 🚀
No description provided.