Skip to content

Commit

Permalink
exui-2655-xss-defence (#1324)
Browse files Browse the repository at this point in the history
* xss defence changes

* lint error

* changed the check condition

* fix unit tests failure

* removed unnecessary checks

---------

Co-authored-by: kasi-subramaniam <[email protected]>
  • Loading branch information
RiteshHMCTS and kasi-subramaniam authored Feb 26, 2025
1 parent 687b215 commit 58bedc0
Show file tree
Hide file tree
Showing 11 changed files with 98 additions and 18 deletions.
8 changes: 6 additions & 2 deletions api/allUserList/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,18 @@ import { getRefdataUserCommonUrlUtil } from '../refdataUserCommonUrlUtil';
import { getConfigValue } from '../configuration';
import { SERVICES_RD_PROFESSIONAL_API_PATH } from '../configuration/references';
import * as log4jui from '../lib/log4jui';
import { exists, valueOrNull } from '../lib/util';
import { exists, objectContainsOnlySafeCharacters, valueOrNull } from '../lib/util';

const logger = log4jui.getLogger('user-list');

export async function handleAllUserListRoute(req: Request, res: Response) {
try {
const rdProfessionalApiPath = getConfigValue(SERVICES_RD_PROFESSIONAL_API_PATH);
const response = await req.http.get(getRefdataUserCommonUrlUtil(rdProfessionalApiPath));
const apiUrl = getRefdataUserCommonUrlUtil(rdProfessionalApiPath);
const response = await req.http.get(apiUrl);
if (!objectContainsOnlySafeCharacters(response.data)) {
return res.send('Invalid user list details').status(400);
}
logger.info('response::', response.data);
res.send(response.data);
} catch (error) {
Expand Down
8 changes: 6 additions & 2 deletions api/allUserListWithoutRoles/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,19 @@ import { Request, Response, Router } from 'express';
import { getConfigValue } from '../configuration';
import { SERVICES_RD_PROFESSIONAL_API_PATH } from '../configuration/references';
import * as log4jui from '../lib/log4jui';
import { exists, valueOrNull } from '../lib/util';
import { exists, objectContainsOnlySafeCharacters, valueOrNull } from '../lib/util';
import { getRefdataAllUserListUrl } from '../refdataAllUserListUrlUtil';

const logger = log4jui.getLogger('user-list');

export async function handleAllUserListRoute(req: Request, res: Response) {
try {
const rdProfessionalApiPath = getConfigValue(SERVICES_RD_PROFESSIONAL_API_PATH);
const response = await req.http.get(getRefdataAllUserListUrl(rdProfessionalApiPath));
const apiUrl = getRefdataAllUserListUrl(rdProfessionalApiPath);
const response = await req.http.get(apiUrl);
if (!objectContainsOnlySafeCharacters(response.data)) {
return res.send('Invalid user list details').status(400);
}
logger.info('response::', response.data);
res.send(response.data);
} catch (error) {
Expand Down
4 changes: 4 additions & 0 deletions api/caaCases/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { EnhancedRequest } from '../models/enhanced-request.interface';
import { getApiPath, getRequestBody, mapCcdCases } from './caaCases.util';
import { CaaCasesFilterType } from './enums';
import { RoleAssignmentResponse } from './models/roleAssignmentResponse';
import { objectContainsOnlySafeCharacters } from '../lib/util';

export async function handleCaaCases(req: EnhancedRequest, res: Response, next: NextFunction) {
const caseTypeId = req.query.caseTypeId as string;
Expand Down Expand Up @@ -38,6 +39,9 @@ export async function handleCaaCases(req: EnhancedRequest, res: Response, next:
const payload = getRequestBody(orgId, fromNo, size, caaCasesPageType, caaCasesFilterValue);

const response = await req.http.post(path, payload);
if (!objectContainsOnlySafeCharacters(response.data)) {
return res.send('Invalid caa case data').status(400);
}
const caaCases = mapCcdCases(caseTypeId, response.data);
res.send(caaCases);
} catch (error) {
Expand Down
9 changes: 6 additions & 3 deletions api/getUserTermsAndConditions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { getConfigValue, showFeature } from '../configuration';
import { FEATURE_TERMS_AND_CONDITIONS_ENABLED, SERVICES_TERMS_AND_CONDITIONS_API_PATH } from '../configuration/references';
import { GetUserAcceptTandCResponse } from '../interfaces/userAcceptTandCResponse';
import { application } from '../lib/config/application.config';
import { valueOrNull } from '../lib/util';
import { objectContainsOnlySafeCharacters, valueOrNull } from '../lib/util';
import { getUserTermsAndConditionsUrl } from './userTermsAndConditionsUtil';

/**
Expand All @@ -29,9 +29,12 @@ async function getUserTermsAndConditions(req: Request, res: Response) {
res.status(400).send(errReport);
}
try {
const url = getUserTermsAndConditionsUrl(getConfigValue(SERVICES_TERMS_AND_CONDITIONS_API_PATH), req.params.userId, application.idamClient);
const response = await req.http.get(url);
const apiUrl = getUserTermsAndConditionsUrl(getConfigValue(SERVICES_TERMS_AND_CONDITIONS_API_PATH), req.params.userId, application.idamClient);
const response = await req.http.get(apiUrl);
const userTandCResponse = response.data as GetUserAcceptTandCResponse;
if (!objectContainsOnlySafeCharacters(response.data)) {
return res.send('Invalid terms and condition data').status(400);
}
res.send(userTandCResponse.accepted);
} catch (error) {
// we get a 404 if the user has not agreed to Terms and conditions
Expand Down
43 changes: 43 additions & 0 deletions api/lib/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,3 +73,46 @@ export function isObject(o) {
export function isUserTandCPostSuccessful(postResponse: PostUserAcceptTandCResponse, userId: string): any {
return postResponse.userId === userId;
}

// check for the presence of dangerous code in an array of strings
export function arrayContainOnlySafeCharacters(values: string[]): boolean {
return values.every((value) =>
(value !== null && typeof value === 'object')
? objectContainsOnlySafeCharacters(value)
: !containsDangerousCode(value)
);
}

// check for the presence of dangerous code in an object
export function objectContainsOnlySafeCharacters(values: object): boolean {
for (const key in values) {
const inputValue = values[key];
if (Array.isArray(inputValue)) {
if (!arrayContainOnlySafeCharacters(inputValue)) {
return false;
}
} else if (inputValue !== null && typeof inputValue === 'object') {
if (!objectContainsOnlySafeCharacters(inputValue)) {
return false;
}
} else if (typeof inputValue === 'string' && containsDangerousCode(inputValue)) {
return false;
}
}
return true;
}

/**
* Checks if a string contains any potentially dangerous code (JavaScript, CSS, URL schemes, JSONP).
* @param input - The input string to be checked.
* @returns True if the string contains potentially dangerous code, otherwise false.
*/
export function containsDangerousCode(input: string): boolean {
// Regular expressions to detect common dangerous patterns
const jsPattern = /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>|javascript:|on\w+=|eval\(|new Function\(|document\.cookie|<\s*iframe.*?>.*?<\s*\//i;
const cssPattern = /<style\b[^<]*(?:(?!<\/style>)<[^<]*)*<\/style>|expression\(|url\(/i;
const urlSchemePattern = /data:|vbscript:/i;
const jsonPattern = /callback=|jsonp=/i;
return jsPattern.test(input) || cssPattern.test(input) || urlSchemePattern.test(input) || jsonPattern.test(input);
}

12 changes: 9 additions & 3 deletions api/organisation/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,15 @@ import { NextFunction, Request, Response, Router } from 'express';
import { getConfigValue } from '../configuration';
import { SERVICES_RD_PROFESSIONAL_API_PATH } from '../configuration/references';
import { OrganisationUser } from '../interfaces/organisationPayload';
import { objectContainsOnlySafeCharacters } from '../lib/util';

export async function handleOrganisationRoute(req: Request, res: Response, next: NextFunction) {
try {
const response = await req.http.get(
`${getConfigValue(SERVICES_RD_PROFESSIONAL_API_PATH)}/refdata/external/v2/organisations`
);
const apiUrl = `${getConfigValue(SERVICES_RD_PROFESSIONAL_API_PATH)}/refdata/external/v2/organisations`;
const response = await req.http.get(apiUrl);
if (!objectContainsOnlySafeCharacters(response.data)) {
return res.send('Invalid organisation data').status(400);
}
res.send(response.data);
} catch (error) {
next(error);
Expand All @@ -27,6 +30,9 @@ export async function handleOrganisationV1Route(req: Request, res: Response, nex
const response = await req.http.get(
`${getConfigValue(SERVICES_RD_PROFESSIONAL_API_PATH)}/refdata/external/v1/organisations`
);
if (!objectContainsOnlySafeCharacters(response.data)) {
return res.send('Invalid organisation data').status(400);
}
res.send(response.data);
} catch (error) {
next(error);
Expand Down
5 changes: 4 additions & 1 deletion api/payments/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Request, Response, Router } from 'express';
import { getConfigValue } from '../configuration';
import { SERVICES_FEE_AND_PAY_API_PATH } from '../configuration/references';
import { exists, valueOrNull } from '../lib/util';
import { exists, objectContainsOnlySafeCharacters, valueOrNull } from '../lib/util';

async function handleAddressRoute(req: Request, res: Response) {
let errReport: any;
Expand All @@ -17,6 +17,9 @@ async function handleAddressRoute(req: Request, res: Response) {
const response = await req.http.get(
`${getConfigValue(SERVICES_FEE_AND_PAY_API_PATH)}/pba-accounts/${req.params.account}/payments/`
);
if (!objectContainsOnlySafeCharacters(response.data.payments)) {
return res.send('Invalid payment data').status(400);
}
res.send(response.data.payments);
} catch (error) {
const status = exists(error, 'status') ? error.status : 500;
Expand Down
8 changes: 6 additions & 2 deletions api/termsAndConditions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,16 @@ import { getConfigValue } from '../configuration';
import { SERVICES_TERMS_AND_CONDITIONS_API_PATH } from '../configuration/references';
import { application } from '../lib/config/application.config';
import { getTermsAndConditionsUrl } from './termsAndConditionsUtil';
import { objectContainsOnlySafeCharacters } from '../lib/util';

async function getTermsAndConditions(req: Request, res: Response) {
let errReport: any;
try {
const url = getTermsAndConditionsUrl(getConfigValue(SERVICES_TERMS_AND_CONDITIONS_API_PATH), application.idamClient);
const response = await req.http.get(url);
const apiUrl = getTermsAndConditionsUrl(getConfigValue(SERVICES_TERMS_AND_CONDITIONS_API_PATH), application.idamClient);
const response = await req.http.get(apiUrl);
if (!objectContainsOnlySafeCharacters(response.data)) {
return res.send('Invalid terms and condition data').status(400);
}
res.send(response.data);
} catch (error) {
if (error.status === 404) {
Expand Down
4 changes: 4 additions & 0 deletions api/user-details/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import * as log4jui from '../lib/log4jui';
import { Request, Response, Router } from 'express';
import { SERVICES_RD_PROFESSIONAL_API_PATH } from '../configuration/references';
import { getConfigValue } from '../configuration';
import { objectContainsOnlySafeCharacters } from '../lib/util';

const logger = log4jui.getLogger('user-details');

Expand All @@ -12,6 +13,9 @@ export async function handleUserDetailsRoute(req: Request, res: Response) {
const apiUrl = getRefdataUserDetailsUrl(rdProfessionalApiPath, req.query.userId as string);
logger.info('User Details API Link: ', apiUrl);
const response = await req.http.get(apiUrl);
if (!objectContainsOnlySafeCharacters(response.data)) {
return res.send('Invalid user details').status(400);
}
res.send(response.data);
} catch (error) {
logger.error('error', error);
Expand Down
6 changes: 4 additions & 2 deletions api/user/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import * as express from 'express';
import { getConfigValue } from '../configuration';
import { SESSION_TIMEOUTS } from '../configuration/references';
import * as log4jui from '../lib/log4jui';
import { exists } from '../lib/util';
import { exists, objectContainsOnlySafeCharacters } from '../lib/util';
import { UserProfileModel } from './user';

const logger = log4jui.getLogger('auth');
Expand All @@ -13,7 +13,6 @@ router.get('/details', handleUserRoute);

function handleUserRoute(req, res) {
const { email, orgId, roles, userId } = req.session.auth;

const sessionTimeouts = getConfigValue(SESSION_TIMEOUTS);
const sessionTimeout = getUserSessionTimeout(roles, sessionTimeouts);

Expand All @@ -27,6 +26,9 @@ function handleUserRoute(req, res) {

try {
console.log(userDetails);
if (!objectContainsOnlySafeCharacters(userDetails)) {
return res.send('Invalid user data').status(400);
}
res.send(userDetails);
} catch (error) {
logger.info(error);
Expand Down
9 changes: 6 additions & 3 deletions api/userList/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Request, Response, Router } from 'express';
import { getConfigValue } from '../configuration';
import { SERVICES_RD_PROFESSIONAL_API_PATH } from '../configuration/references';
import * as log4jui from '../lib/log4jui';
import { exists, valueOrNull } from '../lib/util';
import { exists, objectContainsOnlySafeCharacters, valueOrNull } from '../lib/util';
import { getRefdataUserUrl } from '../refdataUserUrlUtil';

const logger = log4jui.getLogger('user-list');
Expand All @@ -18,9 +18,12 @@ export async function handleUserListRoute(req: Request, res: Response) {
logger.info(JSON.stringify(req.query));
logger.info('USER LIST INFO');
logger.info(getRefdataUserUrl(rdProfessionalApiPath, req.query.pageNumber as string));
const response = await req.http.get(getRefdataUserUrl(rdProfessionalApiPath, req.query.pageNumber as string));

const apiUrl = getRefdataUserUrl(rdProfessionalApiPath, req.query.pageNumber as string);
const response = await req.http.get(apiUrl);
logger.info('response:', response.data);
if (!objectContainsOnlySafeCharacters(response.data)) {
return res.send('Invalid user list details').status(400);
}
res.send(response.data);
} catch (error) {
logger.error('error', error);
Expand Down

0 comments on commit 58bedc0

Please sign in to comment.