Skip to content

Commit

Permalink
Merge branch 'release/orca' into feat/CE-1067
Browse files Browse the repository at this point in the history
  • Loading branch information
barrfalk authored Sep 25, 2024
2 parents 859c22b + c05bd2d commit 02caa88
Show file tree
Hide file tree
Showing 30 changed files with 4,721 additions and 407 deletions.
25 changes: 20 additions & 5 deletions backend/src/auth/jwtrole.guard.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
import { ExecutionContext, Injectable, CanActivate, UnauthorizedException, Logger } from "@nestjs/common";
import {
ExecutionContext,
Injectable,
CanActivate,
UnauthorizedException,
Logger,
ForbiddenException,
} from "@nestjs/common";
import { Reflector } from "@nestjs/core";
import { AuthGuard } from "@nestjs/passport";
import { Role } from "src/enum/role.enum";
import { Role } from "../enum/role.enum";
import { ROLES_KEY } from "./decorators/roles.decorator";
import { IS_PUBLIC_KEY } from "./decorators/public.decorator";

Expand Down Expand Up @@ -46,6 +53,17 @@ export class JwtRoleGuard extends AuthGuard("jwt") implements CanActivate {
} else {
this.logger.debug("User authorization verified");
}
const userRoles: string[] = user.client_roles;
// Check if the user has the readonly role
const hasReadOnlyRole = userRoles.includes(Role.READ_ONLY);

// If the user has readonly role, allow only GET requests
if (hasReadOnlyRole) {
if (request.method !== "GET") {
this.logger.debug(`User with readonly role attempted ${request.method} method`);
throw new ForbiddenException("Access denied: Read-only users cannot perform this action");
}
}

// if there aren't any required roles, don't allow the user to access any api. Unless the API is marked as public, at least one role is required.
if (!requiredRoles) {
Expand All @@ -57,9 +75,6 @@ export class JwtRoleGuard extends AuthGuard("jwt") implements CanActivate {
this.logger.debug(`Endpoint ${request.originalUrl} is properly guarded.`);
}

// roles that the user has
const userRoles: string[] = user.client_roles;

this.logger.debug(`User Roles: ${userRoles}`);

// does the user have a required role?
Expand Down
16 changes: 16 additions & 0 deletions backend/src/middleware/maps/automapper-dto-to-entity-maps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,10 @@ export const mapComplaintDtoToComplaint = (mapper: Mapper) => {
return null; // This will be handled in the service
}),
),
forMember(
(dest) => dest.is_privacy_requested,
mapFrom((src) => src.isPrivacyRequested),
),
);
};

Expand Down Expand Up @@ -333,6 +337,10 @@ export const mapWildlifeComplaintDtoToHwcrComplaint = (mapper: Mapper) => {
return record;
}),
),
forMember(
(dest) => dest.complaint_identifier.is_privacy_requested,
mapFrom((src) => src.isPrivacyRequested),
),
);
};

Expand Down Expand Up @@ -490,6 +498,10 @@ export const mapAllegationComplaintDtoToAllegationComplaint = (mapper: Mapper) =
(dest) => dest.suspect_witnesss_dtl_text,
mapFrom((src) => src.violationDetails),
),
forMember(
(dest) => dest.complaint_identifier.is_privacy_requested,
mapFrom((src) => src.isPrivacyRequested),
),
);
};

Expand Down Expand Up @@ -593,6 +605,10 @@ export const mapGirComplaintDtoToGirComplaint = (mapper: Mapper) => {
(dest) => dest.gir_complaint_guid,
mapFrom((src) => src.girId),
),
forMember(
(dest) => dest.complaint_identifier.is_privacy_requested,
mapFrom((src) => src.isPrivacyRequested),
),
);
};

Expand Down
28 changes: 28 additions & 0 deletions backend/src/middleware/maps/automapper-entity-to-dto-maps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,10 @@ export const complaintToComplaintDtoMap = (mapper: Mapper) => {
return null;
}),
),
forMember(
(destination) => destination.isPrivacyRequested,
mapFrom((source) => source.is_privacy_requested),
),
);
};

Expand Down Expand Up @@ -702,6 +706,10 @@ export const applyWildlifeComplaintMap = (mapper: Mapper) => {
return null;
}),
),
forMember(
(destination) => destination.isPrivacyRequested,
mapFrom((source) => source.complaint_identifier.is_privacy_requested),
),
);
};

Expand Down Expand Up @@ -921,6 +929,10 @@ export const applyAllegationComplaintMap = (mapper: Mapper) => {
return null;
}),
),
forMember(
(destination) => destination.isPrivacyRequested,
mapFrom((source) => source.complaint_identifier.is_privacy_requested),
),
);
};
export const applyGeneralInfomationComplaintMap = (mapper: Mapper) => {
Expand Down Expand Up @@ -1120,6 +1132,10 @@ export const applyGeneralInfomationComplaintMap = (mapper: Mapper) => {
return complaint_method_received_code?.complaint_method_received_code || null;
}),
),
forMember(
(destination) => destination.isPrivacyRequested,
mapFrom((source) => source.complaint_identifier.is_privacy_requested),
),
);
};

Expand Down Expand Up @@ -1667,6 +1683,18 @@ export const mapAllegationReport = (mapper: Mapper, tz: string = "America/Vancou
return "";
}),
),
forMember(
(destination) => destination.privacyRequested,
mapFrom((source) => {
if (source.complaint_identifier.is_privacy_requested === "Y") {
return "Yes";
} else if (source.complaint_identifier.is_privacy_requested === "N") {
return "No";
} else {
return null;
}
}),
),

//--
forMember(
Expand Down
6 changes: 6 additions & 0 deletions backend/src/middleware/maps/dto-to-table-map.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,12 @@ export const mapComplaintDtoToComplaintTable = (mapper: Mapper) => {
};
}),
),
forMember(
(dest) => dest.is_privacy_requested,
mapFrom((src) => {
return src.isPrivacyRequested;
}),
),
);
};

Expand Down
1 change: 1 addition & 0 deletions backend/src/types/models/complaints/complaint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,5 @@ export interface ComplaintDto {
delegates: Array<DelegateDto>;
webeocId: string;
complaintMethodReceivedCode: string;
isPrivacyRequested: string;
}
7 changes: 7 additions & 0 deletions backend/src/types/models/complaints/update-complaint.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,4 +165,11 @@ export class UpdateComplaintDto {
description: "Method in which the complaint was created",
})
comp_mthd_recv_cd_agcy_cd_xref: CompMthdRecvCdAgcyCdXref;

@ApiProperty({
example: "true",
description:
"flag to represent that the caller has asked for special care when handling their personal information",
})
is_privacy_requested: string;
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export interface ComplaintReportData {
complaintMethodReceivedCode: string;

//-- caller information
privacyRequested: string;
name: string;
phone1: string;
phone2: string;
Expand Down
5 changes: 5 additions & 0 deletions backend/src/v1/complaint/complaint.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1563,6 +1563,11 @@ export class ComplaintService {
"AllegationReportData",
);

//-- this is a bit of a hack to hide and show the privacy requested row
if (data.privacyRequested) {
data = { ...data, privacy: [{ value: data.privacyRequested }] };
}

break;
}
}
Expand Down
14 changes: 10 additions & 4 deletions backend/src/v1/complaint/entities/complaint.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,10 +138,6 @@ export class Complaint {
})
location_geometry_point: Point;

/*
@Column()
location_geometry_point: string;
*/
@ApiProperty({
example: "Near Golden",
description: "The summary text for the location of the complaint",
Expand Down Expand Up @@ -205,6 +201,14 @@ export class Complaint {
@Column()
update_utc_timestamp: Date;

@ApiProperty({
example: "true",
description:
"flag to represent that the caller has asked for special care when handling their personal information",
})
@Column()
is_privacy_requested: string;

constructor(
detail_text?: string,
caller_name?: string,
Expand Down Expand Up @@ -232,6 +236,7 @@ export class Complaint {
person_complaint_xref?: PersonComplaintXref[],
webeoc_identifier?: string,
comp_mthd_recv_cd_agcy_cd_xref?: CompMthdRecvCdAgcyCdXref,
is_privacy_requested?: string,
) {
this.detail_text = detail_text;
this.caller_name = caller_name;
Expand Down Expand Up @@ -259,5 +264,6 @@ export class Complaint {
this.person_complaint_xref = person_complaint_xref;
this.webeoc_identifier = webeoc_identifier;
this.comp_mthd_recv_cd_agcy_cd_xref = comp_mthd_recv_cd_agcy_cd_xref;
this.is_privacy_requested = is_privacy_requested;
}
}
Binary file modified backend/templates/complaint/CDOGS-ERS-COMPLAINT-TEMPLATE-v1.docx
Binary file not shown.
2 changes: 1 addition & 1 deletion database/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# RedHat UBI 8 with nodejs 14
FROM postgis/postgis:15-master@sha256:9e03ed5407b7c0e81e0e2191bdb1168334339fd02cbe6fc67ef0a6eb88c0b2c4
FROM postgis/postgis:15-master@sha256:c1288c91f8671521bb7c7d3f08a3e2b537d48cdd1725c78bdf8fa34ac81225f4

# Enable pgcrypto extension on startup
RUN sed -i '/EXISTS postgis_tiger_geocoder;*/a CREATE EXTENSION IF NOT EXISTS pgcrypto;' \
Expand Down
2 changes: 1 addition & 1 deletion database/postgis/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# RedHat UBI 8 with nodejs 14
FROM postgis/postgis:15-master@sha256:9e03ed5407b7c0e81e0e2191bdb1168334339fd02cbe6fc67ef0a6eb88c0b2c4
FROM postgis/postgis:15-master@sha256:c1288c91f8671521bb7c7d3f08a3e2b537d48cdd1725c78bdf8fa34ac81225f4

# Enable pgcrypto extension on startup
RUN sed -i '/EXISTS postgis_tiger_geocoder;*/a CREATE EXTENSION IF NOT EXISTS pgcrypto;' \
Expand Down
15 changes: 15 additions & 0 deletions frontend/src/app/common/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { AUTH_TOKEN } from "../service/user-service";
import { ApiRequestParameters } from "../types/app/api-request-parameters";
import { toggleLoading, toggleNotification } from "../store/reducers/app";
import { store } from "../../app/store/store";
import { ToggleError } from "./toast";

const STATUS_CODES = {
Ok: 200,
Expand Down Expand Up @@ -47,6 +48,20 @@ axios.interceptors.response.use(
},
);

axios.interceptors.response.use(
(response) => {
// Successful response, just return the data
return response;
},
(error: AxiosError) => {
const { response } = error;

if (response && response.status === STATUS_CODES.Forbiden) {
ToggleError("User is not authorized to perform this action");
}
},
);

const { KEYCLOAK_URL } = config;

export const generateApiParameters = <T = {}>(
Expand Down
65 changes: 35 additions & 30 deletions frontend/src/app/components/containers/admin/user-management.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -198,15 +198,17 @@ export const UserManagement: FC = () => {
const officerId = officer?.value ? officer.value : "";
const officeId = office?.value ? office.value : "";
dispatch(assignOfficerToOffice(officerId, officeId));
res = await updateTeamRole(selectedUserIdir, officerGuid, selectedAgency?.value, null, [
{ name: Roles.COS_OFFICER },
]);
const mapRoles = selectedRoles?.map((role) => {
return { name: role.value };
});
res = await updateTeamRole(selectedUserIdir, officerGuid, selectedAgency?.value, null, mapRoles);
break;
}
}
if (res && res.team && res.roles) {
ToggleSuccess("Success");
} else {
debugger;
ToggleError("Unable to update");
}
}
Expand Down Expand Up @@ -324,39 +326,42 @@ export const UserManagement: FC = () => {
/>
</div>
<br />
</>
)}
{selectedAgency?.value === "COS" && (
<>
<div>
Select Role
<ValidationMultiSelect
className="comp-details-input"
options={CEEB_ROLE_OPTIONS}
placeholder="Select"
id="roles-select-id"
Select Office
<CompSelect
id="species-select-id"
classNamePrefix="comp-select"
onChange={handleRoleChange}
errMsg={""}
values={selectedRoles}
onChange={(evt) => handleOfficeChange(evt)}
classNames={{
menu: () => "top-layer-select",
}}
options={officeAssignments}
placeholder="Select"
enableValidation={true}
value={office}
errorMessage={officeError}
/>
</div>
<br />
</>
)}
{selectedAgency?.value === "COS" && (
<div>
Select Office
<CompSelect
id="species-select-id"
classNamePrefix="comp-select"
onChange={(evt) => handleOfficeChange(evt)}
classNames={{
menu: () => "top-layer-select",
}}
options={officeAssignments}
placeholder="Select"
enableValidation={true}
value={office}
errorMessage={officeError}
/>
</div>
)}
<div>
Select Role
<ValidationMultiSelect
className="comp-details-input"
options={CEEB_ROLE_OPTIONS}
placeholder="Select"
id="roles-select-id"
classNamePrefix="comp-select"
onChange={handleRoleChange}
errMsg={""}
values={selectedRoles}
/>
</div>
<br />
<div>
<Button
Expand Down
Loading

0 comments on commit 02caa88

Please sign in to comment.