Skip to content

Commit

Permalink
Merge branch 'master' into renovate/hmcts-properties-volume-0.x
Browse files Browse the repository at this point in the history
  • Loading branch information
olusegz07 authored Feb 4, 2025
2 parents 8cb1f88 + da319c1 commit bb2c11f
Show file tree
Hide file tree
Showing 58 changed files with 896 additions and 212 deletions.
4 changes: 3 additions & 1 deletion api/auth/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
SERVICES_IDAM_ISS_URL,
SERVICES_IDAM_LOGIN_URL,
SERVICES_IDAM_OAUTH_CALLBACK_URL,
SERVICES_IDAM_SERVICE_OVERRIDE,
SERVICE_S2S_PATH,
SESSION_SECRET,
SYSTEM_USER_NAME,
Expand Down Expand Up @@ -113,7 +114,8 @@ export const getXuiNodeMiddleware = () => {
sessionKey: 'xui-webapp',
tokenEndpointAuthMethod: 'client_secret_post',
tokenURL: tokenUrl,
useRoutes: true
useRoutes: true,
serviceOverride: getConfigValue(SERVICES_IDAM_SERVICE_OVERRIDE)
};

const baseStoreOptions = {
Expand Down
1 change: 1 addition & 0 deletions api/configuration/references.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ export const SERVICES_IDAM_CLIENT_ID = 'services.idam.idamClientID';
export const SERVICES_IDAM_LOGIN_URL = 'services.idam.idamLoginUrl';
export const SERVICES_IDAM_ISS_URL = 'services.idam.iss';
export const SERVICES_IDAM_OAUTH_CALLBACK_URL = 'services.idam.oauthCallbackUrl';
export const SERVICES_IDAM_SERVICE_OVERRIDE = 'services.idam.serviceOverride';

export const SERVICE_S2S_PATH = 'services.s2s';
export const SERVICES_TERMS_AND_CONDITIONS_URL = 'services.termsAndConditions';
Expand Down
4 changes: 2 additions & 2 deletions api/globalSearch/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import 'mocha';
import * as sinon from 'sinon';
import * as sinonChai from 'sinon-chai';
import { mockReq, mockRes } from 'sinon-express-mock';
import { GlobalSearchService } from '../interfaces/globalSearchService';
import { HMCTSServiceDetails } from '../interfaces/hmctsServiceDetails';
import { http } from '../lib/http';
import * as globalSearchServices from './index';
import { RefDataHMCTSService } from '../ref-data/models/ref-data-hmcts-service.model';
Expand Down Expand Up @@ -137,7 +137,7 @@ describe('Jurisdiction', () => {
]
}
];
const serviceList: GlobalSearchService[] = [
const serviceList: HMCTSServiceDetails[] = [

{ serviceId: 'IA', serviceName: 'Immigration and Asylum Appeals' },
{ serviceId: 'Civil', serviceName: 'Civil' },
Expand Down
15 changes: 5 additions & 10 deletions api/globalSearch/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@ import {
SERVICES_CCD_DATA_STORE_API_PATH,
SERVICES_LOCATION_REF_API_URL
} from '../configuration/references';
import { GlobalSearchService } from '../interfaces/globalSearchService';
import { HMCTSServiceDetails } from '../interfaces/hmctsServiceDetails';
import { EnhancedRequest } from '../lib/models';
import { RefDataHMCTSService } from '../ref-data/models/ref-data-hmcts-service.model';
import { http } from '../lib/http';
import { setHeaders } from '../lib/proxy';
import { toTitleCase } from '../utils';

/**
* Get global search services
Expand Down Expand Up @@ -60,17 +61,17 @@ export async function getSearchResults(req: EnhancedRequest, res: Response, next
* @param jurisdictions
* @returns
*/
export function generateServices(refDataHMCTS: RefDataHMCTSService[]): GlobalSearchService[] {
export function generateServices(refDataHMCTS: RefDataHMCTSService[]): HMCTSServiceDetails[] {
// Retrieve global search services id from config
const globalSearchServiceIds = getConfigValue(GLOBAL_SEARCH_SERVICES);
const globalSearchServiceIdsArray = globalSearchServiceIds.split(',');

// Generate global search services
const globalSearchServices: GlobalSearchService[] = [];
const globalSearchServices: HMCTSServiceDetails[] = [];
globalSearchServiceIdsArray.forEach((serviceId) => {
// search for the service name based on the globalSearchServiceId
const jurisdiction = refDataHMCTS?.length > 0 ? refDataHMCTS.filter((x) => x.ccd_service_name?.toLowerCase() === serviceId.toLowerCase()) : null;
if (jurisdiction) {
if (jurisdiction?.length > 0) {
// handle Civil service which has different service_short_description
if (jurisdiction.length > 1) {
globalSearchServices.push({ serviceId: jurisdiction[0].ccd_service_name, serviceName: toTitleCase(jurisdiction[0].ccd_service_name) });
Expand All @@ -85,9 +86,3 @@ export function generateServices(refDataHMCTS: RefDataHMCTSService[]): GlobalSea
// Return generated global search services
return globalSearchServices;
}

function toTitleCase(serviceName: string): string {
return serviceName.replace(/([a-zA-Z])([a-zA-Z]*)/g, (match, firstLetter, rest) => {
return firstLetter.toUpperCase() + rest.toLowerCase();
});
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export interface GlobalSearchService {
export interface HMCTSServiceDetails {
serviceId: string;
serviceName: string;
}
6 changes: 4 additions & 2 deletions api/roleAccess/utils.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,15 @@ describe('roleAssignment.utils', () => {
}
};
mockSubstantiveRoles.push(mockDangerousRole);
expect(substantiveRolesValid(mockSubstantiveRoles)).to.equal(false);
// todo: add assertion back in following updated list of valid characters
// expect(substantiveRolesValid(mockSubstantiveRoles)).to.equal(false);
mockSubstantiveRoles.pop();
expect(substantiveRolesValid(mockSubstantiveRoles)).to.equal(true);
mockDangerousRole.roleJurisdiction.values.push('<script>');
mockDangerousRole.roleName = 'test role';
mockSubstantiveRoles.push(mockDangerousRole);
expect(substantiveRolesValid(mockSubstantiveRoles)).to.equal(false);
// todo: add assertion back in following updated list of valid characters
// expect(substantiveRolesValid(mockSubstantiveRoles)).to.equal(false);
});
});
});
3 changes: 2 additions & 1 deletion api/user/utils.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,8 @@ describe('user.utils', () => {
expect(userDetailsValid(mockUserDetails)).to.equal(true);
});

it('should set user details to invalid if it has dangerous characters', () => {
// todo: unignore and fix following updated list of valid characters
xit('should set user details to invalid if it has dangerous characters', () => {
mockUserDetails.email = '<script>alert("hello")</script>';
expect(userDetailsValid(mockUserDetails)).to.equal(false);
mockUserDetails.email = '[email protected]';
Expand Down
20 changes: 17 additions & 3 deletions api/utils.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { expect } from 'chai';
import { allContainOnlySafeCharacters, hasUnacceptableCharacters, urlHasUnacceptableCharacters } from './utils';

import { allContainOnlySafeCharacters, hasUnacceptableCharacters, toTitleCase, urlHasUnacceptableCharacters } from './utils';

const validRoleList = [
'[PETSOLICITOR]',
Expand Down Expand Up @@ -203,7 +204,19 @@ const validRoleList = [
];

describe('api utils', () => {
describe('hasUnacceptableCharacters', () => {
describe('toTitleCase', () => {
it('should correctly set a service name to title case', () => {
expect(toTitleCase('')).to.equal('');
expect(toTitleCase('ia')).to.equal('Ia');
expect(toTitleCase('IA')).to.equal('Ia');
expect(toTitleCase('iA')).to.equal('Ia');
expect(toTitleCase(' iA ')).to.equal(' Ia ');
expect(toTitleCase('4 cIvIL 14')).to.equal('4 Civil 14');
});
});

// todo: unignore and fix following updated list of valid characters
xdescribe('hasUnacceptableCharacters', () => {
it('should match strings that contain dangerous characters', () => {
expect(hasUnacceptableCharacters(null)).to.equal(false);
const testString = '<script>alert("hello")</script>';
Expand Down Expand Up @@ -231,7 +244,8 @@ describe('api utils', () => {
});
});

describe('allContainOnlySafeCharacters', () => {
// todo: unignore and fix following updated list of valid characters
xdescribe('allContainOnlySafeCharacters', () => {
it('should match lists with strings that do not contain dangerous characters', () => {
expect(allContainOnlySafeCharacters([])).to.equal(true);
const testList = ['ab', 'cd=ef', 'gh.jk'];
Expand Down
14 changes: 13 additions & 1 deletion api/utils.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@

export function toTitleCase(serviceName: string): string {
return serviceName.replace(/([a-zA-Z])([a-zA-Z]*)/g, (match, firstLetter, rest) => {
return firstLetter.toUpperCase() + rest.toLowerCase();
});
}

export function allContainOnlySafeCharacters(values: string[]) {
for (const value of values) {
if (hasUnacceptableCharacters(value)) {
Expand All @@ -8,12 +15,17 @@ export function allContainOnlySafeCharacters(values: string[]) {
return true;
}

// eslint-disable-next-line @typescript-eslint/no-unused-vars
export function hasUnacceptableCharacters(value: string): boolean {
// ensures no characters that could be used for security attacks are present
// the below only checks for the characters in the string
// return /^[^%<>^$//]+$/.test(value);
// substring approach below preferred
return !(/^(?!.*\/\*|.*\/\/|.*;|.*&|.*\?|.*<|.*\^|.*>).+$/.test(value));

// todo: verify which characters are never possible and shouldn't be permitted
// original implementation below includes '&' for example, which is valid in organisation names
// return !(/^(?!.*\/\*|.*\/\/|.*;|.*&|.*\?|.*<|.*\^|.*>).+$/.test(value));
return false;
}

// url may have special characters but should guard against other dangerous characters
Expand Down
49 changes: 48 additions & 1 deletion api/waSupportedJurisdictions/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,29 @@
import { NextFunction, Response, Router } from 'express';
import { getConfigValue } from '../configuration';
import { WA_SUPPORTED_JURISDICTIONS } from '../configuration/references';
import { SERVICES_LOCATION_REF_API_URL, WA_SUPPORTED_JURISDICTIONS } from '../configuration/references';
import { HMCTSServiceDetails } from '../interfaces/hmctsServiceDetails';
import { http } from '../lib/http';
import { EnhancedRequest } from '../lib/models';
import { setHeaders } from '../lib/proxy';
import { RefDataHMCTSService } from '../ref-data/models/ref-data-hmcts-service.model';
import { toTitleCase } from '../utils';

const baseLocationRefUrl = getConfigValue(SERVICES_LOCATION_REF_API_URL);

// Used for all work - could be used for all in future?
export async function getDetailedWASupportedJurisdictions(req: EnhancedRequest, res: Response, next: NextFunction): Promise<any> {
const apiPath = `${baseLocationRefUrl}/refdata/location/orgServices`;
try {
const response = await http.get(`${apiPath}`, { headers: setHeaders(req) });

const services: any = generateServices(response.data);
res.send(services).status(200);
} catch (error) {
next(error);
}
}

// used for angular layer (just list of services per config)
export async function getWASupportedJurisdictions(req: EnhancedRequest, res: Response, next: NextFunction): Promise<any> {
try {
const jurisdictions = getConfigValue(WA_SUPPORTED_JURISDICTIONS);
Expand All @@ -13,6 +34,7 @@ export async function getWASupportedJurisdictions(req: EnhancedRequest, res: Res
}
}

// Only used within node layer
export function getWASupportedJurisdictionsList(): any {
try {
const jurisdictions = getConfigValue(WA_SUPPORTED_JURISDICTIONS);
Expand All @@ -23,8 +45,33 @@ export function getWASupportedJurisdictionsList(): any {
}
}

// Note: separate from global search services currently
// This is to allow more customisation - general generate services within utils
export function generateServices(refDataHMCTS: RefDataHMCTSService[]): HMCTSServiceDetails[] {
const jurisdictions = getConfigValue(WA_SUPPORTED_JURISDICTIONS);
const jurisdictionsArray = jurisdictions.split(',');
const waSupportedServices: HMCTSServiceDetails[] = [];
jurisdictionsArray.forEach((serviceId) => {
// search for the service name based on the supported jursdiction
const jurisdiction = refDataHMCTS?.length > 0 ? refDataHMCTS.filter((x) => x.ccd_service_name?.toLowerCase() === serviceId.toLowerCase()) : null;
if (jurisdiction?.length > 0) {
// handle Civil service which has different service_short_description
if (jurisdiction.length > 1) {
waSupportedServices.push({ serviceId: jurisdiction[0].ccd_service_name, serviceName: toTitleCase(jurisdiction[0].ccd_service_name) });
} else {
waSupportedServices.push({ serviceId: jurisdiction[0].ccd_service_name, serviceName: jurisdiction[0].service_short_description });
}
} else {
waSupportedServices.push({ serviceId, serviceName: serviceId });
}
});

return waSupportedServices;
}

export const router = Router({ mergeParams: true });

router.get('/detail', getDetailedWASupportedJurisdictions);
router.get('/get', getWASupportedJurisdictions);

export default router;
1 change: 1 addition & 0 deletions charts/xui-webapp/values.preview.template.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ nodejs:
PREVIEW_DEPLOYMENT_ID: exui-preview-deployment-${CHANGE_ID}
FEATURE_JRD_E_LINKS_V2_ENABLED: true
FEATURE_LAU_SPECIFIC_CHALLENGED_ENABLED: true
SERVICES_IDAM_SERVICE_OVERRIDE: false
keyVaults:
rpx:
secrets:
Expand Down
4 changes: 4 additions & 0 deletions config/custom-environment-variables.json
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,10 @@
"idamClientID": "SERVICES_IDAM_CLIENT_ID",
"idamLoginUrl": "SERVICES_IDAM_LOGIN_URL",
"oauthCallbackUrl": "SERVICES_IDAM_OAUTH_CALLBACK_URL",
"serviceOverride": {
"__name": "SERVICES_IDAM_SERVICE_OVERRIDE",
"__format": "json"
},
"iss": "SERVICES_IDAM_ISS_URL"
},
"location_api": "SERVICES_LOCATION_API",
Expand Down
1 change: 1 addition & 0 deletions config/default.json
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@
"idamLoginUrl": "https://hmcts-access.service.gov.uk",
"indexUrl": "/",
"oauthCallbackUrl": "oauth2/callback",
"serviceOverride": false,
"iss": "https://forgerock-am.service.core-compute-idam-prod.internal:8443/openam/oauth2/hmcts"
},
"location_api": "http://rd-location-ref-api-prod.service.core-compute-prod.internal",
Expand Down
4 changes: 2 additions & 2 deletions infrastructure/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ resource "azurerm_key_vault_secret" "redis6_connection_string" {
}

module "redis6-cache" {
source = "[email protected]:hmcts/cnp-module-redis?ref=master"
source = "[email protected]:hmcts/cnp-module-redis?ref=4.x"
product = "${var.shared_product_name}-mc-redis6"
name = "${var.product}-${var.component}-${var.env}"
location = var.location
Expand All @@ -50,7 +50,7 @@ module "redis6-cache" {
}

module "application_insights" {
source = "[email protected]:hmcts/terraform-module-application-insights?ref=main"
source = "[email protected]:hmcts/terraform-module-application-insights?ref=4.x"

env = var.env
product = var.product
Expand Down
2 changes: 1 addition & 1 deletion infrastructure/provider.tf
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ terraform {
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "~> 3.117.0"
version = "~> 4.0"
}
}

Expand Down
10 changes: 5 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -87,14 +87,14 @@
"@cucumber/cucumber": "^10.8.0",
"@edium/fsm": "^2.1.2",
"@faker-js/faker": "^9.2.0",
"@hmcts/ccd-case-ui-toolkit": "7.1.27",
"@hmcts/ccd-case-ui-toolkit": "7.1.32",
"@hmcts/ccpay-web-component": "6.2.1",
"@hmcts/frontend": "0.0.50-alpha",
"@hmcts/media-viewer": "4.0.9",
"@hmcts/nodejs-healthcheck": "1.7.0",
"@hmcts/media-viewer": "4.0.10",
"@hmcts/nodejs-healthcheck": "1.8.5",
"@hmcts/properties-volume": "^0.0.13",
"@hmcts/rpx-xui-common-lib": "2.0.30",
"@hmcts/rpx-xui-node-lib": "2.30.1",
"@hmcts/rpx-xui-common-lib": "2.0.33",
"@hmcts/rpx-xui-node-lib": "2.30.2",
"@microsoft/applicationinsights-web": "^3.1.0",
"@ng-idle/core": "^14.0.0",
"@ng-idle/keepalive": "^14.0.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ <h2 class="hmcts-footer__heading">{{help.heading | rpxTranslate}}</h2>
<hr class="govuk-section-break govuk-section-break--l">
<ul class="hmcts-footer__list hmcts-footer__list--inline">
<li class="hmcts-footer__list-item" *ngFor="let item of navigation.items">
<a class="govuk-footer__link" [routerLink]="item.href" [target]="item.target">{{item.text | rpxTranslate}}</a>
<a class="govuk-footer__link" [routerLink]="item.href" [target]="item.target"><span class="visuallyhidden">{{'(Opens in a new window)' | rpxTranslate}}</span>{{item.text | rpxTranslate}}</a>
</li>
<li> <ng-container *xuilibFeatureToggle="'welsh-language'">
<a href="javascript:void(0)" (click)="toggleLanguage(currentLang == 'cy' ? 'en' : currentLang == 'en' ? 'cy' : 'en')" class="language govuk-footer__link">{{currentLang == 'cy' ? 'English' : currentLang == 'en' ? 'Cymraeg' : 'English' }}</a>
Expand Down
4 changes: 4 additions & 0 deletions src/app/models/hmcts-service-details.model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export interface HMCTSServiceDetails {
serviceId: string;
serviceName: string;
}
1 change: 1 addition & 0 deletions src/app/models/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export * from './app-title.model';
export * from './configuration.model';
export * from './hmcts-service-details.model';
export * from './error-message.model';
export * from './nav-item.model';
export * from './TermsAndCondition';
Expand Down
22 changes: 19 additions & 3 deletions src/cases/components/case-alert/alert.component.html
Original file line number Diff line number Diff line change
@@ -1,8 +1,24 @@
<div class="exui-alert" *ngIf="message?.length>0">
<cut-alert [type]='level' >
<div class="exui-alert" *ngIf="successMessage?.length>0">
<cut-alert [type]='"success"' >
<i role="presentation" class="icon icon-tick" aria-hidden="false"></i>
<div class="alert-message">
{{message}}
{{successMessage}}
</div>
</cut-alert>
</div>
<div class="exui-alert" *ngIf="errorMessage?.length>0">
<cut-alert [type]='"error"' >
<i role="presentation" class="icon icon-tick" aria-hidden="false"></i>
<div class="alert-message">
{{errorMessage}}
</div>
</cut-alert>
</div>
<div class="exui-alert" *ngIf="warningMessage?.length>0">
<cut-alert [type]='"warning"' >
<i role="presentation" class="icon icon-tick" aria-hidden="false"></i>
<div class="alert-message">
{{warningMessage}}
</div>
</cut-alert>
</div>
Expand Down
Loading

0 comments on commit bb2c11f

Please sign in to comment.