Skip to content

Commit

Permalink
PIMS-2082 Backend Authorization Updates (#2679)
Browse files Browse the repository at this point in the history
Co-authored-by: LawrenceLau2020 <[email protected]>
  • Loading branch information
dbarkowsky and LawrenceLau2020 authored Sep 18, 2024
1 parent 2aa895d commit 3afc60a
Show file tree
Hide file tree
Showing 52 changed files with 617 additions and 456 deletions.
8 changes: 4 additions & 4 deletions express-api/src/constants/roles.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
/**
* @enum
* The values in this enum must exactly mirror the names of the Keycloak roles.
* The values in this enum must exactly mirror the IDs in the Role table.
*/
export enum Roles {
ADMIN = 'Administrator',
GENERAL_USER = 'General User',
AUDITOR = 'Auditor',
ADMIN = '00000000-0000-0000-0000-000000000000',
GENERAL_USER = '00000000-0000-0000-0000-000000000001',
AUDITOR = '00000000-0000-0000-0000-000000000002',
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
import { Request, Response } from 'express';
import { SSOUser } from '@bcgov/citz-imb-sso-express';
import {
AdministrativeAreaFilterSchema,
AdministrativeAreaPublicResponseSchema,
} from '@/services/administrativeAreas/administrativeAreaSchema';
import administrativeAreasServices from '@/services/administrativeAreas/administrativeAreasServices';
import { Roles } from '@/constants/roles';
import userServices from '@/services/users/usersServices';

/**
* @description Gets a list of administrative areas.
Expand All @@ -15,13 +13,12 @@ import userServices from '@/services/users/usersServices';
* @returns {Response} A 200 status with a list of administrative areas.
*/
export const getAdministrativeAreas = async (req: Request, res: Response) => {
const ssoUser = req.user;
const user = req.pimsUser;
const filter = AdministrativeAreaFilterSchema.safeParse(req.query);
if (filter.success) {
const adminAreas = await administrativeAreasServices.getAdministrativeAreas(filter.data);
// TODO: Do we still need this condition? Few fields are trimmed since moving to view.
if (!ssoUser.hasRoles([Roles.ADMIN])) {
const trimmed = AdministrativeAreaPublicResponseSchema.array().parse(adminAreas.data);
if (!user.hasOneOfRoles([Roles.ADMIN])) {
const trimmed = AdministrativeAreaPublicResponseSchema.array().parse(adminAreas);
return res.status(200).send({
...adminAreas,
data: trimmed,
Expand All @@ -40,7 +37,7 @@ export const getAdministrativeAreas = async (req: Request, res: Response) => {
* @returns {Response} A 201 status and response with the added administrative area.
*/
export const addAdministrativeArea = async (req: Request, res: Response) => {
const user = await userServices.getUser((req.user as SSOUser).preferred_username);
const user = req.pimsUser;
const addBody = { ...req.body, CreatedById: user.Id };
const response = await administrativeAreasServices.addAdministrativeArea(addBody);
return res.status(201).send(response);
Expand Down
10 changes: 4 additions & 6 deletions express-api/src/controllers/agencies/agenciesController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@ import { Request, Response } from 'express';
import * as agencyService from '@/services/agencies/agencyServices';
import { AgencyFilterSchema, AgencyPublicResponseSchema } from '@/services/agencies/agencySchema';
import { z } from 'zod';
import { SSOUser } from '@bcgov/citz-imb-sso-express';
import { Roles } from '@/constants/roles';
import userServices from '@/services/users/usersServices';
import { Agency } from '@/typeorm/Entities/Agency';

/**
Expand All @@ -14,11 +12,11 @@ import { Agency } from '@/typeorm/Entities/Agency';
* @returns {Response} A 200 status with a list of agencies.
*/
export const getAgencies = async (req: Request, res: Response) => {
const ssoUser = req.user;
const user = req.pimsUser;
const filter = AgencyFilterSchema.safeParse(req.query);
if (filter.success) {
const agencies = await agencyService.getAgencies(filter.data);
if (!ssoUser.client_roles || !ssoUser.client_roles.includes(Roles.ADMIN)) {
if (!user.hasOneOfRoles([Roles.ADMIN])) {
const trimmed = AgencyPublicResponseSchema.array().parse(agencies);
return res.status(200).send({
...agencies,
Expand All @@ -38,7 +36,7 @@ export const getAgencies = async (req: Request, res: Response) => {
* @returns {Response} A 201 status and the data of the agency added.
*/
export const addAgency = async (req: Request, res: Response) => {
const user = await userServices.getUser((req.user as SSOUser).preferred_username);
const user = req.pimsUser;
const agency = await agencyService.addAgency({ ...req.body, CreatedById: user.Id });

return res.status(201).send(agency);
Expand Down Expand Up @@ -77,7 +75,7 @@ export const updateAgencyById = async (req: Request, res: Response) => {
if (updateInfo.ParentId != null && updateInfo.ParentId === updateInfo.Id) {
return res.status(403).send('An agency cannot be its own parent.');
}
const user = await userServices.getUser((req.user as SSOUser).preferred_username);
const user = req.pimsUser;
const agency = await agencyService.updateAgencyById({ ...req.body, UpdatedById: user.Id });
return res.status(200).send(agency);
};
Expand Down
24 changes: 10 additions & 14 deletions express-api/src/controllers/buildings/buildingsController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,8 @@ import { Request, Response } from 'express';
import * as buildingService from '@/services/buildings/buildingServices';
import { BuildingFilter, BuildingFilterSchema } from '@/services/buildings/buildingSchema';
import userServices from '@/services/users/usersServices';
import { SSOUser } from '@bcgov/citz-imb-sso-express';
import { Building } from '@/typeorm/Entities/Building';
import { checkUserAgencyPermission, isAdmin, isAuditor } from '@/utilities/authorizationChecks';
import { checkUserAgencyPermission } from '@/utilities/authorizationChecks';
import { Roles } from '@/constants/roles';
import { AppDataSource } from '@/appDataSource';
import { exposedProjectStatuses } from '@/constants/projectStatus';
Expand All @@ -19,14 +18,14 @@ import { ProjectProperty } from '@/typeorm/Entities/ProjectProperty';
export const getBuildings = async (req: Request, res: Response) => {
const filter = BuildingFilterSchema.safeParse(req.query);
const includeRelations = req.query.includeRelations === 'true';
const kcUser = req.user as unknown as SSOUser;
const user = req.pimsUser;
if (!filter.success) {
return res.status(400).send('Could not parse filter.');
}
const filterResult = filter.data;
if (!(isAdmin(kcUser) || isAuditor(kcUser))) {
if (!user.hasOneOfRoles([Roles.ADMIN, Roles.AUDITOR])) {
// get array of user's agencies
const usersAgencies = await userServices.getAgencies(kcUser.preferred_username);
const usersAgencies = await userServices.getAgencies(user.Username);
filterResult.agencyId = usersAgencies;
}
// Get parcels associated with agencies of the requesting user
Expand All @@ -51,7 +50,7 @@ export const getBuilding = async (req: Request, res: Response) => {

// admin and auditors are permitted to see any building
const permittedRoles = [Roles.ADMIN, Roles.AUDITOR];
const kcUser = req.user as unknown as SSOUser;
const user = req.pimsUser;
const building = await buildingService.getBuildingById(buildingId);

if (!building) {
Expand All @@ -75,7 +74,7 @@ export const getBuilding = async (req: Request, res: Response) => {
);

if (
!(await checkUserAgencyPermission(kcUser, [building.AgencyId], permittedRoles)) &&
!(await checkUserAgencyPermission(user, [building.AgencyId], permittedRoles)) &&
!isVisibleToOtherAgencies
) {
return res.status(403).send('You are not authorized to view this building.');
Expand All @@ -94,9 +93,9 @@ export const updateBuilding = async (req: Request, res: Response) => {
if (isNaN(buildingId) || buildingId !== req.body.Id) {
return res.status(400).send('Building ID was invalid or mismatched with body.');
}
const user = await userServices.getUser((req.user as SSOUser).preferred_username);
const user = req.pimsUser;
const updateBody = { ...req.body, UpdatedById: user.Id };
const building = await buildingService.updateBuildingById(updateBody, req.user);
const building = await buildingService.updateBuildingById(updateBody, req.pimsUser);
return res.status(200).send(building);
};

Expand All @@ -111,10 +110,7 @@ export const deleteBuilding = async (req: Request, res: Response) => {
if (isNaN(buildingId)) {
return res.status(400).send('Building ID was invalid.');
}
const delResult = await buildingService.deleteBuildingById(
buildingId,
req.user.preferred_username,
);
const delResult = await buildingService.deleteBuildingById(buildingId, req.pimsUser);
return res.status(200).send(delResult);
};

Expand All @@ -126,7 +122,7 @@ export const deleteBuilding = async (req: Request, res: Response) => {
* Note: the original implementation returns 200, but as a resource is created 201 is better.
*/
export const addBuilding = async (req: Request, res: Response) => {
const user = await userServices.getUser((req.user as SSOUser).preferred_username);
const user = req.pimsUser;
const createBody: Building = { ...req.body, CreatedById: user.Id };
createBody.Evaluations = createBody.Evaluations?.map((evaluation) => ({
...evaluation,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,8 @@ import notificationServices, {
NotificationStatus,
} from '@/services/notifications/notificationServices';
import userServices from '@/services/users/usersServices';
import { SSOUser } from '@bcgov/citz-imb-sso-express';
import { Request, Response } from 'express';
import { DisposalNotificationFilterSchema } from './notificationsSchema';
import { isAdmin, isAuditor } from '@/utilities/authorizationChecks';
import projectServices from '@/services/projects/projectsServices';
import { Roles } from '@/constants/roles';
import logger from '@/utilities/winstonLogger';
Expand All @@ -23,12 +21,11 @@ export const getNotificationsByProjectId = async (req: Request, res: Response) =
return res.status(400).send({ message: 'Could not parse filter.' });
}
const filterResult = filter.data;
const kcUser = req.user as unknown as SSOUser;
const user = await userServices.getUser((req.user as SSOUser).preferred_username);
const user = req.pimsUser;

if (!(isAdmin(kcUser) || isAuditor(kcUser))) {
if (!user.hasOneOfRoles([Roles.ADMIN, Roles.AUDITOR])) {
// get array of user's agencies
const usersAgencies = await userServices.getAgencies(kcUser.preferred_username);
const usersAgencies = await userServices.getAgencies(user.Username);

const project = await projectServices.getProjectById(filterResult.projectId);
if (!usersAgencies.includes(project.AgencyId)) {
Expand Down Expand Up @@ -56,16 +53,15 @@ export const getNotificationsByProjectId = async (req: Request, res: Response) =
};

export const resendNotificationById = async (req: Request, res: Response) => {
const kcUser = req.user;
if (!kcUser.hasRoles([Roles.ADMIN]))
const user = req.pimsUser;
if (!user.hasOneOfRoles([Roles.ADMIN]))
return res.status(403).send('User lacks permissions to resend notification.');
const id = Number(req.params.id);
const notification = await notificationServices.getNotificationById(id);
if (!notification) {
return res.status(404).send('Notification not found.');
}
const resultantNotification = await notificationServices.sendNotification(notification, kcUser);
const user = await userServices.getUser(kcUser.preferred_username);
const resultantNotification = await notificationServices.sendNotification(notification, user);
const updatedNotification = await notificationServices.updateNotificationStatus(
resultantNotification.Id,
user,
Expand All @@ -74,15 +70,14 @@ export const resendNotificationById = async (req: Request, res: Response) => {
};

export const cancelNotificationById = async (req: Request, res: Response) => {
const kcUser = req.user;
if (!kcUser.hasRoles([Roles.ADMIN]))
const user = req.pimsUser;
if (!user.hasOneOfRoles([Roles.ADMIN]))
return res.status(403).send('User lacks permissions to cancel notification.');
const id = Number(req.params.id);
const notification = await notificationServices.getNotificationById(id);
if (!notification) {
return res.status(404).send('Notification not found.');
}
const user = await userServices.getUser(kcUser.preferred_username);
const resultantNotification = await notificationServices.cancelNotificationById(
notification.Id,
user,
Expand Down
21 changes: 10 additions & 11 deletions express-api/src/controllers/parcels/parcelsController.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import { Request, Response } from 'express';
import parcelServices from '@/services/parcels/parcelServices';
import { ParcelFilter, ParcelFilterSchema } from '@/services/parcels/parcelSchema';
import { SSOUser } from '@bcgov/citz-imb-sso-express';
import userServices from '@/services/users/usersServices';
import { Parcel } from '@/typeorm/Entities/Parcel';
import { Roles } from '@/constants/roles';
import { checkUserAgencyPermission, isAdmin, isAuditor } from '@/utilities/authorizationChecks';
import { checkUserAgencyPermission } from '@/utilities/authorizationChecks';
import { AppDataSource } from '@/appDataSource';
import { ProjectProperty } from '@/typeorm/Entities/ProjectProperty';
import { exposedProjectStatuses } from '@/constants/projectStatus';
Expand All @@ -24,7 +23,7 @@ export const getParcel = async (req: Request, res: Response) => {

// admin and auditors are permitted to see any parcel
const permittedRoles = [Roles.ADMIN, Roles.AUDITOR];
const kcUser = req.user as unknown as SSOUser;
const user = req.pimsUser;
const parcel = await parcelServices.getParcelById(parcelId);

if (!parcel) {
Expand All @@ -48,7 +47,7 @@ export const getParcel = async (req: Request, res: Response) => {
);

if (
!(await checkUserAgencyPermission(kcUser, [parcel.AgencyId], permittedRoles)) &&
!(await checkUserAgencyPermission(user, [parcel.AgencyId], permittedRoles)) &&
!isVisibleToOtherAgencies
) {
return res.status(403).send('You are not authorized to view this parcel.');
Expand All @@ -67,9 +66,9 @@ export const updateParcel = async (req: Request, res: Response) => {
if (isNaN(parcelId) || parcelId !== req.body.Id) {
return res.status(400).send('Parcel ID was invalid or mismatched with body.');
}
const user = await userServices.getUser((req.user as SSOUser).preferred_username);
const user = req.pimsUser;
const updateBody = { ...req.body, UpdatedById: user.Id };
const parcel = await parcelServices.updateParcel(updateBody, req.user);
const parcel = await parcelServices.updateParcel(updateBody, req.pimsUser);
if (!parcel) {
return res.status(404).send('Parcel matching this internal ID not found.');
}
Expand All @@ -87,7 +86,7 @@ export const deleteParcel = async (req: Request, res: Response) => {
if (isNaN(parcelId)) {
return res.status(400).send('Parcel ID was invalid.');
}
const delResult = await parcelServices.deleteParcelById(parcelId, req.user.preferred_username);
const delResult = await parcelServices.deleteParcelById(parcelId, req.pimsUser);
return res.status(200).send(delResult);
};

Expand All @@ -100,14 +99,14 @@ export const deleteParcel = async (req: Request, res: Response) => {
export const getParcels = async (req: Request, res: Response) => {
const filter = ParcelFilterSchema.safeParse(req.query);
const includeRelations = req.query.includeRelations === 'true';
const kcUser = req.user as unknown as SSOUser;
if (!filter.success) {
return res.status(400).send('Could not parse filter.');
}
const filterResult = filter.data;
if (!(isAdmin(kcUser) || isAuditor(kcUser))) {
const user = req.pimsUser;
if (!user.hasOneOfRoles([Roles.ADMIN, Roles.AUDITOR])) {
// get array of user's agencies
const usersAgencies = await userServices.getAgencies(kcUser.preferred_username);
const usersAgencies = await userServices.getAgencies(user.Username);
filterResult.agencyId = usersAgencies;
}
// Get parcels associated with agencies of the requesting user
Expand All @@ -129,7 +128,7 @@ export const getParcels = async (req: Request, res: Response) => {
* Note: the original implementation returns 200, but as a resource is created 201 is better.
*/
export const addParcel = async (req: Request, res: Response) => {
const user = await userServices.getUser((req.user as SSOUser).preferred_username);
const user = req.pimsUser;
const parcel: Parcel = { ...req.body, CreatedById: user.Id };
parcel.Evaluations = parcel.Evaluations?.map((evaluation) => ({
...evaluation,
Expand Down
Loading

0 comments on commit 3afc60a

Please sign in to comment.