user-realm.js 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268
  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 querystring = require('querystring');
  23. var request = require('request');
  24. var _ = require('underscore');
  25. var url = require('url');
  26. var constants = require('./constants');
  27. var Logger = require('./log').Logger;
  28. var util = require('./util');
  29. var AccountType = constants.UserRealm.AccountType;
  30. var FederationProtocolType = constants.UserRealm.FederationProtocolType;
  31. var USER_REALM_PATH_TEMPLATE = 'common/UserRealm/<user>';
  32. /**
  33. * Create a new UserRealm object
  34. * @private
  35. * @constructor
  36. * @param {object} callContext Contains any context information that applies to the request.
  37. * @param {string} userPrinciple The username for which a realm needs to be discovered.
  38. * @param {string} authority The string url of the authority that owns the userPrinciple.
  39. */
  40. function UserRealm(callContext, userPrinciple, authority) {
  41. this._log = new Logger('UserRealm', callContext._logContext);
  42. this._callContext = callContext;
  43. this._apiVersion = '1.0';
  44. this._federationProtocol = null;
  45. this._accountType = null;
  46. this._federationMetadataUrl = null;
  47. this._federationActiveAuthUrl = null;
  48. this._userPrinciple = userPrinciple;
  49. this._authority = authority;
  50. }
  51. /**
  52. * The API version requested by UserRealm.
  53. * @type {string}
  54. * @instance
  55. * @memberOf UserRealm
  56. * @name apiVersion
  57. */
  58. Object.defineProperty(UserRealm.prototype, 'apiVersion', {
  59. get : function() {
  60. return this._apiVersion;
  61. }
  62. });
  63. /**
  64. * The federation protocol used by the users realm.
  65. * @type {string}
  66. * @instance
  67. * @memberOf UserRealm
  68. * @name federationProtocol
  69. */
  70. Object.defineProperty(UserRealm.prototype, 'federationProtocol', {
  71. get : function() {
  72. return this._federationProtocol;
  73. }
  74. });
  75. /**
  76. * The Type of account. Either managed or federated.
  77. * @type {string}
  78. * @instance
  79. * @memberOf UserRealm
  80. * @name accountType
  81. */
  82. Object.defineProperty(UserRealm.prototype, 'accountType', {
  83. get : function() {
  84. return this._accountType;
  85. }
  86. });
  87. /**
  88. * If this is a federated account then this property will contain the mex url.
  89. * @type {string}
  90. * @instance
  91. * @memberOf UserRealm
  92. * @name federationsMetadataUrl
  93. */
  94. Object.defineProperty(UserRealm.prototype, 'federationMetadataUrl', {
  95. get : function() {
  96. return this._federationMetadataUrl;
  97. }
  98. });
  99. /**
  100. * If the account is federated this will contain the authentication endpoint.
  101. * @type {string}
  102. * @instance
  103. * @memberOf UserRealm
  104. * @name federationActiveAuthUrl
  105. */
  106. Object.defineProperty(UserRealm.prototype, 'federationActiveAuthUrl', {
  107. get : function() {
  108. return this._federationActiveAuthUrl;
  109. }
  110. });
  111. /**
  112. * Given the authority url this method constructs a full user realm discovery url.
  113. * @private
  114. * @returns A full user realm discovery url including path and query string.
  115. */
  116. UserRealm.prototype._getUserRealmUrl = function() {
  117. var userRealmUrl = util.copyUrl(this._authority);
  118. var urlEncodedUser = encodeURIComponent(this._userPrinciple);
  119. userRealmUrl.pathname = USER_REALM_PATH_TEMPLATE.replace('<user>', urlEncodedUser);
  120. var userRealmQuery = {
  121. 'api-version' : this._apiVersion
  122. };
  123. userRealmUrl.search = querystring.stringify(userRealmQuery);
  124. userRealmUrl = util.copyUrl(userRealmUrl);
  125. return userRealmUrl;
  126. };
  127. /**
  128. * Given a constants object and a value, validates that the value is a key in the constants object.
  129. * @private
  130. * @param {object} constants An object containing constant key value pairs.
  131. * @param {string} value A value to check against the constants
  132. * @param {bool} caseSensitive set to true if comparisons should be made as case sensitive. Defaults to false.
  133. * @returns {bool|string} If value passed in matches one of the constants then the return value is the matched constant.
  134. * If a non case sensitive match was done, then the value returned may be different than the value
  135. * passed in. If there is no match then the method returns false.
  136. */
  137. UserRealm.prototype._validateConstantValue = function(constants, value, caseSensitive) {
  138. if (!value) {
  139. return false;
  140. }
  141. if (!caseSensitive) {
  142. value = value.toLowerCase();
  143. }
  144. return _.contains(_.values(constants), value) ? value : false;
  145. };
  146. /**
  147. * Checks whether an account type string is valid.
  148. * @private
  149. * @param {string} type An account type string.
  150. * @returns {bool}
  151. */
  152. UserRealm.prototype._validateAccountType = function(type) {
  153. return this._validateConstantValue(AccountType, type);
  154. };
  155. /**
  156. * Checks whether a federation protocol string is valid.
  157. * @private
  158. * @param {string} protocol A federation protocol string.
  159. * @returns {bool}
  160. */
  161. UserRealm.prototype._validateFederationProtocol = function(protocol) {
  162. return this._validateConstantValue(FederationProtocolType, protocol);
  163. };
  164. /**
  165. * Logs the values parsed as part of user realm discovery.
  166. * @private
  167. */
  168. UserRealm.prototype._logParsedResponse = function() {
  169. this._log.verbose('UserRealm response:');
  170. this._log.verbose(' AccountType: ' + this.accountType);
  171. this._log.verbose(' FederationProtocol: ' + this.federationProtocol);
  172. this._log.verbose(' FederationMetatdataUrl: ' + this.federationMetadataUrl, true);
  173. this._log.verbose(' FederationActiveAuthUrl: ' + this.federationActiveAuthUrl, true);
  174. };
  175. /**
  176. * Parses the response from a user realm discovery request.
  177. * @private
  178. * @param {string} body The body returned as part of the http user realm discovery request.
  179. * @param {UserRealm.DiscoverCallback} callback Called when parsing is complete.
  180. */
  181. UserRealm.prototype._parseDiscoveryResponse = function(body, callback) {
  182. this._log.verbose('Discovery response:\n' + body, true);
  183. var response;
  184. try {
  185. response = JSON.parse(body);
  186. } catch (err) {
  187. callback(this._log.createError('Parsing realm discovery respone JSON failed: ' + body, true));
  188. return;
  189. }
  190. var accountType = this._validateAccountType(response['account_type']);
  191. if (!accountType) {
  192. callback(this._log.createError('Cannot parse account_type: ' + accountType));
  193. return;
  194. }
  195. this._accountType = accountType;
  196. if (this._accountType === AccountType.Federated) {
  197. var protocol = this._validateFederationProtocol(response['federation_protocol']);
  198. if (!protocol) {
  199. callback(this._log.createError('Cannot parse federation protocol: ' + protocol));
  200. return;
  201. }
  202. this._federationProtocol = protocol;
  203. this._federationMetadataUrl = response['federation_metadata_url'];
  204. this._federationActiveAuthUrl = response['federation_active_auth_url'];
  205. }
  206. this._logParsedResponse();
  207. callback();
  208. };
  209. /**
  210. * @callback DiscoverCallback
  211. * @memberOf UserRealm
  212. * @param {Error} error If an error occurs during discovery then this parameter will be used to return the error.
  213. */
  214. /**
  215. * Performs user realm discovery and fills in the properties on this object.
  216. * @private
  217. * @param {UserRealm.DiscoverCallback} callback Called when discovery is complete.
  218. */
  219. UserRealm.prototype.discover = function(callback) {
  220. var self = this;
  221. var options = util.createRequestOptions(
  222. this,
  223. {
  224. headers : {
  225. Accept : 'application/json'
  226. }
  227. }
  228. );
  229. var userRealmUrl = this._getUserRealmUrl();
  230. this._log.verbose('Performing user realm discovery at: ' + url.format(userRealmUrl), true);
  231. request.get(userRealmUrl, options, util.createRequestHandler('User Realm Discovery', this._log, callback,
  232. function(response, body) {
  233. self._parseDiscoveryResponse(body, callback);
  234. })
  235. );
  236. };
  237. module.exports = UserRealm;