wstrust-response.js 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288
  1. /*
  2. * @copyright
  3. * Copyright © Microsoft Open Technologies, Inc.
  4. *
  5. * All Rights Reserved
  6. *
  7. * Licensed under the Apache License, Version 2.0 (the "License");
  8. * you may not use this file except in compliance with the License.
  9. * You may obtain a copy of the License at
  10. *
  11. * http: *www.apache.org/licenses/LICENSE-2.0
  12. *
  13. * THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS
  14. * OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION
  15. * ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A
  16. * PARTICULAR PURPOSE, MERCHANTABILITY OR NON-INFRINGEMENT.
  17. *
  18. * See the Apache License, Version 2.0 for the specific language
  19. * governing permissions and limitations under the License.
  20. */
  21. 'use strict';
  22. var xmldom = require('xmldom');
  23. var xmlutil = require('./xmlutil');
  24. var Logger = require('./log').Logger;
  25. var WSTrustVersion = require('./constants').WSTrustVersion;
  26. var select = xmlutil.xpathSelect;
  27. var DOMParser = xmldom.DOMParser;
  28. // A regular expression for finding the SAML Assertion in an RSTR. Used to remove the SAML
  29. // assertion when logging the RSTR.
  30. var assertionRegEx = /RequestedSecurityToken.*?((<.*?:Assertion.*?>).*<\/.*?Assertion>).*?/;
  31. /**
  32. * Creates a log message that contains the RSTR scrubbed of the actual SAML assertion.
  33. * @private
  34. * @return {string} A log message.
  35. */
  36. function scrubRSTRLogMessage(RSTR) {
  37. var scrubbedRSTR = null;
  38. var singleLineRSTR = RSTR.replace(/(\r\n|\n|\r)/gm,'');
  39. var matchResult = assertionRegEx.exec(singleLineRSTR);
  40. if (null === matchResult) {
  41. // No Assertion was matched so just return the RSTR as is.
  42. scrubbedRSTR = singleLineRSTR;
  43. } else {
  44. var samlAssertion = matchResult[1];
  45. var samlAssertionStartTag = matchResult[2];
  46. scrubbedRSTR = singleLineRSTR.replace(samlAssertion, samlAssertionStartTag + 'ASSERTION CONTENTS REDACTED</saml:Assertion>');
  47. }
  48. return 'RSTR Response: ' + scrubbedRSTR;
  49. }
  50. /**
  51. * Creates a new WSTrustResponse instance.
  52. * @constructor
  53. * @private
  54. * @param {object} callContext Contains any context information that applies to the request.
  55. * @param {string} response A soap response from a WS-Trust request.
  56. * @param {sting} wstrustVersion The version for the WS-Trust request.
  57. */
  58. function WSTrustResponse(callContext, response, wstrustVersion) {
  59. this._log = new Logger('WSTrustResponse', callContext._logContext);
  60. this._callContext = callContext;
  61. this._response = response;
  62. this._dom = null;
  63. this._errorCode = null;
  64. this._faultMessage = null;
  65. this._tokenType = null;
  66. this._token = null;
  67. this._wstrustVersion = wstrustVersion;
  68. this._log.verbose(function(){return scrubRSTRLogMessage(response);});
  69. }
  70. /**
  71. * If the soap response contained a soap fault then this property will contain the fault
  72. * error code. Otherwise it will return null
  73. * @instance
  74. * @type {string}
  75. * @memberOf WSTrustResponse
  76. * @name errorCode
  77. */
  78. Object.defineProperty(WSTrustResponse.prototype, 'errorCode', {
  79. get: function() {
  80. return this._errorCode;
  81. }
  82. });
  83. /**
  84. * @property {string} FaultMessage If the soap resopnse contained a soap fault with a fault message then it will
  85. * be returned by this property.
  86. * @instance
  87. * @type {string}
  88. * @memberOf WSTrustResponse
  89. * @name faultMessage
  90. */
  91. Object.defineProperty(WSTrustResponse.prototype, 'faultMessage', {
  92. get: function() {
  93. return this._faultMessage;
  94. }
  95. });
  96. /**
  97. * @property {string} TokenType If the soap resonse contained a token then this property will contain
  98. * the token type uri
  99. * @instance
  100. * @type {string}
  101. * @memberOf WSTrustResponse
  102. * @name tokenType
  103. */
  104. Object.defineProperty(WSTrustResponse.prototype, 'tokenType', {
  105. get: function() {
  106. return this._tokenType;
  107. }
  108. });
  109. /**
  110. * @property {string} Token If the soap response contained a token then this property will hold that token.
  111. * @instance
  112. * @type {string}
  113. * @memberOf WSTrustResponse
  114. * @name token
  115. */
  116. Object.defineProperty(WSTrustResponse.prototype, 'token', {
  117. get: function() {
  118. return this._token;
  119. }
  120. });
  121. // Sample error message
  122. //<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:a="http://www.w3.org/2005/08/addressing" xmlns:u="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
  123. // <s:Header>
  124. // <a:Action s:mustUnderstand="1">http://www.w3.org/2005/08/addressing/soap/fault</a:Action>
  125. // - <o:Security s:mustUnderstand="1" xmlns:o="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
  126. // <u:Timestamp u:Id="_0">
  127. // <u:Created>2013-07-30T00:32:21.989Z</u:Created>
  128. // <u:Expires>2013-07-30T00:37:21.989Z</u:Expires>
  129. // </u:Timestamp>
  130. // </o:Security>
  131. // </s:Header>
  132. // <s:Body>
  133. // <s:Fault>
  134. // <s:Code>
  135. // <s:Value>s:Sender</s:Value>
  136. // <s:Subcode>
  137. // <s:Value xmlns:a="http://docs.oasis-open.org/ws-sx/ws-trust/200512">a:RequestFailed</s:Value>
  138. // </s:Subcode>
  139. // </s:Code>
  140. // <s:Reason>
  141. // <s:Text xml:lang="en-US">MSIS3127: The specified request failed.</s:Text>
  142. // </s:Reason>
  143. // </s:Fault>
  144. // </s:Body>
  145. //</s:Envelope>
  146. /**
  147. * Attempts to parse an error from the soap response. If there is one then it
  148. * will fill in the error related properties. Otherwsie it will do nothing.
  149. * @private
  150. * @returns {bool} true if an error was found and parsed in the response.
  151. */
  152. WSTrustResponse.prototype._parseError = function() {
  153. var errorFound = false;
  154. var faultNode = select(this._dom, '//s:Envelope/s:Body/s:Fault/s:Reason');
  155. if (faultNode.length) {
  156. this._faultMessage = xmlutil.serializeNodeChildren(faultNode[0]);
  157. if (this._faultMessage) {
  158. errorFound = true;
  159. }
  160. }
  161. // Subcode has minoccurs=0 and maxoccurs=1(default) according to the http://www.w3.org/2003/05/soap-envelope
  162. // Subcode may have another subcode as well. This is only targetting at top level subcode.
  163. // Subcode value may have different messages not always uses http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd.
  164. // text inside the value is not possible to select without prefix, so substring is necessary
  165. var subcodeNode = select(this._dom, '//s:Envelope/s:Body/s:Fault/s:Code/s:Subcode/s:Value');
  166. if (1 < subcodeNode.length) {
  167. throw this._log.createError('Found too many fault code values:' + subcodeNode.length);
  168. }
  169. if (subcodeNode.length) {
  170. var errorCode = subcodeNode[0].firstChild.data;
  171. this._errorCode = (errorCode.split(':'))[1];
  172. errorFound = true;
  173. }
  174. return errorFound;
  175. };
  176. /**
  177. * Attempts to parse a token from the soap response. If there is one then it will fill in the
  178. * token related properties. Otherwise it does nothing.
  179. * @private
  180. * @throws {Error} If the response is not parseable, or too many tokens are found.
  181. */
  182. WSTrustResponse.prototype._parseToken = function() {
  183. var xPath = this._wstrustVersion === WSTrustVersion.WSTRUST2005 ? '//s:Envelope/s:Body/t:RequestSecurityTokenResponse/t:TokenType' : '//s:Envelope/s:Body/wst:RequestSecurityTokenResponseCollection/wst:RequestSecurityTokenResponse/wst:TokenType';
  184. var tokenTypeNodes = select(this._dom, xPath);
  185. if (!tokenTypeNodes.length) {
  186. this._log.warn('No TokenType elements found in RSTR');
  187. }
  188. for (var i = 0, length = tokenTypeNodes.length; i < length; i++) {
  189. if (this._token) {
  190. this._log.warn('Found more than one returned token. Using the first.');
  191. break;
  192. }
  193. var tokenTypeNode = tokenTypeNodes[i];
  194. var tokenType = xmlutil.findElementText(tokenTypeNode);
  195. if (!tokenType) {
  196. this._log.warn('Could not find token type in RSTR token');
  197. }
  198. var securityTokenPath = this._wstrustVersion === WSTrustVersion.WSTRUST2005 ? 't:RequestedSecurityToken' : 'wst:RequestedSecurityToken';
  199. var requestedTokenNode = select(tokenTypeNode.parentNode, securityTokenPath);
  200. if (1 < requestedTokenNode) {
  201. throw this._log.createError('Found too many RequestedSecurityToken nodes for token type: ' + tokenType);
  202. }
  203. if (!requestedTokenNode.length) {
  204. this._log.warn('Unable to find RequestsSecurityToken element associated with TokenType element: ' + tokenType);
  205. continue;
  206. }
  207. var token = xmlutil.serializeNodeChildren(requestedTokenNode[0]);
  208. if (!token) {
  209. this._log.warn('Unable to find token associated with TokenType element: ' + tokenType);
  210. continue;
  211. }
  212. this._token = token;
  213. this._tokenType = tokenType;
  214. this._log.info('Found token of type: ' + this._tokenType);
  215. }
  216. if (!this._token) {
  217. throw this._log.createError('Unable to find any tokens in RSTR.');
  218. }
  219. };
  220. /**
  221. * This method parses the soap response that was passed in at construction.
  222. * @throws {Error} If the server returned an error, or there was any failure to parse the response.
  223. */
  224. WSTrustResponse.prototype.parse = function() {
  225. if (!this._response) {
  226. throw this._log.createError('Received empty RSTR response body.');
  227. }
  228. try {
  229. try {
  230. var options = {
  231. errorHandler : this._log.error
  232. };
  233. this._dom = new DOMParser(options).parseFromString(this._response);
  234. } catch (err) {
  235. throw this._log.createError('Failed to parse RSTR in to DOM', err, true);
  236. }
  237. var errorFound = this._parseError();
  238. if (errorFound) {
  239. var stringErrorCode = this.ErrorCode || 'NONE';
  240. var stringFaultMessage = this.FaultMessage || 'NONE';
  241. throw this._log.createError('Server returned error in RSTR - ErrorCode: ' + stringErrorCode + ' : FaultMessage: ' + stringFaultMessage, true);
  242. }
  243. this._parseToken();
  244. } catch (err) {
  245. delete this._dom;
  246. throw err;
  247. }
  248. };
  249. module.exports = WSTrustResponse;