Skip to content
This repository has been archived by the owner on Jan 6, 2025. It is now read-only.

I24 permission protection + metadata on course-evaluation page #113

Merged
merged 17 commits into from
Mar 2, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
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
12 changes: 2 additions & 10 deletions client/components/Coordinator/Evaluation/EvaluationList.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,15 +52,13 @@ const EvaluationList = () => {
useEffect(() => {
// 1. Find all CourseEvaluations where the createdBy key matches the logged in user
services['course-evaluation'].find();
services['users'].find();
setLoading(false);
}, []);

const courseEvaluations = useSelector((state) => state['course-evaluation'])
?.queryResult?.data;
const users = useSelector((state) => state['users'])?.queryResult?.data;

if (loading || !users || !courseEvaluations) {
if (loading || !courseEvaluations) {
return (
<Card>
<CardBody>Loading...</CardBody>
Expand All @@ -76,15 +74,9 @@ const EvaluationList = () => {

// 3. Render course list elemnts
evaluationListings = evaluationListings.map(
({ _id, courseId, reviewDescription }) => {
({ _id, courseId, reviewDescription, coordinators }) => {
// select out the coordinators with the permission for this evaluation

const coordinators = users.filter(({ perms }) =>
perms.some(
({ course_id, role }) => course_id === _id && role === 'Coordinator'
)
);

return (
<ListItem key={_id} divider>
<EvaluationListing
Expand Down
4 changes: 2 additions & 2 deletions client/components/Coordinator/Evaluation/EvaluationListing.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ const EvaluationListing = ({
}) => {
const classes = useStyles();

const coordinatorNames = coordinators.map(({ name }) => name)?.join(', ');
const coordinatorNames = coordinators.map(({ name,email }) => name || email)?.join(', ');

return (
<Grid
Expand All @@ -33,7 +33,7 @@ const EvaluationListing = ({
>
<GridItem md={7}>
<h3 className={classes.title}>{courseCode}</h3>
<h4 className={classes.description}>{coordinatorNames}.</h4>
<h4 className={classes.description}>{coordinatorNames}</h4>
<h5 className={classes.description}>{evaluationDescription}</h5>
</GridItem>
<GridItem md={2}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,49 +29,24 @@ const useStyles = makeStyles({
import { useSelector } from 'react-redux';
import { services, rawServices } from 'store/feathersClient';

import { useState, useEffect } from 'react';
import { useState } from 'react';

const ManageReviewers = ({ evaluationID }) => {
const classes = useStyles();
const [modal, setModal] = useState(false);
const [email, setEmail] = useState('');

useEffect(() => {
services['users'].find({
perms: {
$in: [{ course_id: evaluationID, role: 'Reviewer' }],
},
});
}, []);

const courseEval = useSelector((state) => state['course-evaluation']);
const evalData = courseEval?.data;
const users = useSelector((state) => state['users']);
const userData = users?.queryResult?.data;

// selects out all reviewers with correct permission
const reviewers = userData.filter((user) =>
user.perms.reduce(
(acc, permission) =>
acc ||
(permission.course_id == evaluationID && permission.role == 'Reviewer'),
false
)
);
const reviewers = evalData?.reviewers || [];

// removes a user from the evaluation
const removePermission = async (userId, evaluationId) => {
try {
const oldPermissions = reviewers.find((user) => user._id == userId).perms;
const newPerms = oldPermissions.filter(
(permission) =>
!(
permission.course_id == evaluationId &&
permission.role == 'Reviewer'
)
);
const response = await services['users'].patch(userId, {
perms: newPerms,
services['users'].patch(userId, {
$pull: { perms: {course_id:evaluationId,role:'Reviewer'}}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tested and working

});
} catch (error) {
console.error(error);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ const useStyles = makeStyles({
const ReviewerListing = ({
email,
name,
inviter,
googleId,
removeReviewer,
}) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,20 @@
import Card from 'components/MaterialKit/Card/Card.js';
import CardBody from 'components/MaterialKit/Card/CardBody.js';
import CardHeader from 'components/MaterialKit/Card/CardHeader.js';
import Grid from 'components/MaterialKit/Grid/GridContainer.js';
import GridItem from 'components/MaterialKit/Grid/GridItem.js';

// Store Actions and Redux
import { useSelector } from 'react-redux';
import { services } from 'store/feathersClient';

import { useEffect } from 'react';

const OtherInformation = () => {
useEffect(() => {
services['users'].find();
}, []);

const courseEval = useSelector((state) => state['course-evaluation']);
const evalData = courseEval?.data;

const users = useSelector((state) => state['users']);
const userData = users?.queryResult?.data;
const coordinators = evalData?.coordinators || [];

const createdOn = new Date(evalData?.createdAt);
const createdBy = evalData?.createdBy;
const author = userData?.find((elem) => elem._id == createdBy);
const dateString = createdOn?.toLocaleDateString('en-gb', {
year: 'numeric',
month: 'short',
Expand All @@ -33,11 +26,18 @@ const OtherInformation = () => {
<Card>
<CardHeader color="success">Other Information</CardHeader>
<CardBody>
<h4>Created by:</h4>
<p>
{author?.name ?? 'Unknown creator'} (on {dateString ?? 'unknown date'}
)
</p>
<Grid direction="row" alignItems="center" justify="center">
<GridItem xs={6}>
<h4>Coordinators</h4>
{coordinators.map(({name},index) => <p key={`coordinators-${index}`}>{name}</p>)}
</GridItem>
<GridItem xs={6}>
<h4>Date Started</h4>
<p>
{dateString ?? 'unknown date'}
</p>
</GridItem>
</Grid>
</CardBody>
</Card>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,10 @@ const ProgressDisplay = ({ reviewer, review }) => {
display="inline-block"
color="white"
onClick={handleView}
disabled={!review?._id}
>
<PageviewIcon />
View
View
</Button>
{review?.submittedDate && (

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,62 +12,40 @@ import { useSelector } from 'react-redux';
import { services } from 'store/feathersClient';

import { useRouter } from 'next/router';
import { useState, useEffect } from 'react';
import { useEffect } from 'react';

const ReviewProgress = () => {
const router = useRouter();

const [reviewers, setReviewers] = useState([]);
const [loading, setLoading] = useState(true);

const hasPath = router.query.hasOwnProperty('courseID');

const { courseID } = router.query;
useEffect(() => {
if (router.query.hasOwnProperty('courseID')) {
const { courseID } = router.query;

services['review'].find({
course_id: courseID,
});
services['users'].find(
{query:
{
perms: {
$elemMatch: { course_id: courseID, role: 'Reviewer' },
}
}
}
);
}
}, [hasPath]);

if (hasPath) {
const reviews = useSelector((state) => state['review']);
const reviewData = reviews?.queryResult?.data;

const users = useSelector((state) => state['users']);

const usersData = users?.queryResult?.data;

// TODO Note: Something similar is done in utils/compileResult
const progressCards = usersData?.map(reviewer=>{
const reviewOfUser = reviewData?.find(review=> review.user_id === reviewer._id) || {};
return(
<ProgressDisplay reviewer={reviewer} review={reviewOfUser} key={reviewer._id} />
);
services['review'].find({
course_id: courseID,
});

return (
<Card>
<CardHeader color="success">Review Progress</CardHeader>
<CardBody>
<GridContainer>{progressCards}</GridContainer>
</CardBody>
</Card>
}, []);

const courseEval = useSelector((state) => state['course-evaluation']);
const evalData = courseEval?.data;
const usersData = evalData?.reviewers;
const reviews = useSelector((state) => state['review']);
const reviewData = reviews?.queryResult?.data;

// TODO Note: Something similar is done in utils/compileResult
const progressCards = usersData?.map(reviewer=>{
const reviewOfUser = reviewData?.find(review=> review.user_id === reviewer._id) || {};
return(
<ProgressDisplay reviewer={reviewer} review={reviewOfUser} key={reviewer._id} />
);
} else {
return <p>invalid...</p>;
}
});

return (
<Card>
<CardHeader color="success">Review Progress</CardHeader>
<CardBody>
<GridContainer>{progressCards}</GridContainer>
</CardBody>
</Card>
);
};

export default ReviewProgress;
3 changes: 1 addition & 2 deletions client/components/reviewer/ReviewList.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,7 @@ const EvaluationList = () => {
const authUser = useSelector((state) => state.auth.user);

useEffect(() => {
// 1. Find all CourseEvaluations where the createdBy key matches the logged in user
services['course-evaluation'].find();
services['course-evaluation'].find(); // This is already a filtered lists based on perms
}, []);

// Executes on Component Remount (after auth user is fetched)
Expand Down
4 changes: 2 additions & 2 deletions client/components/reviewer/ReviewListing.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,14 @@ const ReviewListing = ({
}) => {
const classes = useStyles();

const coordinatorNames = coordinators?.join(', ');
const coordinatorNames = coordinators?.map(({name,email})=>name || email).join(', ');

return (
<Grid className={classes.root} direction="row" alignItems="center" justify="center">
<GridItem md={7}>
<Link href={`/reviewer/${evalId}/1-overview-and-eoc`} style={{textDecoration:'none'}}>
<h3 className={classes.title}>{courseCode}</h3>
<h4 className={classes.description}>{coordinatorNames}.............</h4>
<h4 className={classes.description}>{coordinatorNames}</h4>
<h5 className={classes.description}>{evaluationDescription}</h5>
</Link>
</GridItem>
Expand Down
14 changes: 3 additions & 11 deletions client/pages/coordinator/[courseID]/review/compiled.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,19 +24,11 @@ const CompiledPage = () => {
course_id: courseID
},
}); //Accessed by queryResult.data
services['users'].find(
{query:
{
perms: {
$elemMatch: { course_id: courseID, role: 'Reviewer' },
}
}
}
); // Accessed by queryResult.data
}, [courseID]);

const userState = useSelector(state=> state.users);
const reviewers = userState?.queryResult.data;
const courseEval = useSelector((state) => state['course-evaluation']);
const evalData = courseEval?.data;
const reviewers = evalData?.reviewers || [];
const reviewState = useSelector(state=> state.review);
const reviews = reviewState?.queryResult.data;

Expand Down
56 changes: 56 additions & 0 deletions mkdocs/docs/developer/backend/permission.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# Permission
Permission refers to the authority of the user to access or modify information. In business rules, the only reason why a user has to have permission if they need to have access to that information or authority to have action. This means that if a user does not need to access that information, they should not have it.

In IndEAA, this is essential in restricting each of the users to perform only actions that they have permission over. The permission has 2 main elements:

- `role` (what sets of permission does the user have?)
- `course_id` (which `course_evaluation` where this role persists?)


## Relationship to Frontend
Although this part of the documentation applies for both the backend and frontend, this documentation is mainly for backend as it is where most business rules will apply. Permissions for frontend mainly apply only for the convenience of the user, but does not protect certain information or action from being exposed. Hence, all data-driven action (CRUD operations) should be protected by the backend.

## Role

### Administrator
Administrators have the capability to adjust all the permissions of the user in the system, and even has the ability to delete users from the system.

### Coordinator
Coordinators have the capability adjust everything related to the `course_evaluation` and should be able to view details about other coordinators and reviewers of their `course_evaluation`

### Reviewer

Reviewers have the capability to adjust everything related to their own `review` for a `course_evaluation`, and should be able to view details about coordinators for the `course_evaluation` they are assigned to.

## Hooks

### Role-Based Restriction
This hook restricts actions/service methods only to users with a specific role regardless of `course_id`.

???+ example "Examples of Times where you want this"

- Admiinistrator Role

The administrator role does not need `course_id`. Hence, should be used for it

- Coordinator Role

Creating a `course_evaluation` requires a Coordinator role, but does not need to check for `course_id`

### Filter-Based Restriction
This hook restricts query (`GET` and `FIND` service methods) to limit (`FIND`) based on query or restricts access (`GET`).

### Role-And-Course Based Restriction
This hook is a stricter version of `Role-Based Restrictions` as this also applies with `course_id` that the user has access to.

???+ example "Example of Times where you want this"

- Multi-Role Coordinator

A coordinator should not have access to other `course_evaluation` they do not have access on.

- Multi-Role Reviewer

A reviewer should not have access to `course_evaluation` they do not have access on, and should also not be able to view `review` that they are not the review person for.


Loading