diff --git a/changelog.txt b/changelog.txt index 6f8df3c..814cd6c 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,9 @@ +Version 0.1.13 +-------------- +Release Date: 1 May 2015 + * Scrub security sensitive data in WS-Trust exchange from log messages. + * Update the version of the jws dependency to the latest release. + Version 0.1.12 -------------- Release Date: 19 February 2015 diff --git a/lib/log.js b/lib/log.js index ef74791..ee9ba2e 100644 --- a/lib/log.js +++ b/lib/log.js @@ -138,12 +138,16 @@ Object.defineProperty(Logger.prototype, 'context', { /** * Generates a log entry * @param {Logging.LOGGING_LEVEL} level The level of this log entry - * @param {string} message A message string to log. + * @param {string|function} message A message string, or a function that returns a message string, to log. * @param {Error} [error] If this is a {@link Logging.LOGGING_LEVEL.ERROR|ERROR} level log entry then the caller * should pass an error object in this parameter. */ Logger.prototype.log = function(level, message, error) { if (level <= Logging.LogOptions.level) { + if (_.isFunction(message)) { + message = message(); + } + var correlationId = this._logContext.correlationId || ''; var formattedMessage = correlationId + ' - ' + this._componentName + ': ' + LEVEL_STRING_MAP[level] + ' ' + message; diff --git a/lib/wstrust-request.js b/lib/wstrust-request.js index 80038b1..0aad8fa 100644 --- a/lib/wstrust-request.js +++ b/lib/wstrust-request.js @@ -27,6 +27,9 @@ var Logger = require('./log').Logger; var util = require('./util'); var WSTrustResponse = require('./wstrust-response'); +var USERNAME_PLACEHOLDER = '{UsernamePlaceHolder}'; +var PASSWORD_PLACEHOLDER = '{PasswordPlaceHolder}'; + /** * Creates a new instance of WSTrustRequest * @constructor @@ -42,22 +45,6 @@ function WSTrustRequest(callContext, wstrustEndpointUrl, appliesTo) { this._appliesTo = appliesTo; } -/** - * Builds the UsernameToken XML that will carry the user creds in the RST. - * @param {string} username A username - * @param {string} password The password that corresponds to the username parameter. - * @returns {string} A string containing the UsernameToken XML - */ -WSTrustRequest.prototype._buildSoapMessageCredentials = function(username, password) { - var usernameTokenXml = - '\ - ' + username + '\ - ' + password +'\ - '; - - return usernameTokenXml; -}; - /** * Given a Date object adds the minutes parameter and returns a new Date object. * @private @@ -80,7 +67,7 @@ function _datePlusMinutes(date, minutes) { * @param {string} password The passowrd that corresponds to the username parameter. * @returns {string} A string that contains the soap security header. */ -WSTrustRequest.prototype._buildSecurityHeader = function(username, password) { +WSTrustRequest.prototype._buildSecurityHeader = function() { var timeNow = new Date(); var expireTime = _datePlusMinutes(timeNow, 10); @@ -92,13 +79,30 @@ _datePlusMinutes(timeNow, 10); \ ' + timeNowString + '\ ' + expireTimeString + '\ - ' + - this._buildSoapMessageCredentials(username, password) + - ''; + \ + \ + ' + USERNAME_PLACEHOLDER + '\ + ' + PASSWORD_PLACEHOLDER + '\ + \ + '; return securityHeaderXml; }; +/** + * Replaces the placeholders in the RST template with the actual username and password values. + * @private + * @param {string} RSTTemplate An RST with placeholders for username and password. + * @param {string} username A username + * @param {string} password The passowrd that corresponds to the username parameter. + * @returns {string} A string containing a complete RST soap message. + */ + +WSTrustRequest.prototype._populateRSTUsernamePassword = function(RSTTemplate, username, password) { + var RST = RSTTemplate.replace(USERNAME_PLACEHOLDER, username).replace(PASSWORD_PLACEHOLDER, password); + return RST; +}; + /** * Builds a WS-Trust RequestSecurityToken (RST) message using username password authentication. * @private @@ -109,7 +113,9 @@ _datePlusMinutes(timeNow, 10); WSTrustRequest.prototype._buildRST = function(username, password) { var messageID = uuid.v4(); - var RST = + // Create a template RST with placeholders for the username and password so the + // the RST can be logged without the sensitive information. + var RSTTemplate = '\ \ http://docs.oasis-open.org/ws-sx/ws-trust/200512/RST/Issue\ @@ -118,7 +124,7 @@ WSTrustRequest.prototype._buildRST = function(username, password) { http://www.w3.org/2005/08/addressing/anonymous\ \ ' + this._wstrustEndpointUrl + '\ - ' + this._buildSecurityHeader(username, password) + '\ + ' + this._buildSecurityHeader() + '\ \ \ \ @@ -133,6 +139,9 @@ WSTrustRequest.prototype._buildRST = function(username, password) { \ '; + this._log.verbose('Created RST: \n' + RSTTemplate); + + var RST = this._populateRSTUsernamePassword(RSTTemplate, username, password); return RST; }; @@ -176,7 +185,7 @@ WSTrustRequest.prototype.acquireToken = function(username, password, callback) { } ); - this._log.verbose('Sending RST to: ' + this._wstrustEndpointUrl + '\n' + RST); + this._log.verbose('Sending RST to: ' + this._wstrustEndpointUrl); request.post(this._wstrustEndpointUrl, options, util.createRequestHandler('WS-Trust RST', this._log, callback, function(response, body) { diff --git a/lib/wstrust-response.js b/lib/wstrust-response.js index d923e2a..b71dffc 100644 --- a/lib/wstrust-response.js +++ b/lib/wstrust-response.js @@ -29,6 +29,34 @@ var Logger = require('./log').Logger; var select = xmlutil.xpathSelect; var DOMParser = xmldom.DOMParser; +// A regular expression for finding the SAML Assertion in an RSTR. Used to remove the SAML +// assertion when logging the RSTR. +var assertionRegEx = /RequestedSecurityToken.*?((<.*?:Assertion.*?>).*<\/.*?Assertion>).*?/; + +/** + * Creates a log message that contains the RSTR scrubbed of the actual SAML assertion. + * @private + * @return {string} A log message. + */ +function scrubRSTRLogMessage(RSTR) { + var scrubbedRSTR = null; + + var singleLineRSTR = RSTR.replace(/(\r\n|\n|\r)/gm,''); + + var matchResult = assertionRegEx.exec(singleLineRSTR); + if (null === matchResult) { + // No Assertion was matched so just return the RSTR as is. + scrubbedRSTR = singleLineRSTR; + } else { + var samlAssertion = matchResult[1]; + var samlAssertionStartTag = matchResult[2]; + + scrubbedRSTR = singleLineRSTR.replace(samlAssertion, samlAssertionStartTag + 'ASSERTION CONTENTS REDACTED'); + } + + return 'RSTR Response: ' + scrubbedRSTR; +} + /** * Creates a new WSTrustResponse instance. * @constructor @@ -46,7 +74,7 @@ function WSTrustResponse(callContext, response) { this._tokenType = null; this._token = null; - this._log.verbose('RSTR Response: ' + this._response); + this._log.verbose(function(){return scrubRSTRLogMessage(response);}); } /** diff --git a/package.json b/package.json index e5827a9..ccf6d0a 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "type": "git", "url": "https://github.com/MSOpenTech/azure-activedirectory-library-for-nodejs.git" }, - "version": "0.1.12", + "version": "0.1.13", "description": "Windows Azure Active Directory Client Library for node", "keywords": [ "node", "azure", "AAD", "adal", "adfs", "oauth" ], "main": "./lib/adal.js", @@ -23,7 +23,7 @@ "licenses": [ { "type": "Apache 2.0", "url": "http://www.apache.org/licenses/LICENSE-2.0" } ], "dependencies": { "date-utils": "*", - "jws": "1.x.x", + "jws": "3.x.x", "node-uuid": "1.4.1", "request": ">= 2.9.203", "underscore": ">= 1.3.1",