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

Commit

Permalink
Merge pull request #113 from uwasystemhealth/i24-permission-protection
Browse files Browse the repository at this point in the history
I24 permission protection + metadata on course-evaluation page
  • Loading branch information
MouseAndKeyboard authored Mar 2, 2021
2 parents 54c3c65 + 570f703 commit ea01853
Show file tree
Hide file tree
Showing 24 changed files with 231 additions and 148 deletions.
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'}}
});
} 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

0 comments on commit ea01853

Please sign in to comment.