| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349 |
- /*
- * @copyright
- * Copyright © Microsoft Open Technologies, Inc.
- *
- * All Rights Reserved
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http: *www.apache.org/licenses/LICENSE-2.0
- *
- * THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS
- * OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION
- * ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A
- * PARTICULAR PURPOSE, MERCHANTABILITY OR NON-INFRINGEMENT.
- *
- * See the Apache License, Version 2.0 for the specific language
- * governing permissions and limitations under the License.
- */
- 'use strict';
- var request = require('request');
- var url = require('url');
- var DOMParser = require('xmldom').DOMParser;
- var _ = require('underscore');
- var Logger = require('./log').Logger;
- var util = require('./util');
- var xmlutil = require('./xmlutil');
- var select = xmlutil.xpathSelect;
- var Namespaces = require('./constants').XmlNamespaces;
- var WSTrustVersion = require('./constants').WSTrustVersion;
- /**
- * Create a new Mex object.
- * @private
- * @constructor
- * @param {object} callContext Contains any context information that applies to the request.
- * @param {string} url The url of the mex endpoint.
- */
- function Mex(callContext, url) {
- this._log = new Logger('MEX', callContext._logContext);
- this._callContext = callContext;
- this._url = url;
- this._dom = null;
- this._mexDoc = null;
- this._usernamePasswordPolicy = {};
- this._log.verbose('Mex created');
- this._log.verbose('Mex created with url: ' + url, true);
- }
- /**
- * Returns the policy containing IDP url and wstrust version from which a username passwowrd can be exchanged for a token.
- * @instance
- * @memberOf Mex
- * @name usernamePasswordPolicy
- */
- Object.defineProperty(Mex.prototype, 'usernamePasswordPolicy', {
- get: function() {
- return this._usernamePasswordPolicy;
- }
- });
- /**
- * @callback DiscoverCallback
- * @memberOf Mex
- * @param {object} error
- */
- /**
- * Performs Mex discovery. This method will retrieve the mex document, parse it, and extract
- * the username password ws-trust endpoint.
- * @private
- * @param {Mex.DiscoverCallback} callback Called when discover is complete.
- */
- Mex.prototype.discover = function (callback) {
- this._log.verbose('Retrieving mex');
- this._log.verbose('Retrieving mex at: ' + this._url);
- var self = this;
- var options = util.createRequestOptions(self, { headers : { 'Content-Type' : 'application/soap+xml'} });
- request.get(this._url, options, util.createRequestHandler('Mex Get', this._log, callback,
- function(response, body) {
- try {
- self._mexDoc = body;
- var options = {
- errorHandler : self._log.error
- };
- self._dom = new DOMParser(options).parseFromString(self._mexDoc);
- self._parse(callback);
- return;
- } catch (err) {
- self._log.error('Failed to parse mex response in to DOM', err, true);
- callback(err);
- }
- })
- );
- };
- var TRANSPORT_BINDING_XPATH = 'wsp:ExactlyOne/wsp:All/sp:TransportBinding';
- var TRANSPORT_BINDING_2005_XPATH = 'wsp:ExactlyOne/wsp:All/sp2005:TransportBinding';
- /**
- * Checks a DOM policy node that is a potentialy appplicable username password policy
- * to ensure that it has the correct transport.
- * @private
- * @param {object} policyNode The policy node to check.
- * @returns {string} If the policy matches the desired transport then the id of the policy is returned.
- * If not then null is returned.
- */
- Mex.prototype._checkPolicy = function(policyNode) {
- var policyId = null;
- var id = policyNode.getAttributeNS(Namespaces.wsu, 'Id');
- var transportBindingNodes = select(policyNode, TRANSPORT_BINDING_XPATH);
- if (0 === transportBindingNodes.length) {
- transportBindingNodes = select(policyNode, TRANSPORT_BINDING_2005_XPATH);
- }
- if (0 !== transportBindingNodes.length) {
- if (id) {
- policyId = id;
- }
- }
- if (policyId) {
- this._log.verbose('found matching policy id');
- this._log.verbose('found matching policy id: ' + policyId, true);
- } else {
- if (!id) {
- id = '<no id>';
- }
- this._log.verbose('potential policy did not match required transport binding');
- this._log.verbose('potential policy did not match required transport binding: ' + id, true);
- }
- return policyId;
- };
- /**
- * Finds all username password policies within the mex document.
- * @private
- * @param xpath The xpath expression for selecting username token nodes.
- * @returns {object} A map object that contains objects containing the id of username password polices.
- */
- Mex.prototype._selectUsernamePasswordPolicies = function(xpath) {
- var policies = {};
- var usernameTokenNodes = select(this._dom, xpath);
- if (!usernameTokenNodes.length) {
- this._log.warn('no username token policy nodes found');
- return;
- }
- for (var i=0; i < usernameTokenNodes.length; i++) {
- var policyNode = usernameTokenNodes[i].parentNode.parentNode.parentNode.parentNode.parentNode.parentNode.parentNode;
- var id = this._checkPolicy(policyNode);
- if (id) {
- var idRef = '#' + id;
- policies[idRef] = { id : idRef };
- }
- }
- return _.isEmpty(policies) ? null : policies;
- };
- var SOAP_ACTION_XPATH = 'wsdl:operation/soap12:operation/@soapAction';
- var RST_SOAP_ACTION_13 = 'http://docs.oasis-open.org/ws-sx/ws-trust/200512/RST/Issue';
- var RST_SOAP_ACTION_2005 = 'http://schemas.xmlsoap.org/ws/2005/02/trust/RST/Issue';
- var SOAP_TRANSPORT_XPATH = 'soap12:binding/@transport';
- var SOAP_HTTP_TRANSPORT_VALUE = 'http://schemas.xmlsoap.org/soap/http';
- /**
- * Given a DOM binding node determines whether it matches the correct soap action and transport.
- * @private
- * @param {object} bindingNode The DOM node to check.
- * @returns {bool}
- */
- Mex.prototype._checkSoapActionAndTransport = function(bindingNode) {
- var soapTransportAttributes;
- var soapAction;
- var soapTransport;
- var bindingName = bindingNode.getAttribute('name');
- var soapActionAttributes = select(bindingNode, SOAP_ACTION_XPATH);
- if (soapActionAttributes.length) {
- soapAction = soapActionAttributes[0].value;
- soapTransportAttributes = select(bindingNode, SOAP_TRANSPORT_XPATH);
- }
- if (soapTransportAttributes.length) {
- soapTransport = soapTransportAttributes[0].value;
- }
- if (soapTransport === SOAP_HTTP_TRANSPORT_VALUE) {
- if (soapAction === RST_SOAP_ACTION_13) {
- this._log.verbose('foud binding matching Action and Transport: ' + bindingName);
- return WSTrustVersion.WSTRUST13;
- }
- else if (soapAction === RST_SOAP_ACTION_2005) {
- this._log.verbose('found binding matching Action and Transport: ' + bindingName);
- return WSTrustVersion.WSTRUST2005;
- }
- }
- this._log.verbose('binding node did not match soap Action or Transport: ' + bindingName);
- return WSTrustVersion.UNDEFINED;
- };
- /**
- * Given a map with policy id keys, finds the bindings in the mex document that are linked to thos policies.
- * @private
- * @param {object} policies A map with policy id keys.
- * @returns {object} a map of bindings id's to policy id's.
- */
- Mex.prototype._getMatchingBindings = function(policies) {
- var bindings = {};
- var bindingPolicyRefNodes = select(this._dom, '//wsdl:definitions/wsdl:binding/wsp:PolicyReference');
- for (var i=0; i < bindingPolicyRefNodes.length; i++) {
- var node = bindingPolicyRefNodes[i];
- var uri = node.getAttribute('URI');
- var policy = policies[uri];
- if (policy) {
- var bindingNode = node.parentNode;
- var bindingName = bindingNode.getAttribute('name');
- var version = this._checkSoapActionAndTransport(bindingNode);
- if (version !== WSTrustVersion.UNDEFINED) {
- var bindingPolicy = {};
- bindingPolicy.url = uri;
- bindingPolicy.version = version;
- bindings[bindingName] = bindingPolicy;
- }
- }
- }
- return _.isEmpty(bindings) ? null : bindings;
- };
- /**
- * Ensures that a url points to an SSL endpoint.
- * @private
- * @param {string} endpointUrl The url to check.
- * @returns {bool}
- */
- Mex.prototype._urlIsSecure = function(endpointUrl) {
- var parsedUrl = url.parse(endpointUrl);
- return parsedUrl.protocol === 'https:';
- };
- var PORT_XPATH = '//wsdl:definitions/wsdl:service/wsdl:port';
- var ADDRESS_XPATH = 'wsa10:EndpointReference/wsa10:Address';
- /**
- * Finds all of the wsdl ports in the mex document that are associated with username password policies. Augments
- * the passed in bindings with the endpoint url of the correct port.
- * @private
- * @param {object} bindings A map of binding id's to policy id's.
- */
- Mex.prototype._getPortsForPolicyBindings = function(bindings, policies) {
- var portNodes = select(this._dom, PORT_XPATH);
- if (0 === portNodes.length) {
- this._log.warning('no ports found');
- }
- for (var i=0; i < portNodes.length; i++) {
- var portNode = portNodes[i];
- var bindingId = portNode.getAttribute('binding');
- // Clear any prefix
- var bindingIdParts = bindingId.split(':');
- bindingId = bindingIdParts[bindingIdParts.length - 1];
- var trustPolicy = bindings[bindingId];
- if (trustPolicy) {
- var bindingPolicy = policies[trustPolicy.url];
- if (bindingPolicy && !bindingPolicy.url) {
- bindingPolicy.version = trustPolicy.version;
- var addressNode = select(portNode, ADDRESS_XPATH);
- if (0 === addressNode) {
- throw this._log.createError('no address nodes on port.');
- }
- var address = xmlutil.findElementText(addressNode[0]);
- if (this._urlIsSecure(address)) {
- bindingPolicy.url = address;
- } else {
- this._log.warn('skipping insecure endpoint: ' + address);
- }
- }
- }
- }
- };
- /**
- * Given a list of username password policies chooses one of them at random as the policy chosen by this Mex instance.
- * @private
- * @param {object} policies A map of policy id's to an object containing username password ws-trust endpoint addresses.
- */
- Mex.prototype._selectSingleMatchingPolicy = function(policies) {
- // if both wstrust13 and wstrust2005 policy exists, then choose wstrust13, otherwise choose whatever exists.
- var matchingPolicies = _.filter(policies, function(policy) { return policy.url ? true : false; });
- if (!matchingPolicies) {
- this._log.warn('no policies found with an url');
- return;
- }
- var wstrust13Policy = null, wstrust2005Policy = null;
- for(var i = 0; i < matchingPolicies.length; ++i) {
- var matchingPolicy = matchingPolicies[i];
- if (WSTrustVersion.WSTRUST13 === matchingPolicy.version) {
- wstrust13Policy = matchingPolicy;
- }
- else if (WSTrustVersion.WSTRUST2005 === matchingPolicy.version) {
- wstrust2005Policy = matchingPolicy;
- }
- }
- if (!wstrust13Policy && !wstrust2005Policy) {
- this._log.warn('no policies found with an url');
- this._usernamePasswordPolicy = null;
- return;
- }
- this._usernamePasswordPolicy = wstrust13Policy ? wstrust13Policy : wstrust2005Policy;
- };
- /**
- * Parses the mex document previously retrieved.
- * @private
- * @param {Mex.DiscoverCallback} callback
- */
- Mex.prototype._parse = function(callback) {
- var self = this;
- var xpathExpression = '//wsdl:definitions/wsp:Policy/wsp:ExactlyOne/wsp:All/sp:SignedEncryptedSupportingTokens/wsp:Policy/sp:UsernameToken/wsp:Policy/sp:WssUsernameToken10';
- var policies = self._selectUsernamePasswordPolicies(xpathExpression);
- xpathExpression = '//wsdl:definitions/wsp:Policy/wsp:ExactlyOne/wsp:All/sp2005:SignedSupportingTokens/wsp:Policy/sp2005:UsernameToken/wsp:Policy/sp2005:WssUsernameToken10';
- if (policies) {
- _.extend(policies, self._selectUsernamePasswordPolicies(xpathExpression));
- }
- else {
- policies = self._selectUsernamePasswordPolicies(xpathExpression);
- }
- if (!policies) {
- callback(self._log.createError('No matching policies'));
- return;
- }
- var bindings = self._getMatchingBindings(policies);
- if (!bindings) {
- callback(self._log.createError('No matching bindings'));
- return;
- }
- self._getPortsForPolicyBindings(bindings, policies);
- self._selectSingleMatchingPolicy(policies);
- var err = this._url ? undefined : this._log.createError('No ws-trust endpoints match requirements.');
- callback(err);
- };
- module.exports = Mex;
|