wstrust-request.js 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240
  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 request = require('request');
  23. var uuid = require('uuid');
  24. var Logger = require('./log').Logger;
  25. var util = require('./util');
  26. var WSTrustResponse = require('./wstrust-response');
  27. var WSTrustVersion = require('./constants').WSTrustVersion;
  28. var USERNAME_PLACEHOLDER = '{UsernamePlaceHolder}';
  29. var PASSWORD_PLACEHOLDER = '{PasswordPlaceHolder}';
  30. /**
  31. * Creates a new instance of WSTrustRequest
  32. * @constructor
  33. * @private
  34. * @param {object} callContext Contains any context information that applies to the request.
  35. * @param {string} wstrustEndpointUrl An STS WS-Trust soap endpoint.
  36. * @param {string} appliesTo A URI that identifies a service for which the a token is to be obtained.
  37. */
  38. function WSTrustRequest(callContext, wstrustEndpointUrl, appliesTo, wstrustEndpointVersion) {
  39. this._log = new Logger('WSTrustRequest', callContext._logContext);
  40. this._callContext = callContext;
  41. this._wstrustEndpointUrl = wstrustEndpointUrl;
  42. this._appliesTo = appliesTo;
  43. this._wstrustEndpointVersion = wstrustEndpointVersion;
  44. }
  45. /**
  46. * Given a Date object adds the minutes parameter and returns a new Date object.
  47. * @private
  48. * @static
  49. * @memberOf WSTrustRequest
  50. * @param {Date} date A Date object.
  51. * @param {Number} minutes The number of minutes to add to the date parameter.
  52. * @returns {Date} Returns a Date object.
  53. */
  54. function _datePlusMinutes(date, minutes) {
  55. var minutesInMilliSeconds = minutes * 60 * 1000;
  56. var epochTime = date.getTime() + minutesInMilliSeconds;
  57. return new Date(epochTime);
  58. }
  59. /**
  60. * Builds the soap security header for the RST message.
  61. * @private
  62. * @param {string} username A username
  63. * @param {string} password The passowrd that corresponds to the username parameter.
  64. * @returns {string} A string that contains the soap security header.
  65. */
  66. WSTrustRequest.prototype._buildSecurityHeader = function() {
  67. var timeNow = new Date();
  68. var expireTime = _datePlusMinutes(timeNow, 10);
  69. var timeNowString = timeNow.toISOString();
  70. var expireTimeString = expireTime.toISOString();
  71. var securityHeaderXml =
  72. '<wsse:Security s:mustUnderstand=\'1\' xmlns:wsse=\'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd\'>\
  73. <wsu:Timestamp wsu:Id=\'_0\'>\
  74. <wsu:Created>' + timeNowString + '</wsu:Created>\
  75. <wsu:Expires>' + expireTimeString + '</wsu:Expires>\
  76. </wsu:Timestamp>\
  77. <wsse:UsernameToken wsu:Id=\'ADALUsernameToken\'>\
  78. <wsse:Username>' + USERNAME_PLACEHOLDER + '</wsse:Username>\
  79. <wsse:Password>' + PASSWORD_PLACEHOLDER + '</wsse:Password>\
  80. </wsse:UsernameToken>\
  81. </wsse:Security>';
  82. return securityHeaderXml;
  83. };
  84. /**
  85. * Replaces the placeholders in the RST template with the actual username and password values.
  86. * @private
  87. * @param {string} RSTTemplate An RST with placeholders for username and password.
  88. * @param {string} username A username
  89. * @param {string} password The passowrd that corresponds to the username parameter.
  90. * @returns {string} A string containing a complete RST soap message.
  91. */
  92. WSTrustRequest.prototype._populateRSTUsernamePassword = function(RSTTemplate, username, password) {
  93. var RST = RSTTemplate.replace(USERNAME_PLACEHOLDER, username).replace(PASSWORD_PLACEHOLDER, this._populatedEscapedPassword(password));
  94. return RST;
  95. };
  96. /**
  97. * Escape xml characters in password.
  98. * @private
  99. * @param {string} password The password to be excaped with xml charaters.
  100. */
  101. WSTrustRequest.prototype._populatedEscapedPassword = function (password) {
  102. var escapedPassword = password;
  103. return escapedPassword.replace(/&/g, '&amp;')
  104. .replace(/"/g, '&quot;')
  105. .replace(/'/g, '&apos;')
  106. .replace(/</g, '&lt;')
  107. .replace(/>/g, '&gt;');
  108. }
  109. /**
  110. * Builds a WS-Trust RequestSecurityToken (RST) message using username password authentication.
  111. * @private
  112. * @param {string} username A username
  113. * @param {string} password The passowrd that corresponds to the username parameter.
  114. * @returns {string} A string containing a complete RST soap message.
  115. */
  116. WSTrustRequest.prototype._buildRST = function(username, password) {
  117. var messageID = uuid.v4();
  118. // Create a template RST with placeholders for the username and password so the
  119. // the RST can be logged without the sensitive information.
  120. var schemaLocation = 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd';
  121. var soapAction = 'http://docs.oasis-open.org/ws-sx/ws-trust/200512/RST/Issue';
  122. var rstTrustNamespace = 'http://docs.oasis-open.org/ws-sx/ws-trust/200512';
  123. var keyType = 'http://docs.oasis-open.org/ws-sx/ws-trust/200512/Bearer';
  124. var requestType = 'http://docs.oasis-open.org/ws-sx/ws-trust/200512/Issue';
  125. if (this._wstrustEndpointVersion === WSTrustVersion.WSTRUST2005) {
  126. soapAction = 'http://schemas.xmlsoap.org/ws/2005/02/trust/RST/Issue';
  127. rstTrustNamespace = 'http://schemas.xmlsoap.org/ws/2005/02/trust';
  128. keyType = 'http://schemas.xmlsoap.org/ws/2005/05/identity/NoProofKey';
  129. requestType = 'http://schemas.xmlsoap.org/ws/2005/02/trust/Issue';
  130. }
  131. var RSTTemplate =
  132. '<s:Envelope xmlns:s=\'http://www.w3.org/2003/05/soap-envelope\' xmlns:wsa=\'http://www.w3.org/2005/08/addressing\' xmlns:wsu=\'' + schemaLocation + '\'>\
  133. <s:Header>\
  134. <wsa:Action s:mustUnderstand=\'1\'>' + soapAction + '</wsa:Action>\
  135. <wsa:messageID>urn:uuid:' + messageID + '</wsa:messageID>\
  136. <wsa:ReplyTo>\
  137. <wsa:Address>http://www.w3.org/2005/08/addressing/anonymous</wsa:Address>\
  138. </wsa:ReplyTo>\
  139. <wsa:To s:mustUnderstand=\'1\'>' + this._wstrustEndpointUrl + '</wsa:To>\
  140. ' + this._buildSecurityHeader() + '\
  141. </s:Header>\
  142. <s:Body>\
  143. <wst:RequestSecurityToken xmlns:wst=\'' + rstTrustNamespace + '\'>\
  144. <wsp:AppliesTo xmlns:wsp=\'http://schemas.xmlsoap.org/ws/2004/09/policy\'>\
  145. <wsa:EndpointReference>\
  146. <wsa:Address>' + this._appliesTo + '</wsa:Address>\
  147. </wsa:EndpointReference>\
  148. </wsp:AppliesTo>\
  149. <wst:KeyType>' + keyType + '</wst:KeyType>\
  150. <wst:RequestType>' + requestType + '</wst:RequestType>\
  151. </wst:RequestSecurityToken>\
  152. </s:Body>\
  153. </s:Envelope>';
  154. this._log.verbose('Created RST: \n' + RSTTemplate, true);
  155. var RST = this._populateRSTUsernamePassword(RSTTemplate, username, password);
  156. return RST;
  157. };
  158. /**
  159. * Handles the processing of a RSTR
  160. * @private
  161. * @param {string} body
  162. * @param {WSTrustRequest.AcquireTokenCallback} callback
  163. */
  164. WSTrustRequest.prototype._handleRSTR = function(body, callback) {
  165. var err;
  166. var wstrustResponse = new WSTrustResponse(this._callContext, body, this._wstrustEndpointVersion);
  167. try {
  168. wstrustResponse.parse();
  169. } catch (error) {
  170. err = error;
  171. }
  172. callback(err, wstrustResponse);
  173. };
  174. /**
  175. * Performs a WS-Trust RequestSecurityToken request to obtain a federated token in exchange for a username password.
  176. * @param {string} username A username
  177. * @param {string} password The passowrd that corresponds to the username parameter.
  178. * @param {WSTrustRequest.AcquireTokenCallback} callback Called once the federated token has been retrieved or on error.
  179. */
  180. WSTrustRequest.prototype.acquireToken = function(username, password, callback) {
  181. if (this._wstrustEndpointVersion === WSTrustVersion.UNDEFINED) {
  182. var err = this._log.createError('Unsupported wstrust endpoint version. Current support version is wstrust2005 or wstrust13.');
  183. callback(err);
  184. return;
  185. }
  186. var self = this;
  187. var RST = this._buildRST(username, password);
  188. var soapAction = this._wstrustEndpointVersion === WSTrustVersion.WSTRUST2005 ? 'http://schemas.xmlsoap.org/ws/2005/02/trust/RST/Issue' : 'http://docs.oasis-open.org/ws-sx/ws-trust/200512/RST/Issue';
  189. var options = util.createRequestOptions(
  190. this,
  191. {
  192. headers : {
  193. 'Content-Type' : 'application/soap+xml; charset=utf-8',
  194. 'SOAPAction' : soapAction
  195. },
  196. body : RST
  197. }
  198. );
  199. this._log.verbose('Sending RST to: ' + this._wstrustEndpointUrl, true);
  200. request.post(this._wstrustEndpointUrl, options, util.createRequestHandler('WS-Trust RST', this._log, callback,
  201. function(response, body) {
  202. self._handleRSTR(body, callback);
  203. }
  204. ));
  205. };
  206. /**
  207. * @callback AcquireTokenCallback
  208. * @memberOf WSTrustRequest
  209. * @param {Error} err Contains an error object if acquireToken fails.
  210. * @param {WSTrustResponse} A successful response to the RST.
  211. */
  212. module.exports = WSTrustRequest;