Skip to content

Commit

Permalink
Merge branch 'main' into PIMS-1941-express-api-updates
Browse files Browse the repository at this point in the history
  • Loading branch information
TaylorFries authored Aug 7, 2024
2 parents 24c451f + b93705e commit 51f588d
Show file tree
Hide file tree
Showing 60 changed files with 2,335 additions and 958 deletions.
4 changes: 2 additions & 2 deletions .env-template
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ CSS_API_CLIENT_SECRET= # Keycloak CSS API Service Account client_secret
# BC Geocoder
GEOCODER_KEY=

# Error Report
ERROR_REPORT_TO=
# Contact
CONTACT_EMAIL=

# CHES Settings
CHES_USERNAME=
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,6 @@ yarn-error.log*

jest/latestTestRun.json
package-lock.json

# .csv and txt files created by script
tools/propertyCheck/data/*
2 changes: 1 addition & 1 deletion express-api/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ Recommended values used with this API that match with the current API and Docker
|SSO_INTEGRATION_ID|1234|See Keycloak integration. Used for CSS API.|
|SSO_ENVIRONMENT|dev|Target environment of Keycloak integration. Used for CSS API.|
|GEOCODER_KEY|abc123|API key for BC Geocoder use.|
|ERROR_REPORT_TO|<[email protected]>|Destination email for frontend error reporting.|
|CONTACT_EMAIL|<[email protected]>|Destination email for frontend error reporting and help.|
|CHES_USERNAME|abc123|Username for CHES service.|
|CHES_PASSWORD|def456|Password for CHES service.|
|CHES_AUTH_URL|https://...|URL where authorization tokens for CHES are obtained.|
Expand Down
4 changes: 2 additions & 2 deletions express-api/src/constants/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ const config = {
notificationTemplate: 15,
notificationTemplateRPD: 17,
},
errorReport: {
toEmail: process.env.ERROR_REPORT_TO,
contact: {
toEmail: process.env.CONTACT_EMAIL,
},
notificationTemplate: {
title: 'PIMS',
Expand Down
5 changes: 4 additions & 1 deletion express-api/src/controllers/buildings/buildingsController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ 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 { Roles } from '@/constants/roles';

/**
* @description Gets all buildings satisfying the filter parameters.
Expand Down Expand Up @@ -52,12 +53,14 @@ export const getBuilding = async (req: Request, res: Response) => {
return res.status(400).send('Building Id is invalid.');
}

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

if (!building) {
return res.status(404).send('Building matching this ID was not found.');
} else if (!(await checkUserAgencyPermission(kcUser, [building.AgencyId]))) {
} else if (!(await checkUserAgencyPermission(kcUser, [building.AgencyId], permittedRoles))) {
return res.status(403).send('You are not authorized to view this building.');
}
return res.status(200).send(building);
Expand Down
5 changes: 5 additions & 0 deletions express-api/src/controllers/lookup/lookupController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { Role } from '@/typeorm/Entities/Role';
import { Agency } from '@/typeorm/Entities/Agency';
import { AdministrativeArea } from '@/typeorm/Entities/AdministrativeArea';
import { Workflow } from '@/typeorm/Entities/Workflow';
import getConfig from '@/constants/config';

/**
* @description Get all property classification entries.
Expand Down Expand Up @@ -233,6 +234,7 @@ export const lookupTimestampTypes = async (req: Request, res: Response) => {
* @returns {Response} A 200 status and a list entries.
*/
export const lookupAll = async (req: Request, res: Response) => {
const cfg = getConfig();
const Risks = await AppDataSource.getRepository(ProjectRisk).find({
select: {
Id: true,
Expand Down Expand Up @@ -407,6 +409,9 @@ export const lookupAll = async (req: Request, res: Response) => {
a.Name.toLowerCase().localeCompare(b.Name.toLowerCase()),
),
Workflows,
Config: {
contactEmail: cfg.contact.toEmail,
},
};
return res.status(200).send(returnObj);
};
5 changes: 4 additions & 1 deletion express-api/src/controllers/parcels/parcelsController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { ParcelFilter, ParcelFilterSchema } from '@/services/parcels/parcelSchem
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';

/**
Expand All @@ -25,11 +26,13 @@ export const getParcel = async (req: Request, res: Response) => {
return res.status(400).send('Parcel ID was invalid.');
}

// admin and auditors are permitted to see any parcel
const permittedRoles = [Roles.ADMIN, Roles.AUDITOR];
const kcUser = req.user as unknown as SSOUser;
const parcel = await parcelServices.getParcelById(parcelId);
if (!parcel) {
return res.status(404).send('Parcel matching this internal ID not found.');
} else if (!(await checkUserAgencyPermission(kcUser, [parcel.AgencyId]))) {
} else if (!(await checkUserAgencyPermission(kcUser, [parcel.AgencyId], permittedRoles))) {
return res.status(403).send('You are not authorized to view this parcel.');
}
return res.status(200).send(parcel);
Expand Down
16 changes: 14 additions & 2 deletions express-api/src/controllers/projects/projectsController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import userServices from '@/services/users/usersServices';
import { isAdmin, isAuditor, checkUserAgencyPermission } from '@/utilities/authorizationChecks';
import { DeepPartial } from 'typeorm';
import { Project } from '@/typeorm/Entities/Project';
import { Roles } from '@/constants/roles';
import notificationServices from '@/services/notifications/notificationServices';

/**
Expand All @@ -22,6 +23,8 @@ export const getDisposalProject = async (req: Request, res: Response) => {
* "bearerAuth" : []
* }]
*/
// admins are permitted to view any project
const permittedRoles = [Roles.ADMIN];
const user = req.user as SSOUser;
const projectId = Number(req.params.projectId);
if (isNaN(projectId)) {
Expand All @@ -32,7 +35,7 @@ export const getDisposalProject = async (req: Request, res: Response) => {
return res.status(404).send('Project matching this internal ID not found.');
}

if (!(await checkUserAgencyPermission(user, [project.AgencyId]))) {
if (!(await checkUserAgencyPermission(user, [project.AgencyId], permittedRoles))) {
return res.status(403).send('You are not authorized to view this project.');
}

Expand Down Expand Up @@ -98,6 +101,11 @@ export const deleteDisposalProject = async (req: Request, res: Response) => {
* "bearerAuth" : []
* }]
*/
// Only admins can delete projects
if (!isAdmin(req.user)) {
return res.status(403).send('Projects can only be deleted by Administrator role.');
}

const projectId = Number(req.params.projectId);
if (isNaN(projectId)) {
return res.status(400).send('Invalid Project ID');
Expand All @@ -123,6 +131,10 @@ export const deleteDisposalProject = async (req: Request, res: Response) => {
* @returns {Response} A 200 status with the new project.
*/
export const addDisposalProject = async (req: Request, res: Response) => {
// Auditors can no add projects
if (isAuditor(req.user)) {
return res.status(403).send('Projects can not be added by user with Auditor role.');
}
// Extract project data from request body
// Extract projectData and propertyIds from the request body
const {
Expand Down Expand Up @@ -157,7 +169,7 @@ export const getProjects = async (req: Request, res: Response) => {
}
const filterResult = filter.data;
const kcUser = req.user as unknown as SSOUser;
if (!(isAdmin(kcUser) || isAuditor(kcUser))) {
if (!isAdmin(kcUser)) {
// get array of user's agencies
const usersAgencies = await userServices.getAgencies(kcUser.preferred_username);
filterResult.agencyId = usersAgencies;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { AppDataSource } from '@/appDataSource';
import { ImportResult } from '@/typeorm/Entities/ImportResult';
import { readFile } from 'xlsx';
import logger from '@/utilities/winstonLogger';
import { Roles } from '@/constants/roles';

/**
* @description Search for a single keyword across multiple different fields in both parcels and buildings.
Expand Down Expand Up @@ -81,10 +82,16 @@ export const getPropertiesForMap = async (req: Request, res: Response) => {

// Controlling for agency search visibility
const kcUser = req.user;
// admin and suditors can see any property
const permittedRoles = [Roles.ADMIN, Roles.AUDITOR];
// Admins and auditors see all, otherwise...
if (!(isAdmin(kcUser) || isAuditor(kcUser))) {
const requestedAgencies = filterResult.AgencyIds;
const userHasAgencies = await checkUserAgencyPermission(kcUser, requestedAgencies);
const userHasAgencies = await checkUserAgencyPermission(
kcUser,
requestedAgencies,
permittedRoles,
);
// If not agencies were requested or if the user doesn't have those requested agencies
if (!requestedAgencies || !userHasAgencies) {
// Then only show that user's agencies instead.
Expand Down
2 changes: 1 addition & 1 deletion express-api/src/controllers/reports/reportsController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export const submitErrorReport = async (req: Request, res: Response) => {
});
const config = getConfig();
const email: IEmail = {
to: [config.errorReport.toEmail],
to: [config.contact.toEmail],
cc: [req.user.email],
from: '[email protected]', // Made up for this purpose.
bodyType: EmailBody.Html,
Expand Down
6 changes: 3 additions & 3 deletions express-api/src/controllers/users/usersController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { SSOUser } from '@bcgov/citz-imb-sso-express';
import { decodeJWT } from '@/utilities/decodeJWT';
import { UserFiltering, UserFilteringSchema } from '@/controllers/users/usersSchema';
import { z } from 'zod';
import { isAdmin, isAuditor } from '@/utilities/authorizationChecks';
import { isAdmin } from '@/utilities/authorizationChecks';
import notificationServices from '@/services/notifications/notificationServices';
import getConfig from '@/constants/config';
import logger from '@/utilities/winstonLogger';
Expand All @@ -23,7 +23,7 @@ const filterUsersByAgencies = async (req: Request, res: Response, ssoUser: SSOUs
const filterResult = filter.data;

let users;
if (isAdmin(ssoUser) || isAuditor(ssoUser)) {
if (isAdmin(ssoUser)) {
users = await userServices.getUsers(filterResult as UserFiltering);
} else {
// Get agencies associated with the requesting user
Expand Down Expand Up @@ -204,7 +204,7 @@ export const getUserById = async (req: Request, res: Response) => {
const user = await userServices.getUserById(uuid.data);

if (user) {
if (!isAdmin(ssoUser) && !isAuditor(ssoUser)) {
if (!isAdmin(ssoUser)) {
// check if user has the correct agencies
const usersAgencies = await userServices.hasAgencies(ssoUser.preferred_username, [
user.AgencyId,
Expand Down
Loading

0 comments on commit 51f588d

Please sign in to comment.