Skip to content

Commit

Permalink
Merge branch 'release/0.6.9' into CE-1176
Browse files Browse the repository at this point in the history
  • Loading branch information
jon-funk authored Dec 9, 2024
2 parents 085cf5c + 174ef7f commit 23ec702
Show file tree
Hide file tree
Showing 50 changed files with 839 additions and 324 deletions.
10 changes: 9 additions & 1 deletion backend/src/auth/decorators/token.decorator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,13 @@ import { createParamDecorator, ExecutionContext } from "@nestjs/common";
export const Token = createParamDecorator((_data: unknown, ctx: ExecutionContext) => {
//Extract token from request
const request = ctx.switchToHttp().getRequest();
return request.token;
if (request.token) {
return request.token;
}
// If the token is not directly accessible in the request object, take it from the headers
let token = request.headers.authorization;
if (token && token.indexOf("Bearer ") === 0) {
token = token.substring(7);
}
return token;
});
8 changes: 8 additions & 0 deletions backend/src/auth/decorators/user.decorator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { createParamDecorator, ExecutionContext } from "@nestjs/common";

// Returns the user off of the request object.
// Sample usage: foo(@User() user) {...}
export const User = createParamDecorator((data: unknown, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest();
return request.user;
});
7 changes: 5 additions & 2 deletions backend/src/auth/jwtrole.guard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ import { Role } from "../enum/role.enum";
import { ROLES_KEY } from "./decorators/roles.decorator";
import { IS_PUBLIC_KEY } from "./decorators/public.decorator";

// A list of routes that are exceptions to the READ_ONLY role only being allowed to make get requests
const READ_ONLY_EXCEPTIONS = ["/api/v1/officer/request-coms-access/:officer_guid"];

@Injectable()
/**
* An API guard used to authorize controller methods. This guard checks for othe @Roles decorator, and compares it against the role_names of the authenticated user's jwt.
Expand Down Expand Up @@ -57,8 +60,8 @@ export class JwtRoleGuard extends AuthGuard("jwt") implements CanActivate {
// 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 the user has readonly role, allow only GET requests unless the route is in the list of exceptions
if (hasReadOnlyRole && !READ_ONLY_EXCEPTIONS.includes(request.route.path)) {
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");
Expand Down
5 changes: 5 additions & 0 deletions backend/src/helpers/axios-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,8 @@ export const post = async (apiToken: string, url: string, data: any, headers?: a
const config = generateConfig(apiToken, headers);
return axios.post(url, data, config);
};

export const put = async (apiToken: string, url: string, data: any, headers?: any) => {
const config = generateConfig(apiToken, headers);
return axios.put(url, data, config);
};
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,6 @@ export interface ComplaintFilterParameters {
complaintMethod?: string;
actionTaken?: string;
outcomeAnimal?: string;
outcomeAnimalStartDate?: Date;
outcomeAnimalEndDate?: Date;
}
8 changes: 4 additions & 4 deletions backend/src/v1/complaint/complaint.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,15 +141,15 @@ export class ComplaintController {
@Public()
@Post("/staging/action-taken")
@UseGuards(ApiKeyGuard)
stageActionTaken(@Body() action: ActionTaken) {
this.stagingService.stageObject("ACTION-TAKEN", action);
async stageActionTaken(@Body() action: ActionTaken) {
return await this.stagingService.stageObject("ACTION-TAKEN", action);
}

@Public()
@Post("/staging/action-taken-update")
@UseGuards(ApiKeyGuard)
stageActionTakenUpdate(@Body() action: ActionTaken) {
this.stagingService.stageObject("ACTION-TAKEN-UPDATE", action);
async stageActionTakenUpdate(@Body() action: ActionTaken) {
return await this.stagingService.stageObject("ACTION-TAKEN-UPDATE", action);
}

@Public()
Expand Down
22 changes: 17 additions & 5 deletions backend/src/v1/complaint/complaint.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -817,9 +817,11 @@ export class ComplaintService {
private readonly _getComplaintsByOutcomeAnimal = async (
token: string,
outcomeAnimalCode: string,
startDate: Date | undefined,
endDate: Date | undefined,
): Promise<string[]> => {
const { data, errors } = await get(token, {
query: `{getLeadsByOutcomeAnimal (outcomeAnimalCode: "${outcomeAnimalCode}")}`,
query: `{getLeadsByOutcomeAnimal (outcomeAnimalCode: "${outcomeAnimalCode}", startDate: "${startDate}" , endDate: "${endDate}")}`,
});
if (errors) {
this.logger.error("GraphQL errors:", errors);
Expand Down Expand Up @@ -975,8 +977,13 @@ export class ComplaintService {
}

// -- filter by complaint identifiers returned by case management if outcome animal filter is present
if (agency === "COS" && filters.outcomeAnimal) {
const complaintIdentifiers = await this._getComplaintsByOutcomeAnimal(token, filters.outcomeAnimal);
if (agency === "COS" && (filters.outcomeAnimal || filters.outcomeAnimalStartDate)) {
const complaintIdentifiers = await this._getComplaintsByOutcomeAnimal(
token,
filters.outcomeAnimal,
filters.outcomeAnimalStartDate,
filters.outcomeAnimalEndDate,
);

builder.andWhere("complaint.complaint_identifier IN(:...complaint_identifiers)", {
complaint_identifiers: complaintIdentifiers,
Expand Down Expand Up @@ -1144,8 +1151,13 @@ export class ComplaintService {
}

// -- filter by complaint identifiers returned by case management if outcome animal filter is present
if (agency === "COS" && filters.outcomeAnimal) {
const complaintIdentifiers = await this._getComplaintsByOutcomeAnimal(token, filters.outcomeAnimal);
if (agency === "COS" && (filters.outcomeAnimal || filters.outcomeAnimalStartDate)) {
const complaintIdentifiers = await this._getComplaintsByOutcomeAnimal(
token,
filters.outcomeAnimal,
filters.outcomeAnimalStartDate,
filters.outcomeAnimalEndDate,
);
complaintBuilder.andWhere("complaint.complaint_identifier IN(:...complaint_identifiers)", {
complaint_identifiers: complaintIdentifiers,
});
Expand Down
7 changes: 7 additions & 0 deletions backend/src/v1/officer/entities/officer.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,13 @@ export class Officer {
@Column()
auth_user_guid: UUID;

@ApiProperty({
example: false,
description: "Indicates whether an officer has been enrolled in COMS",
})
@Column()
coms_enrolled_ind: boolean;

user_roles: string[];
@AfterLoad()
updateUserRoles() {
Expand Down
10 changes: 9 additions & 1 deletion backend/src/v1/officer/officer.controller.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Controller, Get, Post, Body, Patch, Param, Delete, UseGuards } from "@nestjs/common";
import { Controller, Get, Post, Body, Patch, Param, Delete, UseGuards, Put } from "@nestjs/common";
import { OfficerService } from "./officer.service";
import { CreateOfficerDto } from "./dto/create-officer.dto";
import { UpdateOfficerDto } from "./dto/update-officer.dto";
Expand All @@ -7,6 +7,8 @@ import { Role } from "../../enum/role.enum";
import { JwtRoleGuard } from "../../auth/jwtrole.guard";
import { ApiTags } from "@nestjs/swagger";
import { UUID } from "crypto";
import { User } from "../../auth/decorators/user.decorator";
import { Token } from "../../auth/decorators/token.decorator";

@ApiTags("officer")
@UseGuards(JwtRoleGuard)
Expand Down Expand Up @@ -65,6 +67,12 @@ export class OfficerController {
return this.officerService.update(id, updateOfficerDto);
}

@Put("/request-coms-access/:officer_guid")
@Roles(Role.CEEB, Role.COS_OFFICER, Role.READ_ONLY)
requestComsAccess(@Token() token, @Param("officer_guid") officer_guid: UUID, @User() user) {
return this.officerService.requestComsAccess(token, officer_guid, user);
}

@Delete(":id")
@Roles(Role.COS_OFFICER)
remove(@Param("id") id: string) {
Expand Down
36 changes: 36 additions & 0 deletions backend/src/v1/officer/officer.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import { PersonService } from "../person/person.service";
import { OfficeService } from "../office/office.service";
import { UUID } from "crypto";
import { CssService } from "../../external_api/css/css.service";
import { Role } from "../../enum/role.enum";
import { put } from "../../helpers/axios-api";

@Injectable()
export class OfficerService {
Expand Down Expand Up @@ -172,6 +174,40 @@ export class OfficerService {
return this.findOne(officer_guid);
}

/**
* This function requests the appropriate level of access to the storage bucket in COMS.
* If successful, the officer's record in the officer table has its `coms_enrolled_ind` indicator set to true.
* @param requestComsAccessDto An object containing the officer guid
* @returns the updated record of the officer who was granted access to COMS
*/
async requestComsAccess(token: string, officer_guid: UUID, user: any): Promise<Officer> {
try {
const currentRoles = user.client_roles;
const permissions = currentRoles.includes(Role.READ_ONLY) ? ["READ"] : ["READ", "CREATE", "UPDATE", "DELETE"];
const comsPayload = {
accessKeyId: process.env.OBJECTSTORE_ACCESS_KEY,
bucket: process.env.OBJECTSTORE_BUCKET,
bucketName: process.env.OBJECTSTORE_BUCKET_NAME,
key: process.env.OBJECTSTORE_KEY,
endpoint: process.env.OBJECTSTORE_HTTPS_URL,
secretAccessKey: process.env.OBJECTSTORE_SECRET_KEY,
permCodes: permissions,
};
const comsUrl = `${process.env.OBJECTSTORE_API_URL}/bucket`;
await put(token, comsUrl, comsPayload);
const officerRes = await this.officerRepository
.createQueryBuilder("officer")
.update()
.set({ coms_enrolled_ind: true })
.where({ officer_guid: officer_guid })
.returning("*")
.execute();
return officerRes.raw[0];
} catch (error) {
this.logger.error("An error occurred while requesting COMS access.", error);
}
}

remove(id: number) {
return `This action removes a #${id} officer`;
}
Expand Down
12 changes: 9 additions & 3 deletions charts/app/templates/secret.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@
{{- $webeocPosition := (get $secretData "webeocPosition" | b64dec | default "") }}
{{- $webeocIncident := (get $secretData "webeocIncident" | b64dec | default "") }}
{{- $webeocUrl := (get $secretData "webeocUrl" | b64dec | default "") }}
{{- $webeocComplaintHistorySeconds := (get $secretData "webeocComplaintHistorySeconds" | b64dec | default "") }}
{{- $webeocCronExpression := (get $secretData "webeocCronExpression" | b64dec | default "") }}
{{- $webeocLogPath := (get $secretData "webeocLogPath" | b64dec | default "") }}
{{- $backupDir := (get $secretData "backupDir" | b64dec | default "") }}
{{- $backupStrategy := (get $secretData "backupStrategy" | b64dec | default "") }}
{{- $numBackups := (get $secretData "numBackups" | b64dec | default "") }}
Expand All @@ -44,8 +44,11 @@
{{- $objectstoreUrl := (get $secretData "objectstoreUrl" | b64dec | default "") }}
{{- $objectstoreHttpsUrl := (get $secretData "objectstoreHttpsUrl" | b64dec | default "") }}
{{- $objectstoreBackupDirectory := (get $secretData "objectstoreBackupDirectory" | b64dec | default "") }}
{{- $objectstoreKey := (get $secretData "objectstoreKey" | b64dec | default "") }}
{{- $objectstoreBucket := (get $secretData "objectstoreBucket" | b64dec | default "") }}
{{- $objectstoreBucketName := (get $secretData "objectstoreBucketName" | b64dec | default "") }}
{{- $objectstoreSecretKey := (get $secretData "objectstoreSecretKey" | b64dec | default "") }}
{{- $objectstoreApiUrl := (get $secretData "objectstoreApiUrl" | b64dec | default "") }}
{{- $jwksUri := (get $secretData "jwksUri" | b64dec | default "") }}
{{- $jwtIssuer := (get $secretData "jwtIssuer" | b64dec | default "") }}
{{- $keycloakClientId := (get $secretData "keycloakClientId" | b64dec | default "") }}
Expand Down Expand Up @@ -99,8 +102,11 @@ data:
OBJECTSTORE_URL: {{ $objectstoreUrl | b64enc | quote }}
OBJECTSTORE_HTTPS_URL: {{ $objectstoreHttpsUrl | b64enc | quote }}
OBJECTSTORE_BACKUP_DIRECTORY: {{ $objectstoreBackupDirectory | b64enc | quote }}
OBJECTSTORE_KEY: {{ $objectstoreKey | b64enc | quote }}
OBJECTSTORE_BUCKET: {{ $objectstoreBucket | b64enc | quote }}
OBJECTSTORE_BUCKET_NAME: {{ $objectstoreBucketName | b64enc | quote }}
OBJECTSTORE_SECRET_KEY: {{ $objectstoreSecretKey | b64enc | quote }}
OBJECTSTORE_API_URL: {{ $objectstoreApiUrl | b64enc | quote }}
{{- end }}
{{- if not (lookup "v1" "Secret" .Release.Namespace (printf "%s-webeoc" .Release.Name)) }}
---
Expand All @@ -120,8 +126,8 @@ data:
WEBEOC_POSITION: {{ $webeocPosition | b64enc | quote }}
WEBEOC_INCIDENT: {{ $webeocIncident | b64enc | quote }}
WEBEOC_URL: {{ $webeocUrl | b64enc | quote }}
WEBEOC_COMPLAINT_HISTORY_SECONDS: {{ $webeocComplaintHistorySeconds | b64enc | quote }}
WEBEOC_CRON_EXPRESSION: {{ $webeocCronExpression | b64enc | quote }}
WEBEOC_LOG_PATH: {{ $webeocLogPath | b64enc | quote }}
COMPLAINTS_API_KEY: {{ $caseManagementApiKey | b64enc | quote }}
{{- end }}
{{- if not (lookup "v1" "Secret" .Release.Namespace (printf "%s-flyway" .Release.Name)) }}
Expand Down Expand Up @@ -156,4 +162,4 @@ data:
password: {{ $databasePassword | quote }}
{{- end }}

{{- end }}
{{- end }}
17 changes: 13 additions & 4 deletions charts/app/templates/webeoc/templates/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ spec:
labels:
{{- include "webeoc.labels" . | nindent 8 }}
spec:
volumes:
- name: {{ include "webeoc.fullname" . }}
persistentVolumeClaim:
claimName: {{ include "webeoc.fullname" . }}
automountServiceAccountToken: false
{{- if .Values.webeoc.podSecurityContext }}
securityContext:
Expand All @@ -33,20 +37,23 @@ spec:
securityContext:
{{- toYaml .Values.webeoc.securityContext | nindent 12 }}
{{- end }}
image: "{{.Values.global.registry}}/{{.Values.global.repository}}/webeoc:{{ .Values.global.tag | default .Chart.AppVersion }}"
image: "{{ .Values.global.registry }}/{{ .Values.global.repository }}/webeoc:{{ .Values.global.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ default "Always" .Values.webeoc.imagePullPolicy }}
envFrom:
- secretRef:
name: {{.Release.Name}}-webeoc
name: {{ .Release.Name }}-webeoc
env:
- name: LOG_LEVEL
value: info
- name: NODE_TLS_REJECT_UNAUTHORIZED
value: "0"
- name: NATS_HOST
value: nats://{{.Release.Name}}-nats:4222
value: nats://{{ .Release.Name }}-nats:4222
- name: COMPLAINTS_MANAGEMENT_API_URL
value: https://{{.Release.Name}}-frontend.apps.silver.devops.gov.bc.ca/api/v1
value: https://{{ .Release.Name }}-frontend.apps.silver.devops.gov.bc.ca/api/v1
volumeMounts:
- name: {{ include "webeoc.fullname" . }}
mountPath: /mnt/data
ports:
- name: http
containerPort: {{ .Values.webeoc.service.targetPort }}
Expand All @@ -69,9 +76,11 @@ spec:
timeoutSeconds: 5
resources: # this is optional
limits:
ephemeral-storage: "25Mi"
cpu: 80m
memory: 150Mi
requests:
ephemeral-storage: "15Mi"
cpu: 40m
memory: 75Mi
{{- with .Values.webeoc.nodeSelector }}
Expand Down
15 changes: 15 additions & 0 deletions charts/app/templates/webeoc/templates/pvc.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{{- if .Values.webeoc.enabled }}
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: {{ include "webeoc.fullname" . }}
labels:
{{- include "webeoc.labels" . | nindent 4 }}
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: "25Mi"
storageClassName: netapp-file-standard
{{- end }}
5 changes: 4 additions & 1 deletion charts/app/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ global:
webeocPosition: ~
webeocIncident: ~
webeocUrl: ~
webeocComplaintHistorySeconds: ~
webeocCronExpression: ~
webeocLogPath: ~
backupDir: ~
backupStrategy: ~
numBackups: ~
Expand All @@ -46,8 +46,11 @@ global:
objectstoreAccessKey: ~
objectstoreUrl: ~
objectstoreBackupDirectory: ~
objectstoreKey: ~
objectstoreBucket: ~
objectstoreBucketName: ~
objectstoreSecretKey: ~
objectstoreApiUrl: ~
jwksUri: ~
jwtIssuer: ~
keycloakClientId: ~
Expand Down
3 changes: 3 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -117,5 +117,8 @@ services:
volumes:
- ./webeoc:/app:z
- /app/node_modules
- ./webeoc/logs:/mnt/data # this is just for the developer webeoc logging
user: root
working_dir: "/app"


Loading

0 comments on commit 23ec702

Please sign in to comment.