Skip to content

Commit

Permalink
add: permission checks to application-round pages
Browse files Browse the repository at this point in the history
  • Loading branch information
joonatank committed Apr 25, 2024
1 parent d5d371f commit 1501e8b
Show file tree
Hide file tree
Showing 5 changed files with 93 additions and 42 deletions.
20 changes: 20 additions & 0 deletions apps/admin-ui/src/hooks/usePermission.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type {
UnitNode,
ReservationNode,
UserNode,
ApplicationRoundNode,
} from "common/types/gql-types";
import {
hasPermission as baseHasPermission,
Expand Down Expand Up @@ -83,6 +84,24 @@ const usePermission = () => {
return baseHasAnyPermission(user);
};

// TODO restrict the Permission type to only those that are applicable to application rounds
const hasApplicationRoundPermission = (
applicationRound: ApplicationRoundNode,
permission: Permission
) => {
if (!user) return false;
const units = filterNonNullable(
applicationRound.reservationUnits.flatMap((ru) => ru.unit)
);
for (const unit of units) {
if (hasUnitPermission(user, permission, unit)) {
return true;
}
}
return false;
};

// TODO this is becoming convoluted with the addition of a new function for each object type
return {
user,
hasPermission: (
Expand All @@ -94,6 +113,7 @@ const usePermission = () => {
hasAnyPermission,
hasUnitPermission: (permission: Permission, unit: UnitNode) =>
hasUnitPermission(user, permission, unit),
hasApplicationRoundPermission,
};
};

Expand Down
1 change: 1 addition & 0 deletions apps/admin-ui/src/modules/permissionHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export enum Permission {
CAN_MANAGE_RESOURCES = GeneralPermissionChoices.CanManageResources,
CAN_MANAGE_UNITS = GeneralPermissionChoices.CanManageUnits,
CAN_VALIDATE_APPLICATIONS = GeneralPermissionChoices.CanValidateApplications,
CAN_MANAGE_APPLICATIONS = GeneralPermissionChoices.CanHandleApplications,
CAN_MANAGE_BANNER_NOTIFICATIONS = GeneralPermissionChoices.CanManageNotifications,
}
/* eslint-enable @typescript-eslint/prefer-literal-enum-member */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -556,6 +556,7 @@ function AllocationWrapper({

const { t } = useTranslation();
const { hasUnitPermission } = usePermission();
const { hasApplicationRoundPermission } = usePermission();

// TODO don't use spinners, skeletons are better
// also this blocks the sub component query (the initial with zero filters) which slows down the page load
Expand All @@ -570,8 +571,24 @@ function AllocationWrapper({
return <p>{t("errors.errorFetchingData")}</p>;
}

const appRound = data?.applicationRound;
const reservationUnits = filterNonNullable(appRound?.reservationUnits);
const { applicationRound } = data ?? {};

// should never be null but our codegen causes type problems
const canManage =
applicationRound != null
? hasApplicationRoundPermission(
applicationRound,
Permission.CAN_MANAGE_APPLICATIONS
)
: false;

if (!canManage) {
return <div>{t("errors.noPermission")}</div>;
}

const reservationUnits = filterNonNullable(
applicationRound?.reservationUnits
);
const unitData = reservationUnits.map((ru) => ru?.unit);

// TODO name sort fails with numbers because 11 < 2
Expand All @@ -581,7 +598,7 @@ function AllocationWrapper({
)
.sort((a, b) => a?.nameFi?.localeCompare(b?.nameFi ?? "") ?? 0);

const roundName = appRound?.nameFi ?? "-";
const roundName = applicationRound?.nameFi ?? "-";

const resUnits = uniqBy(filterNonNullable(reservationUnits), "pk").sort(
(a, b) => a?.nameFi?.localeCompare(b?.nameFi ?? "") ?? 0
Expand All @@ -591,13 +608,13 @@ function AllocationWrapper({
<>
<BreadcrumbWrapper backLink=".." />
<ApplicationRoundAllocation
applicationRound={appRound ?? undefined}
applicationRound={applicationRound ?? undefined}
applicationRoundPk={applicationRoundPk}
units={units}
reservationUnits={resUnits}
roundName={roundName}
applicationRoundStatus={
appRound?.status ?? ApplicationRoundStatusChoice.Upcoming
applicationRound?.status ?? ApplicationRoundStatusChoice.Upcoming
}
/>
</>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,34 +2,38 @@ import React from "react";
import { useQuery } from "@apollo/client";
import { useParams } from "react-router-dom";
import { useTranslation } from "react-i18next";
import { type Query } from "common/types/gql-types";
import {
type Query,
type QueryApplicationRoundArgs,
} from "common/types/gql-types";
import { useNotification } from "@/context/NotificationContext";
import BreadcrumbWrapper from "@/component/BreadcrumbWrapper";
import Loader from "@/component/Loader";
import { Review } from "./review/Review";
import { APPLICATION_ROUND_QUERY } from "../queries";
import usePermission from "@/hooks/usePermission";
import { Permission } from "@/modules/permissionHelper";
import { base64encode } from "common/src/helpers";

function ApplicationRound({
applicationRoundId,
}: {
applicationRoundId: number;
}): JSX.Element | null {
function ApplicationRound({ pk }: { pk: number }): JSX.Element {
const { notifyError } = useNotification();
const { t } = useTranslation();

const { data, loading: isLoading } = useQuery<Query>(
APPLICATION_ROUND_QUERY,
{
skip: !applicationRoundId,
variables: {
pk: [applicationRoundId],
},
onError: () => {
notifyError(t("errors.errorFetchingData"));
},
}
);
const applicationRound = data?.applicationRounds?.edges?.[0]?.node;
const id = base64encode(`ApplicationRoundNode:${pk}`);
const { data, loading: isLoading } = useQuery<
Query,
QueryApplicationRoundArgs
>(APPLICATION_ROUND_QUERY, {
skip: !pk,
variables: { id },
onError: () => {
notifyError(t("errors.errorFetchingData"));
},
});

const { applicationRound } = data ?? {};

const { hasApplicationRoundPermission } = usePermission();

if (isLoading) {
return <Loader />;
Expand All @@ -39,6 +43,18 @@ function ApplicationRound({
return <div>{t("errors.applicationRoundNotFound")}</div>;
}

const canView = hasApplicationRoundPermission(
applicationRound,
Permission.CAN_VALIDATE_APPLICATIONS
);
const canManage = hasApplicationRoundPermission(
applicationRound,
Permission.CAN_MANAGE_APPLICATIONS
);
if (!canView && !canManage) {
return <div>{t("errors.noPermission")}</div>;
}

const route = [
{
alias: t("breadcrumb.recurring-reservations"),
Expand Down Expand Up @@ -70,10 +86,12 @@ function ApplicationRoundRouted(): JSX.Element | null {
const { t } = useTranslation();
const { applicationRoundId } = useParams<IParams>();

if (!applicationRoundId || Number.isNaN(Number(applicationRoundId))) {
return <div>{t("errors.router.invalidApplicationRoundNumber")}</div>;
const pk = Number(applicationRoundId);
if (pk > 0) {
return <ApplicationRound pk={pk} />;
}
return <ApplicationRound applicationRoundId={Number(applicationRoundId)} />;

return <div>{t("errors.router.invalidApplicationRoundNumber")}</div>;
}

export default ApplicationRoundRouted;
Original file line number Diff line number Diff line change
Expand Up @@ -32,23 +32,18 @@ export const APPLICATION_ROUNDS_QUERY = gql`
}
`;

// TODO replace with relay query
export const APPLICATION_ROUND_QUERY = gql`
${APPLICATION_ROUND_FRAGMENT}
query ApplicationRound($pk: [Int]!) {
applicationRounds(pk: $pk) {
edges {
node {
...ApplicationRoundFragment
applicationsCount
reservationUnits {
pk
nameFi
unit {
pk
nameFi
}
}
query ApplicationRound($id: ID!) {
applicationRound(id: $id) {
...ApplicationRoundFragment
applicationsCount
reservationUnits {
pk
nameFi
unit {
pk
nameFi
}
}
}
Expand Down

0 comments on commit 1501e8b

Please sign in to comment.