diff --git a/spec/LogsRouter.spec.js b/spec/LogsRouter.spec.js index 36fab149..04eac524 100644 --- a/spec/LogsRouter.spec.js +++ b/spec/LogsRouter.spec.js @@ -62,4 +62,93 @@ describe('LogsRouter', () => { done(); }); }); + + const headers = { + 'X-Parse-Application-Id': 'test', + 'X-Parse-REST-API-Key': 'rest', + 'X-Parse-Master-Key': 'test' + }; + + /** + * Verifies simple passwords in GET login requests with special characters are scrubbed from the verbose log + */ + it('does scrub simple passwords on GET login', done => { + reconfigureServer({ + verbose: true + }).then(function() { + request.get({ + headers: headers, + url: 'http://localhost:8378/1/login?username=test&password=simplepass.com' + }, () => { + request.get({ + url: 'http://localhost:8378/1/scriptlog?size=4&level=verbose', + json: true, + headers: headers + }, (error, response, body) => { + expect(response.statusCode).toEqual(200); + // 4th entry is our actual GET request + expect(body[3].url).toEqual('/1/login?username=test&password=********'); + expect(body[3].message).toEqual('REQUEST for [GET] /1/login?username=test&password=********: {}'); + done(); + }); + }); + }); + }); + + /** + * Verifies complex passwords in GET login requests with special characters are scrubbed from the verbose log + */ + it('does scrub complex passwords on GET login', done => { + reconfigureServer({ + verbose: true + }).then(function() { + request.get({ + headers: headers, + // using urlencoded password, 'simple @,/?:&=+$#pass.com' + url: 'http://localhost:8378/1/login?username=test&password=simple%20%40%2C%2F%3F%3A%26%3D%2B%24%23pass.com' + }, () => { + request.get({ + url: 'http://localhost:8378/1/scriptlog?size=4&level=verbose', + json: true, + headers: headers + }, (error, response, body) => { + expect(response.statusCode).toEqual(200); + // 4th entry is our actual GET request + expect(body[3].url).toEqual('/1/login?username=test&password=********'); + expect(body[3].message).toEqual('REQUEST for [GET] /1/login?username=test&password=********: {}'); + done(); + }); + }); + }); + }); + + /** + * Verifies fields in POST login requests are NOT present in the verbose log + */ + it('does not have password field in POST login', done => { + reconfigureServer({ + verbose: true + }).then(function() { + request.post({ + headers: headers, + url: 'http://localhost:8378/1/login', + data: { + username: 'test', + password: 'simplepass.com' + } + }, () => { + request.get({ + url: 'http://localhost:8378/1/scriptlog?size=4&level=verbose', + json: true, + headers: headers + }, (error, response, body) => { + expect(response.statusCode).toEqual(200); + // 4th entry is our actual GET request + expect(body[3].url).toEqual('/1/login'); + expect(body[3].message).toEqual('REQUEST for [POST] /1/login: {}'); + done(); + }); + }); + }); + }); }); diff --git a/src/Controllers/LoggerController.js b/src/Controllers/LoggerController.js index 5e5da683..cc8f5318 100644 --- a/src/Controllers/LoggerController.js +++ b/src/Controllers/LoggerController.js @@ -46,12 +46,25 @@ export class LoggerController extends AdaptableController { } maskSensitiveUrl(urlString) { - const password = url.parse(urlString, true).query.password; + const urlObj = url.parse(urlString, true); + const query = urlObj.query; + let sanitizedQuery = '?'; - if (password) { - urlString = urlString.replace('password=' + password, 'password=********'); + for(const key in query) { + if(key !== 'password') { + // normal value + sanitizedQuery += key + '=' + query[key] + '&'; + } else { + // password value, redact it + sanitizedQuery += key + '=' + '********' + '&'; + } } - return urlString; + + // trim last character, ? or & + sanitizedQuery = sanitizedQuery.slice(0, -1); + + // return original path name with sanitized params attached + return urlObj.pathname + sanitizedQuery; } maskSensitive(argArray) {