Skip to content
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

PIMS-2082 Backend Authorization Updates #2679

Merged
merged 17 commits into from
Sep 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
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
Loading