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

BE Policy integration - part 1 #1764

Open
wants to merge 39 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
4ad04f3
Adding to cache
krishnan-aot Oct 23, 2024
54e2b0a
Merge remote-tracking branch 'origin/main' into policy-int
krishnan-aot Oct 29, 2024
dc3610d
Replace policy service with http call to policy
krishnan-aot Oct 29, 2024
6d98b41
Rough structure for validation
krishnan-aot Oct 29, 2024
1a735b6
Merge branch 'main' into policy-int
krishnan-aot Nov 4, 2024
8f0bcc9
Merge remote-tracking branch 'origin/main' into policy-int
krishnan-aot Nov 5, 2024
e30d6fd
With correct import
krishnan-aot Nov 5, 2024
0130db6
Upgraded version of policy
krishnan-aot Nov 7, 2024
bac330e
Merge remote-tracking branch 'origin/main' into policy-int
krishnan-aot Nov 7, 2024
d514567
Merge remote-tracking branch 'origin/main' into policy-int
krishnan-aot Nov 8, 2024
3e065f4
Merge remote-tracking branch 'origin/main' into policy-int
krishnan-aot Nov 14, 2024
67819da
Merge remote-tracking branch 'origin/main' into policy-int
krishnan-aot Nov 20, 2024
d1d3bc7
Merge remote-tracking branch 'origin/main' into policy-int
krishnan-aot Nov 25, 2024
111dd5d
Merge remote-tracking branch 'origin/main' into policy-int
krishnan-aot Dec 9, 2024
2c9cb3a
Merge remote-tracking branch 'origin/main' into policy-int
krishnan-aot Dec 9, 2024
abbc60c
Add back orbc policy engine in deps
krishnan-aot Dec 9, 2024
f35e6a2
Merge remote-tracking branch 'origin/main' into policy-int
krishnan-aot Dec 9, 2024
0f3fe92
Merge remote-tracking branch 'origin/main' into policy-int
krishnan-aot Dec 10, 2024
d3fc428
Merge remote-tracking branch 'origin/main' into policy-int
krishnan-aot Dec 11, 2024
0320b51
Merge remote-tracking branch 'origin/main' into policy-int
krishnan-aot Dec 19, 2024
fb273ee
Merge remote-tracking branch 'origin/main' into policy-int
krishnan-aot Jan 3, 2025
1c6984a
Merge remote-tracking branch 'origin/main' into policy-int
krishnan-aot Jan 7, 2025
6327eff
Merge remote-tracking branch 'origin/main' into policy-int
krishnan-aot Jan 10, 2025
177dd1f
Merge remote-tracking branch 'origin/main' into policy-int
krishnan-aot Jan 13, 2025
629b4c1
Upgrade policy engine version
krishnan-aot Jan 13, 2025
7d4f368
Templated code for policy engine validation
krishnan-aot Jan 13, 2025
9e41b44
Merge remote-tracking branch 'origin/main' into policy-int
krishnan-aot Jan 13, 2025
19f459b
Merge remote-tracking branch 'origin/main' into policy-int
krishnan-aot Jan 15, 2025
8b42fc6
Remove cache from start up to on demand
krishnan-aot Jan 15, 2025
de167f0
Merge remote-tracking branch 'origin/main' into policy-int
krishnan-aot Jan 21, 2025
c3fcce8
Merge remote-tracking branch 'origin/main' into policy-int
krishnan-aot Jan 23, 2025
1020b1f
Adding a common service
krishnan-aot Jan 23, 2025
a7ff5c4
Correcting query in sample data
krishnan-aot Jan 23, 2025
9393a21
Some corrections
krishnan-aot Jan 23, 2025
88f0b44
Remove httpservice from common service
krishnan-aot Jan 23, 2025
a2350c1
Correcting import type
krishnan-aot Jan 23, 2025
e854eb8
Correct import again
krishnan-aot Jan 23, 2025
fbb6d9a
Trying again
krishnan-aot Jan 23, 2025
cfaf8e4
Merge remote-tracking branch 'origin/main' into policy-int
krishnan-aot Jan 28, 2025
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
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,8 @@ INSERT INTO
VALUES
(
'3',
'POLICY-CONFIG',
'ENABLED',
'VALIDATE-WITH-POLICY-ENGINE',
'DISABLED',
NULL,
N'dbo',
GETUTCDATE(),
Expand Down Expand Up @@ -301,7 +301,6 @@ VALUES
N'dbo',
GETUTCDATE()
);

SET
IDENTITY_INSERT [dbo].[ORBC_FEATURE_FLAG] OFF
GO
90 changes: 90 additions & 0 deletions vehicles/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions vehicles/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@
"nest-winston": "^1.10.0",
"nestjs-cls": "^4.5.0",
"nestjs-typeorm-paginate": "^4.0.4",
"onroute-policy-engine": "^1.5.0",
"passport": "^0.7.0",
"passport-jwt": "^4.0.1",
"response-time": "^2.3.3",
Expand Down
2 changes: 2 additions & 0 deletions vehicles/src/common/enum/cache-key.enum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,6 @@ export enum CacheKey {
FEATURE_FLAG_TYPE = 'FEATURE_FLAG_TYPE',
PERMIT_APPLICATION_ORIGIN = 'PERMIT_APPLICATION_ORIGIN',
PERMIT_APPROVAL_SOURCE = 'PERMIT_APPROVAL_SOURCE',
POLICY_CONFIGURATIONS = 'POLICY_CONFIGURATIONS',
ORBC_SERVICE_ACCOUNT_ACCESS_TOKEN = 'ORBC_SERVICE_ACCOUNT_ACCESS_TOKEN',
}
1 change: 1 addition & 0 deletions vehicles/src/common/enum/gov-common-services.enum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ export enum GovCommonServices {
COMMON_HOSTED_EMAIL_SERVICE = 'CHES',
COMMON_DOCUMENT_GENERATION_SERVICE = 'CDOGS',
CREDIT_ACCOUNT_SERVICE = 'CREDIT_ACCOUNT',
ORBC_SERVICE_ACCOUNT = 'SA',
}
7 changes: 6 additions & 1 deletion vehicles/src/common/helper/gov-common-services.helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,12 @@ function getTokenCredentials(govCommonServices: GovCommonServices): {
username = process.env.CFS_CREDIT_ACCOUNT_CLIENT_ID;
password = process.env.CFS_CREDIT_ACCOUNT_CLIENT_SECRET;
break;

case GovCommonServices.ORBC_SERVICE_ACCOUNT:
tokenCacheKey = CacheKey.ORBC_SERVICE_ACCOUNT_ACCESS_TOKEN;
tokenUrl = process.env.ORBC_SERVICE_ACCOUNT_TOKEN_URL;
username = process.env.ORBC_SERVICE_ACCOUNT_CLIENT_ID;
password = process.env.ORBC_SERVICE_ACCOUNT_CLIENT_SECRET;
break;
default:
break;
}
Expand Down
37 changes: 36 additions & 1 deletion vehicles/src/common/helper/policy-engine.helper.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import { HttpService } from '@nestjs/axios';
import { AxiosResponse } from 'axios';
import { Cache } from 'cache-manager';
import { Permit } from 'src/modules/permit-application-payment/permit/entities/permit.entity';
import { PolicyApplication } from '../interface/policy-application.interface';
import { ReadPolicyConfigDto } from '../../modules/policy/dto/response/read-policy-config.dto';
import { GovCommonServices } from '../enum/gov-common-services.enum';
import { PermitData } from '../interface/permit.template.interface';
import { PolicyApplication } from '../interface/policy-application.interface';
import { getAccessToken } from './gov-common-services.helper';

export const convertToPolicyApplication = (
application: Permit,
Expand All @@ -10,3 +16,32 @@ export const convertToPolicyApplication = (
permitData: JSON.parse(application.permitData.permitData) as PermitData,
};
};

export const getActivePolicyDefinitions = async (
httpService: HttpService,
cacheManager: Cache,
) => {
const token = await getAccessToken(
GovCommonServices.ORBC_SERVICE_ACCOUNT,
httpService,
cacheManager,
);
const response = await httpService.axiosRef.get<
// eslint-disable-next-line @typescript-eslint/no-explicit-any
any,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
AxiosResponse<Response, any>,
Request
>(process.env.ORBC_POLICY_URL + '/policy-configurations', {
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
},
});
const policyConfigArray =
(await response.data.json()) as ReadPolicyConfigDto[];
if (!policyConfigArray.length) {
return null;
}
return policyConfigArray[0];
};
2 changes: 1 addition & 1 deletion vehicles/src/common/logger/logger.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ const correlationIdFormat = winston.format((info) => {
const cls = ClsServiceManager.getClsService();
const correlationId = cls.getId();
if (correlationId) {
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions, @typescript-eslint/no-base-to-string
info.message = `[${correlationId}] ${info.message}`;
}
return info;
Expand Down
66 changes: 65 additions & 1 deletion vehicles/src/modules/common/common.service.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,22 @@
import { Injectable } from '@nestjs/common';
import {
Inject,
Injectable,
InternalServerErrorException,
} from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Country } from './entities/country.entity';
import { Province } from './entities/province.entity';
import { LogAsyncMethodExecution } from '../../common/decorator/log-async-method-execution.decorator';
import { Permit } from '../permit-application-payment/permit/entities/permit.entity';
import { Policy, ValidationResults } from 'onroute-policy-engine';
import { ReadPolicyConfigDto } from '../policy/dto/response/read-policy-config.dto';
import { CACHE_MANAGER } from '@nestjs/cache-manager';
import { Cache } from 'cache-manager';
import { HttpService } from '@nestjs/axios';
import { CacheKey } from '../../common/enum/cache-key.enum';
import { getActivePolicyDefinitions } from '../../common/helper/policy-engine.helper';
import { addToCache } from '../../common/helper/cache.helper';

@Injectable()
export class CommonService {
Expand All @@ -12,6 +25,9 @@ export class CommonService {
private countryRepository: Repository<Country>,
@InjectRepository(Province)
private provinceRepository: Repository<Province>,
private readonly httpService: HttpService,
@Inject(CACHE_MANAGER)
private readonly cacheManager: Cache,
) {}

@LogAsyncMethodExecution()
Expand All @@ -37,4 +53,52 @@ export class CommonService {
async findAllProvinces(): Promise<Province[]> {
return await this.provinceRepository.find({});
}

/**
* Validates a permit application using the policy engine.
*
* This method retrieves the active policy definitions to validate
* the given permit application and returns the validation results.
*
* The policy definitions are fetched from the cache if available;
* otherwise, it retrieves them from the policy service and stores
* them in the cache for future requests.
* If the policy engine is unavailable, an InternalServerErrorException
* is thrown.
*
* @param {Permit} permitApplication - The permit application to be validated.
* @returns {Promise<ValidationResults>} - The results of the validation process.
* @throws {InternalServerErrorException} - If the policy engine is not available.
*/
@LogAsyncMethodExecution()
async validateWithPolicyEngine(
permitApplication: Permit,
): Promise<ValidationResults> {
const policyDefinitions: string = await this.cacheManager.get(
CacheKey.POLICY_CONFIGURATIONS,
);
if (!policyDefinitions) {
const policyDefinitions = await getActivePolicyDefinitions(
this.httpService,
this.cacheManager,
);
if (!policyDefinitions) {
throw new InternalServerErrorException(
'Policy engine is not available',
);
}
await addToCache(
this.cacheManager,
CacheKey.POLICY_CONFIGURATIONS,
JSON.stringify(policyDefinitions),
);
}
const activePolicyDefintion = JSON.parse(
policyDefinitions,
) as ReadPolicyConfigDto;
const policy = new Policy(activePolicyDefintion.policy);
const validationResults: ValidationResults =
await policy.validate(permitApplication);
return validationResults;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ import { PermitData } from 'src/common/interface/permit.template.interface';
import { isValidLoa } from 'src/common/helper/validate-loa.helper';
import { PermitHistoryDto } from '../permit/dto/response/permit-history.dto';
import { SpecialAuthService } from 'src/modules/special-auth/special-auth.service';
import { CommonService } from '../../common/common.service';

@Injectable()
export class PaymentService {
Expand All @@ -91,6 +92,7 @@ export class PaymentService {
@InjectMapper() private readonly classMapper: Mapper,
@Inject(CACHE_MANAGER)
private readonly cacheManager: Cache,
private readonly commonService: CommonService,
) {}

private generateHashExpiry = (currDate?: Date) => {
Expand Down Expand Up @@ -282,6 +284,10 @@ export class PaymentService {
createTransactionDto.transactionTypeId == TransactionType.REFUND ||
createTransactionDto.paymentMethodTypeCode ===
PaymentMethodTypeEnum.NO_PAYMENT;
const isPolicyEngineEnabled =
featureFlags?.['VALIDATE-WITH-POLICY-ENGINE'] &&
(featureFlags['VALIDATE-WITH-POLICY-ENGINE'] as FeatureFlagValue) ===
FeatureFlagValue.ENABLED;

// If the user is a staff user,
// transacation is NOT a refund or no payment and STAFF-CAN-PAY is disabled,
Expand Down Expand Up @@ -364,6 +370,15 @@ export class PaymentService {
if (permitData.loas) {
await isValidLoa(application, queryRunner, this.classMapper);
}
if (isPolicyEngineEnabled) {
const validationResults =
await this.commonService.validateWithPolicyEngine(application);
if (validationResults?.violations?.length > 0) {
throw new BadRequestException(
'Application data does not meet policy engine requirements.',
);
}
}
}
const totalTransactionAmount = await this.validateApplicationAndPayment(
createTransactionDto,
Expand Down
Loading
Loading