@@ -22,12 +22,25 @@ var request = require('request');
2222var aadutils = require ( './aadutils' ) ;
2323var async = require ( 'async' ) ;
2424
25- var Metadata = function ( url ) {
25+ // Logging
26+
27+ var bunyan = require ( 'bunyan' ) ;
28+ var log = bunyan . createLogger ( { name : 'Microsoft OpenID Connect Passport Strategy' } ) ;
29+
30+ var Metadata = function ( url , authtype ) {
31+
32+
2633 if ( ! url ) {
2734 throw new Error ( "Metadata: url is a required argument" ) ;
2835 }
36+ if ( ! authtype ) {
37+ throw new Error ( 'OIDCBearerStrategy requires an authentication type specified to metadata parser. Valid types are saml, wsfed, or odic"' ) ;
38+ }
39+
2940 this . url = url ;
3041 this . metadata = null ;
42+ this . authtype = authtype ;
43+ log . info ( authtype , 'Metadata requested for authentication type' ) ;
3144} ;
3245
3346Object . defineProperty ( Metadata , 'url' , {
@@ -36,66 +49,162 @@ Object.defineProperty(Metadata, 'url', {
3649 }
3750} ) ;
3851
39- Object . defineProperty ( Metadata , 'oidc ' , {
52+ Object . defineProperty ( Metadata , 'saml ' , {
4053 get : function ( ) {
4154 return this . saml ;
4255 }
4356} ) ;
4457
58+ Object . defineProperty ( Metadata , 'wsfed' , {
59+ get : function ( ) {
60+ return this . wsfed ;
61+ }
62+ } ) ;
63+
64+ Object . defineProperty ( Metadata , 'oidc' , {
65+ get : function ( ) {
66+ return this . oidc ;
67+ }
68+ } ) ;
69+
4570
46- Metadata . prototype . updateOIDCMetadata = function ( doc , next ) {
71+ Object . defineProperty ( Metadata , 'metadata' , {
72+ get : function ( ) {
73+ return this . metadata ;
74+ }
75+ } ) ;
76+
77+ Metadata . prototype . updateSamlMetadata = function ( doc , next ) {
78+ log . info ( 'Request to update the SAML Metadata' ) ;
4779 try {
48- this . odic = { } ;
4980
50- var issuer = aadutils . getElement ( doc , 'EntityDescriptor' ) ;
51- var token_endpoint = aadutils . getElement ( entity , 'IDPSSODescriptor' ) ;
52- var token_endpoint_auth_methods_supported = aadutils . getElement ( entity , 'AuthMethodsSupported' ) ;
81+ this . saml = { } ;
82+
83+ var entity = aadutils . getElement ( doc , 'EntityDescriptor' ) ;
84+ var idp = aadutils . getElement ( entity , 'IDPSSODescriptor' ) ;
5385 var signOn = aadutils . getElement ( idp [ 0 ] , 'SingleSignOnService' ) ;
5486 var signOff = aadutils . getElement ( idp [ 0 ] , 'SingleLogoutService' ) ;
5587 var keyDescriptor = aadutils . getElement ( idp [ 0 ] , 'KeyDescriptor' ) ;
56- this . odic . loginEndpoint = signOn [ 0 ] . $ . Location ;
57- this . oidc . logoutEndpoint = signOff [ 0 ] . $ . Location ;
88+ this . saml . loginEndpoint = signOn [ 0 ] . $ . Location ;
89+ this . saml . logoutEndpoint = signOff [ 0 ] . $ . Location ;
5890
5991 // copy the x509 certs from the metadata
60- this . odic . certs = [ ] ; // where we put certs
92+ this . saml . certs = [ ] ;
6193 for ( var j = 0 ; j < keyDescriptor . length ; j ++ ) {
62- this . odic . certs . push ( keyDescriptor [ j ] . KeyInfo [ 0 ] . X509Data [ 0 ] . X509Certificate [ 0 ] ) ;
94+ this . saml . certs . push ( keyDescriptor [ j ] . KeyInfo [ 0 ] . X509Data [ 0 ] . X509Certificate [ 0 ] ) ;
6395 }
6496 next ( null ) ;
6597 } catch ( e ) {
66- next ( new Error ( 'Invalid Open ID Connect Metadata' + e . message ) ) ;
98+ next ( new Error ( 'Invalid SAMLP Federation Metadata ' + e . message ) ) ;
6799 }
68100} ;
69101
102+ Metadata . prototype . updateOidcMetadata = function ( doc , next ) {
103+ log . info ( 'Request to update the Open ID Connect Metadata' ) ;
104+ try {
105+ this . oidc = { } ;
106+
107+ var issuer = doc [ 'issuer' ] ;
108+ var keyDescriptor = aadutils . getElement ( idp [ 0 ] , 'keys' ) ;
109+
110+ // copy the x509 certs from the metadata
111+ this . oidc . certs = [ ] ;
112+ for ( var j = 0 ; j < keyDescriptor . length ; j ++ ) {
113+ this . oidc . certs . push ( keyDescriptor [ j ] . KeyInfo [ 0 ] . X509Data [ 0 ] . X509Certificate [ 0 ] ) ;
114+ }
115+ next ( null ) ;
116+ } catch ( e ) {
117+ next ( new Error ( 'Invalid Open ID Connect Federation Metadata ' + e . message ) ) ;
118+ }
119+ } ;
120+
121+
122+ Metadata . prototype . updateWsfedMetadata = function ( doc , next ) {
123+ log . info ( 'Request to update the WS Federation Metadata' ) ;
124+ try {
125+ this . wsfed = { } ;
126+ var entity = aadutils . getElement ( doc , 'EntityDescriptor' ) ;
127+ var roles = aadutils . getElement ( entity , 'RoleDescriptor' ) ;
128+ for ( var i = 0 ; i < roles . length ; i ++ ) {
129+ var role = roles [ i ] ;
130+ if ( role [ 'fed:SecurityTokenServiceEndpoint' ] ) {
131+ var endpoint = role [ 'fed:SecurityTokenServiceEndpoint' ] ;
132+ var endPointReference = aadutils . getFirstElement ( endpoint [ 0 ] , 'EndpointReference' ) ;
133+ this . wsfed . loginEndpoint = aadutils . getFirstElement ( endPointReference , 'Address' ) ;
134+
135+ var keyDescriptor = aadutils . getElement ( role , 'KeyDescriptor' ) ;
136+ // copy the x509 certs from the metadata
137+ this . wsfed . certs = [ ] ;
138+ for ( var j = 0 ; j < keyDescriptor . length ; j ++ ) {
139+ this . wsfed . certs . push ( keyDescriptor [ j ] . KeyInfo [ 0 ] . X509Data [ 0 ] . X509Certificate [ 0 ] ) ;
140+ }
141+ break ;
142+ }
143+ }
144+
145+ return next ( null ) ;
146+ } catch ( e ) {
147+ next ( new Error ( 'Invalid WSFED Federation Metadata ' + e . message ) ) ;
148+ }
149+ } ;
70150
71151Metadata . prototype . fetch = function ( callback ) {
72152 var self = this ;
73-
153+ log . info ( "Fetching metadata from the provided metadata URL: " + self . url ) ;
74154 async . waterfall ( [
75- // fetch the metadata for the AAD tenant
155+ // fetch the Federation metadata for the AAD tenant
76156 function ( next ) {
77157 request ( self . url , function ( err , response , body ) {
78158 if ( err ) {
79159 next ( err ) ;
80160 } else if ( response . statusCode !== 200 ) {
81161 next ( new Error ( "Error:" + response . statusCode + " Cannot get AAD Federation metadata from " + self . url ) ) ;
82162 } else {
163+ log . info ( body , "retreived" ) ;
83164 next ( null , body ) ;
84165 }
85166 } ) ;
86167 } ,
87168 function ( body , next ) {
169+ // parse the AAD Federation metadata xml
170+
171+ if ( self . authtype == "saml" || self . authtype == "wsfed" ) {
172+ log . info ( body , "Parsing XML retreived from the endpoint" ) ;
173+ var parser = new xml2js . Parser ( { explicitRoot :true } ) ;
88174 // Note: xml responses from Azure AAD have a leading \ufeff which breaks xml2js parser!
89- JSON . parse ( body , function ( err , data ) {
175+ parser . parseString ( body . replace ( "\ufeff" , "" ) , function ( err , data ) {
90176 self . metatdata = data ;
91177 next ( err ) ;
92178
93179 } ) ;
180+ } else if ( self . authtype == "oidc" ) {
181+ log . info ( body , "Parsing JSON retreived from the endpoint" ) ;
182+ JSON . parse ( body , function ( err , data ) {
183+ self . metatdata = data ;
184+ next ( err ) ;
185+ } ) ;
186+
187+ } else {
188+
189+ next ( new Error ( "Error: No Authentication type specified to metadata parser. Valid types are saml, wsfed, or odic" ) ) ;
190+ }
191+
94192 } ,
193+
95194 function ( next ) {
96- // update the ODIC SSO endpoints and certs from the metadata
97- self . updateODICMetadata ( self . metatdata , next ) ;
98- } ,
195+ if ( self . authtype = "saml" ) {
196+ // update the SAML SSO endpoints and certs from the metadata
197+ self . updateSamlMetadata ( self . metatdata , next ) ;
198+ } } ,
199+ function ( next ) {
200+ if ( self . authtype = "wsfed" ) {
201+ // update the SAML SSO endpoints and certs from the metadata
202+ self . updateWsfedMetadata ( self . metatdata , next ) ;
203+ } } ,
204+ function ( next ) {
205+ if ( self . authtype = "oidc" ) {
206+ self . updateOidcMetadata ( self . metadata , next ) ;
207+ } } ,
99208 ] , function ( err ) {
100209 // return err or success (err === null) to callback
101210 callback ( err ) ;
0 commit comments