Update anonymize logic. Update tests.
MODIFY
src/storage/mongodb/LoggingStorage.ts
src/storage/mongodb/LoggingStorage.ts
@@ -80,7 +80,7 @@ export default class LoggingStorage {
// Insert
if (global.database) {
await global.database.getCollection<Log>(tenantID, 'logs').insertOne(logMDB);
- return logMDB._id;
+ return logMDB._id.toHexString();
}
}

MODIFY
src/utils/Constants.ts
src/utils/Constants.ts
@@ -92,7 +92,7 @@ export default class Constants {
public static readonly MAX_GPS_DISTANCE_METERS = 40000000; // Earth
public static readonly SENSITIVE_DATA = Object.freeze([
- 'name', 'firstName', 'password', 'repeatPassword', 'captcha', 'email', 'coordinates', 'latitude', 'longitude',
+ 'firstName', 'name', 'repeatPassword', 'password', 'captcha', 'email', 'coordinates', 'latitude', 'longitude',
'Authorization', 'client_id', 'client_secret', 'refresh_token', 'localToken', 'token',
]);

MODIFY
src/utils/Logging.ts
src/utils/Logging.ts
@@ -679,11 +679,11 @@ export default class Logging {
log.host = Utils.getHostname();
// Process
log.process = log.process ? log.process : (cluster.isWorker ? 'worker ' + cluster.worker.id.toString() : 'master');
- // Anonymize message
- log.detailedMessages = Utils.cloneObject(log.detailedMessages);
- Logging.anonymizeSensitiveData(log.detailedMessages);
// Check
if (log.detailedMessages) {
+ // Anonymize message
+ log.detailedMessages = Utils.cloneObject(log.detailedMessages);
+ log.detailedMessages = Logging.anonymizeSensitiveData(log.detailedMessages);
// Array?
if (!Array.isArray(log.detailedMessages)) {
log.detailedMessages = [log.detailedMessages];
@@ -713,51 +713,50 @@ export default class Logging {
return logID;
}
- private static anonymizeSensitiveData(message: any): void {
+ private static anonymizeSensitiveData(message: any): any {
if (!message || typeof message === 'number' || Utils.isBoolean(message) || typeof message === 'function') {
- // eslint-disable-next-line no-useless-return
- return;
- } else if (typeof message === 'string') {
- for (const sensitiveData of Constants.SENSITIVE_DATA) {
- // Anonymize
- message.replace(new RegExp(sensitiveData, 'gi'), Constants.ANONYMIZED_VALUE);
- // Should be
- // message=message.replace(new RegExp(sensitiveData, 'gi'), Constants.ANONYMIZED_VALUE); //in order to anonymize the FULL string
- // OR we can use the approach for QUERY STRING from lines 744-755
- }
- } else if (Array.isArray(message)) {
- for (const item of message) {
- Logging.anonymizeSensitiveData(item);
- }
- } else if (typeof message === 'object') {
- for (const key of Object.keys(message)) {
- // String?
- if (typeof message[key] === 'string') {
- for (const sensitiveData of Constants.SENSITIVE_DATA) {
- if (key.toLocaleLowerCase() === sensitiveData.toLocaleLowerCase()) {
- // Anonymize
- message[key] = Constants.ANONYMIZED_VALUE;
- }
- }
- // Check query string
- const dataParts: string[] = message[key].split('&');
+ return message;
+ } else if (typeof message === 'string') { // If the message is a string
+ // Check if it is a query string
+ const dataParts: string[] = message.split('&');
if (dataParts.length > 1) {
for (let i = 0; i < dataParts.length; i++) {
const dataPart = dataParts[i];
for (const sensitiveData of Constants.SENSITIVE_DATA) {
if (dataPart.toLowerCase().startsWith(sensitiveData.toLocaleLowerCase())) {
- // Anonymize
+ // Anonymize each query string part
dataParts[i] = dataPart.substring(0, sensitiveData.length + 1) + Constants.ANONYMIZED_VALUE;
}
}
}
- message[key] = dataParts.join('&');
+ message = dataParts.join('&');
+ return message;
}
- } else {
- Logging.anonymizeSensitiveData(message[key]);
+ // If it is a string, but not a query string
+ for (const sensitiveData of Constants.SENSITIVE_DATA) {
+ if (new RegExp(sensitiveData + '[:=](.*)', 'gi').test(message)) { // If it contains sensitive data and it matches the RegExp, Anonymize
+ message = message.substring(0,sensitiveData.length + 1) + Constants.ANONYMIZED_VALUE;
+ return message;
}
}
- } else {
+ return message;
+ } else if (Array.isArray(message)) { // If the message is an array, apply the anonymizeSensitiveData function for each item
+ const anonymizedMessage = [];
+ for (const item of message) {
+ anonymizedMessage.push(Logging.anonymizeSensitiveData(item));
+ }
+ return anonymizedMessage;
+ } else if (typeof message === 'object') { // If the message is an object
+ for (const key of Object.keys(message)) {
+ if (typeof message[key] === 'string' && Constants.SENSITIVE_DATA.filter((sensitiveData) => key.toLocaleLowerCase() === sensitiveData.toLocaleLowerCase()).length > 0) {
+ // If the key indicates sensitive data and the value is a string, Anonymize the value
+ message[key] = Constants.ANONYMIZED_VALUE;
+ } else { // Otherwise, apply the anonymizeSensitiveData function
+ message[key] = Logging.anonymizeSensitiveData(message[key]);
+ }
+ }
+ return message;
+ }
// Log
Logging.logError({
tenantID: Constants.DEFAULT_TENANT,
@@ -769,7 +768,6 @@ export default class Logging {
detailedMessages: { message: message }
});
}
- }
// Console Log
private static _consoleLog(log): void {

MODIFY
test/api/SecurityTest.ts
test/api/SecurityTest.ts
@@ -19,39 +19,43 @@ const testData: TestData = new TestData();
let initialTenant: Tenant;
function checkSensitiveDataIsObfuscated(message:any): void {
- if (typeof message === 'string') { // In case of a string - check that all the string is anonymized
- expect(message).to.equal(Constants.ANONYMIZED_VALUE);
- } else if (Array.isArray(message)) { // In case of an array, check every item
- for (const item of message) {
- checkSensitiveDataIsObfuscated(item);
- }
- } else if (typeof message === 'object') { // In case of object, check every field
- for (const key of Object.keys(message)) {
- if (typeof message[key] === 'string') { // If the field value is a string and the key is part of sensitive data, check if the value is anonymized
- for (const sensitiveData of Constants.SENSITIVE_DATA) {
- if (key.toLocaleLowerCase() === sensitiveData.toLocaleLowerCase()) {
- expect(message[key]).to.equal(Constants.ANONYMIZED_VALUE);
- }
- }
-
- const dataParts: string[] = message[key].split('&'); // If the value is a query string, check if its parts contain sensitive data and if it is anonymized
+ if (typeof message === 'string') { // If the message is a string
+ const dataParts: string[] = message.split('&');
+ // Check if it is a query string
if (dataParts.length > 1) {
for (let i = 0; i < dataParts.length; i++) {
const dataPart = dataParts[i];
for (const sensitiveData of Constants.SENSITIVE_DATA) {
- if (dataPart.toLowerCase().startsWith(sensitiveData.toLocaleLowerCase())) {
- expect(dataPart.substring(sensitiveData.length + 1, dataPart.length)).to.equal(Constants.ANONYMIZED_VALUE);
- }
+ if (dataPart.toLowerCase().startsWith(sensitiveData.toLocaleLowerCase())) { // Check each query string part anonymized
+ expect(dataPart).to.equal(dataPart.substring(0, sensitiveData.length + 1) + Constants.ANONYMIZED_VALUE);
+ break;
}
}
}
} else {
- checkSensitiveDataIsObfuscated(message[key]); // If the field is something else, we call the method again
+ // If it is a string, but not a query string
+ for (const sensitiveData of Constants.SENSITIVE_DATA) {
+ if (new RegExp(sensitiveData + '[:=](.*)', 'gi').test(message)) { // If it contains sensitive data and it matches the RegExp, check data anonymized
+ expect(message).to.equal(message.toString().substring(0, sensitiveData.length + 1) + Constants.ANONYMIZED_VALUE);
+ break;
+ }
+ }
+ }
+ } else if (Array.isArray(message)) { // In case of an array, check every item anonymized
+ for (const item of message) {
+ checkSensitiveDataIsObfuscated(item);
+ }
+ } else if (typeof message === 'object') { // In case of object
+ for (const key of Object.keys(message)) {
+ if (typeof message[key] === 'string' && Constants.SENSITIVE_DATA.filter((sensitiveData) => key.toLocaleLowerCase() === sensitiveData.toLocaleLowerCase()).length > 0) {
+ // If the key indicates sensitive data and the value is a string, check value anonymized
+ expect(message[key]).to.equal(Constants.ANONYMIZED_VALUE);
+ } else { // Otherwise, apply the whole check to the value
+ checkSensitiveDataIsObfuscated(message[key]);
}
}
}
}
-
describe('Security tests', function() {
this.timeout(30000);
@@ -59,7 +63,6 @@ describe('Security tests', function() {
before(async function() {
global.database = new MongoDBStorage(config.get('storage'));
await global.database.start();
-
// Init values
testData.superCentralService = new CentralServerService(null, { email: config.get('superadmin.username'), password: config.get('superadmin.password') });
testData.centralService = new CentralServerService(ContextDefinition.TENANT_CONTEXTS.TENANT_WITH_ALL_COMPONENTS, { email: config.get('admin.username'), password: config.get('admin.password') });
@@ -79,7 +82,7 @@ describe('Security tests', function() {
});
describe('Success cases (tenant utall)', () => {
- it('Check that sensitive data string is anonymized', async () => { // Will fail
+ it('Check that sensitive data string (containing "=") is anonymized', async () => { // Will fail
const logId:string = await Logging.logDebug({
source: 'test',
tenantID: testData.credentials.tenantId,
@@ -87,12 +90,25 @@ describe('Security tests', function() {
message: 'Just a test',
module: 'test',
method: 'test',
- detailedMessages: 'password=MyDummyPass'
+ detailedMessages: 'repeatPassword=MyDummyPass'
});
-
const read = await testData.centralService.logsApi.readById(logId);
expect(read.status).to.equal(200);
+ checkSensitiveDataIsObfuscated(JSON.parse(read.data.detailedMessages));
+ });
+ it('Check that sensitive data string (containing ":") is anonymized', async () => { // Will fail
+ const logId:string = await Logging.logDebug({
+ source: 'test',
+ tenantID: testData.credentials.tenantId,
+ action: ServerAction.HTTP_REQUEST,
+ message: 'Just a test',
+ module: 'test',
+ method: 'test',
+ detailedMessages: 'firstName:MyName'
+ });
+ const read = await testData.centralService.logsApi.readById(logId);
+ expect(read.status).to.equal(200);
checkSensitiveDataIsObfuscated(JSON.parse(read.data.detailedMessages));
});
it('Check that sensitive data is anonymized in object with string fields', async () => {
@@ -124,7 +140,6 @@ describe('Security tests', function() {
const read = await testData.centralService.logsApi.readById(logId);
expect(read.status).to.equal(200);
-
checkSensitiveDataIsObfuscated(JSON.parse(read.data.detailedMessages));
});
it('Check that sensitive data is anonymized in object with query string fields', async () => { // Passes because query strings are treated correctly in object fields
@@ -144,7 +159,6 @@ describe('Security tests', function() {
const read = await testData.centralService.logsApi.readById(logId);
expect(read.status).to.equal(200);
-
checkSensitiveDataIsObfuscated(JSON.parse(read.data.detailedMessages));
});
it('Check that sensitive data is anonymized in array with strings', async () => { // Will fail
@@ -158,18 +172,23 @@ describe('Security tests', function() {
detailedMessages: [
'name=test',
'firstName=test',
- 'password=test&repeatPassword=test',
+ 'password=test',
+ 'repeatPassword=test',
'captcha=test',
'email=test',
'coordinates=test',
- 'latitude=test&longitude=test',
- 'Authorization=test&client_id=test&client_secret=test&refresh_token=test&localToken=test&token=test'
+ 'latitude=test',
+ 'longitude=test',
+ 'Authorization=test',
+ 'client_id=test',
+ 'client_secret=test',
+ 'refresh_token=test',
+ 'localToken=test',
+ 'token=test'
]
});
-
const read = await testData.centralService.logsApi.readById(logId);
expect(read.status).to.equal(200);
-
checkSensitiveDataIsObfuscated(JSON.parse(read.data.detailedMessages));
});
});

Update anonymize logic. Update tests.