openid 1.0.0 → 1.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/openid.js.orig ADDED
@@ -0,0 +1,1541 @@
1
+ /* OpenID for node.js
2
+ *
3
+ * http://ox.no/software/node-openid
4
+ * http://github.com/havard/node-openid
5
+ *
6
+ * Copyright (C) 2010 by Håvard Stranden
7
+ *
8
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
9
+ * of this software and associated documentation files (the "Software"), to deal
10
+ * in the Software without restriction, including without limitation the rights
11
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12
+ * copies of the Software, and to permit persons to whom the Software is
13
+ * furnished to do so, subject to the following conditions:
14
+ *
15
+ * The above copyright notice and this permission notice shall be included in
16
+ * all copies or substantial portions of the Software.
17
+ *
18
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24
+ *
25
+ * -*- Mode: JS; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
26
+ * vim: set sw=2 ts=2 et tw=80 :
27
+ */
28
+
29
+ var convert = require('./lib/convert'),
30
+ crypto = require('crypto'),
31
+ request = require('request'),
32
+ querystring = require('querystring'),
33
+ url = require('url'),
34
+ xrds = require('./lib/xrds');
35
+
36
+ var _associations = {};
37
+ var _discoveries = {};
38
+
39
+ var AX_MAX_VALUES_COUNT = 1000;
40
+
41
+ var openid = exports;
42
+
43
+ function hasOwnProperty(obj, prop) {
44
+ return Object.prototype.hasOwnProperty.call(obj, prop);
45
+ }
46
+
47
+ openid.RelyingParty = function(returnUrl, realm, stateless, strict, extensions)
48
+ {
49
+ this.returnUrl = returnUrl;
50
+ this.realm = realm || null;
51
+ this.stateless = stateless;
52
+ this.strict = strict;
53
+ this.extensions = extensions;
54
+ }
55
+
56
+ openid.RelyingParty.prototype.authenticate = function(identifier, immediate, callback)
57
+ {
58
+ openid.authenticate(identifier, this.returnUrl, this.realm,
59
+ immediate, this.stateless, callback, this.extensions, this.strict);
60
+ }
61
+
62
+ openid.RelyingParty.prototype.verifyAssertion = function(requestOrUrl, callback)
63
+ {
64
+ openid.verifyAssertion(requestOrUrl, callback, this.stateless, this.extensions, this.strict);
65
+ }
66
+
67
+ var _isDef = function(e)
68
+ {
69
+ var undefined;
70
+ return e !== undefined;
71
+ }
72
+
73
+ var _toBase64 = function(binary)
74
+ {
75
+ return convert.base64.encode(convert.btwoc(binary));
76
+ }
77
+
78
+ var _fromBase64 = function(str)
79
+ {
80
+ return convert.unbtwoc(convert.base64.decode(str));
81
+ }
82
+
83
+ var _xor = function(a, b)
84
+ {
85
+ if(a.length != b.length)
86
+ {
87
+ throw new Error('Length must match for xor');
88
+ }
89
+
90
+ var r = '';
91
+ for(var i = 0; i < a.length; ++i)
92
+ {
93
+ r += String.fromCharCode(a.charCodeAt(i) ^ b.charCodeAt(i));
94
+ }
95
+
96
+ return r;
97
+ }
98
+
99
+ openid.saveAssociation = function(provider, type, handle, secret, expiry_time_in_seconds, callback)
100
+ {
101
+ setTimeout(function() {
102
+ openid.removeAssociation(handle);
103
+ }, expiry_time_in_seconds * 1000);
104
+ _associations[handle] = {provider: provider, type : type, secret: secret};
105
+ callback(null); // Custom implementations may report error as first argument
106
+ }
107
+
108
+ openid.loadAssociation = function(handle, callback)
109
+ {
110
+ if(_isDef(_associations[handle]))
111
+ {
112
+ callback(null, _associations[handle]);
113
+ }
114
+ else
115
+ {
116
+ callback(null, null);
117
+ }
118
+ }
119
+
120
+ openid.removeAssociation = function(handle)
121
+ {
122
+ delete _associations[handle];
123
+ return true;
124
+ }
125
+
126
+ openid.saveDiscoveredInformation = function(key, provider, callback)
127
+ {
128
+ _discoveries[key] = provider;
129
+ return callback(null);
130
+ }
131
+
132
+ openid.loadDiscoveredInformation = function(key, callback)
133
+ {
134
+ if(!_isDef(_discoveries[key]))
135
+ {
136
+ return callback(null, null);
137
+ }
138
+
139
+ return callback(null, _discoveries[key]);
140
+ }
141
+
142
+ var _buildUrl = function(theUrl, params)
143
+ {
144
+ theUrl = url.parse(theUrl, true);
145
+ delete theUrl['search'];
146
+ if(params)
147
+ {
148
+ if(!theUrl.query)
149
+ {
150
+ theUrl.query = params;
151
+ }
152
+ else
153
+ {
154
+ for(var key in params)
155
+ {
156
+ if(hasOwnProperty(params, key))
157
+ {
158
+ theUrl.query[key] = params[key];
159
+ }
160
+ }
161
+ }
162
+ }
163
+
164
+ return url.format(theUrl);
165
+ }
166
+
167
+ var _get = function (getUrl, params, callback, redirects) {
168
+ var options = {
169
+ url: getUrl,
170
+ maxRedirects: redirects || 5,
171
+ qs: params,
172
+ headers: { 'Accept' : 'application/xrds+xml,text/html,text/plain,*/*' }
173
+ };
174
+ request.get(options, function (error, response, body) {
175
+ if (error) {
176
+ callback(error);
177
+ } else {
178
+ callback(body, response.headers, response.statusCode);
179
+ }
180
+ });
181
+ };
182
+
183
+ var _post = function (postUrl, data, callback, redirects) {
184
+ var options = {
185
+ url: postUrl,
186
+ maxRedirects: redirects || 5,
187
+ form: data,
188
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
189
+ };
190
+ request.post(options, function (error, response, body) {
191
+ if (error) {
192
+ callback(error);
193
+ } else {
194
+ callback(body, response.headers, response.statusCode);
195
+ }
196
+ });
197
+ };
198
+
199
+ var _decodePostData = function(data)
200
+ {
201
+ var lines = data.split('\n');
202
+ var result = {};
203
+ for (var i = 0; i < lines.length ; i++) {
204
+ var line = lines[i];
205
+ if (line.length > 0 && line[line.length - 1] == '\r') {
206
+ line = line.substring(0, line.length - 1);
207
+ }
208
+ var colon = line.indexOf(':');
209
+ if (colon === -1)
210
+ {
211
+ continue;
212
+ }
213
+ var key = line.substr(0, line.indexOf(':'));
214
+ var value = line.substr(line.indexOf(':') + 1);
215
+ result[key] = value;
216
+ }
217
+
218
+ return result;
219
+ }
220
+
221
+ var _normalizeIdentifier = function(identifier)
222
+ {
223
+ identifier = identifier.replace(/^\s+|\s+$/g, '');
224
+ if(!identifier)
225
+ return null;
226
+ if(identifier.indexOf('xri://') === 0)
227
+ {
228
+ identifier = identifier.substring(6);
229
+ }
230
+
231
+ if(/^[(=@\+\$!]/.test(identifier))
232
+ {
233
+ return identifier;
234
+ }
235
+
236
+ if(identifier.indexOf('http') === 0)
237
+ {
238
+ return identifier;
239
+ }
240
+ return 'http://' + identifier;
241
+ }
242
+
243
+ var _parseXrds = function(xrdsUrl, xrdsData)
244
+ {
245
+ var services = xrds.parse(xrdsData);
246
+ if(services == null)
247
+ {
248
+ return null;
249
+ }
250
+
251
+ var providers = [];
252
+ for(var i = 0, len = services.length; i < len; ++i)
253
+ {
254
+ var service = services[i];
255
+ var provider = {};
256
+
257
+ provider.endpoint = service.uri;
258
+ if(/https?:\/\/xri./.test(xrdsUrl))
259
+ {
260
+ provider.claimedIdentifier = service.id;
261
+ }
262
+ if(service.type == 'http://specs.openid.net/auth/2.0/signon')
263
+ {
264
+ provider.version = 'http://specs.openid.net/auth/2.0';
265
+ provider.localIdentifier = service.id;
266
+ }
267
+ else if(service.type == 'http://specs.openid.net/auth/2.0/server')
268
+ {
269
+ provider.version = 'http://specs.openid.net/auth/2.0';
270
+ }
271
+ else if(service.type == 'http://openid.net/signon/1.0' ||
272
+ service.type == 'http://openid.net/signon/1.1')
273
+ {
274
+ provider.version = service.type;
275
+ provider.localIdentifier = service.delegate;
276
+ }
277
+ else
278
+ {
279
+ continue;
280
+ }
281
+ providers.push(provider);
282
+ }
283
+
284
+ return providers;
285
+ }
286
+
287
+ var _matchMetaTag = function(html)
288
+ {
289
+ var metaTagMatches = /<meta\s+.*?http-equiv="x-xrds-location"\s+(.*?)>/ig.exec(html);
290
+ if(!metaTagMatches || metaTagMatches.length < 2)
291
+ {
292
+ return null;
293
+ }
294
+
295
+ var contentMatches = /content="(.*?)"/ig.exec(metaTagMatches[1]);
296
+ if(!contentMatches || contentMatches.length < 2)
297
+ {
298
+ return null;
299
+ }
300
+
301
+ return contentMatches[1];
302
+ }
303
+
304
+ var _matchLinkTag = function(html, rel)
305
+ {
306
+ var providerLinkMatches = new RegExp('<link\\s+.*?rel=["\'][^"\']*?' + rel + '[^"\']*?["\'].*?>', 'ig').exec(html);
307
+
308
+ if(!providerLinkMatches || providerLinkMatches.length < 1)
309
+ {
310
+ return null;
311
+ }
312
+
313
+ var href = /href=["'](.*?)["']/ig.exec(providerLinkMatches[0]);
314
+
315
+ if(!href || href.length < 2)
316
+ {
317
+ return null;
318
+ }
319
+ return href[1];
320
+ }
321
+
322
+ var _parseHtml = function(htmlUrl, html, callback, hops)
323
+ {
324
+ var metaUrl = _matchMetaTag(html);
325
+ if(metaUrl != null)
326
+ {
327
+ return _resolveXri(metaUrl, callback, hops + 1);
328
+ }
329
+
330
+ var provider = _matchLinkTag(html, 'openid2.provider');
331
+ if(provider == null)
332
+ {
333
+ provider = _matchLinkTag(html, 'openid.server');
334
+ if(provider == null)
335
+ {
336
+ callback(null);
337
+ }
338
+ else
339
+ {
340
+ var localId = _matchLinkTag(html, 'openid.delegate');
341
+ callback([{
342
+ version: 'http://openid.net/signon/1.1',
343
+ endpoint: provider,
344
+ claimedIdentifier: htmlUrl,
345
+ localIdentifier : localId
346
+ }]);
347
+ }
348
+ }
349
+ else
350
+ {
351
+ var localId = _matchLinkTag(html, 'openid2.local_id');
352
+ callback([{
353
+ version: 'http://specs.openid.net/auth/2.0/signon',
354
+ endpoint: provider,
355
+ claimedIdentifier: htmlUrl,
356
+ localIdentifier : localId
357
+ }]);
358
+ }
359
+ }
360
+
361
+ var _parseHostMeta = function(hostMeta, callback)
362
+ {
363
+ var match = /^Link: <([^\n\r]+)>;/.exec(hostMeta);
364
+ if(match != null)
365
+ {
366
+ var xriUrl = match[0].slice(7,match.length - 4);
367
+ _resolveXri(xriUrl, callback);
368
+ }
369
+ else
370
+ {
371
+ callback(null)
372
+ }
373
+ }
374
+
375
+ var _resolveXri = function(xriUrl, callback, hops)
376
+ {
377
+ if(!hops)
378
+ {
379
+ hops = 1;
380
+ }
381
+ else if(hops >= 5)
382
+ {
383
+ return callback(null);
384
+ }
385
+
386
+ _get(xriUrl, null, function(data, headers, statusCode)
387
+ {
388
+ if(statusCode != 200)
389
+ {
390
+ return callback(null);
391
+ }
392
+
393
+ var xrdsLocation = headers['x-xrds-location'];
394
+ if(_isDef(xrdsLocation))
395
+ {
396
+ _get(xrdsLocation, null, function(data, headers, statusCode)
397
+ {
398
+ if(statusCode != 200 || data == null)
399
+ {
400
+ callback(null);
401
+ }
402
+ else
403
+ {
404
+ callback(_parseXrds(xrdsLocation, data));
405
+ }
406
+ });
407
+ }
408
+ else if(data != null)
409
+ {
410
+ var contentType = headers['content-type'];
411
+ // text/xml is not compliant, but some hosting providers refuse header
412
+ // changes, so text/xml is encountered
413
+ if(contentType && (contentType.indexOf('application/xrds+xml') === 0 || contentType.indexOf('text/xml') === 0))
414
+ {
415
+ return callback(_parseXrds(xriUrl, data));
416
+ }
417
+ else
418
+ {
419
+ return _resolveHtml(xriUrl, callback, hops + 1, data);
420
+ }
421
+ }
422
+ });
423
+ }
424
+
425
+ var _resolveHtml = function(identifier, callback, hops, data)
426
+ {
427
+ if(!hops)
428
+ {
429
+ hops = 1;
430
+ }
431
+ else if(hops >= 5)
432
+ {
433
+ return callback(null);
434
+ }
435
+
436
+ if(data == null)
437
+ {
438
+ _get(identifier, null, function(data, headers, statusCode)
439
+ {
440
+ if(statusCode != 200 || data == null)
441
+ {
442
+ callback(null);
443
+ }
444
+ else
445
+ {
446
+ _parseHtml(identifier, data, callback, hops + 1);
447
+ }
448
+ });
449
+ }
450
+ else
451
+ {
452
+ _parseHtml(identifier, data, callback, hops);
453
+ }
454
+
455
+ }
456
+
457
+ var _resolveHostMeta = function(identifier, strict, callback, fallBackToProxy)
458
+ {
459
+ var host = url.parse(identifier);
460
+ var hostMetaUrl;
461
+ if(fallBackToProxy && !strict)
462
+ {
463
+ hostMetaUrl = 'https://www.google.com/accounts/o8/.well-known/host-meta?hd=' + host.host
464
+ }
465
+ else
466
+ {
467
+ hostMetaUrl = host.protocol + '//' + host.host + '/.well-known/host-meta';
468
+ }
469
+ if(!hostMetaUrl)
470
+ {
471
+ callback(null);
472
+ }
473
+ else
474
+ {
475
+ _get(hostMetaUrl, null, function(data, headers, statusCode)
476
+ {
477
+ if(statusCode != 200 || data == null)
478
+ {
479
+ if(!fallBackToProxy && !strict){
480
+ _resolveHostMeta(identifier, strict, callback, true);
481
+ }
482
+ else{
483
+ callback(null);
484
+ }
485
+ }
486
+ else
487
+ {
488
+ //Attempt to parse the data but if this fails it may be because
489
+ //the response to hostMetaUrl was some other http/html resource.
490
+ //Therefore fallback to the proxy if no providers are found.
491
+ _parseHostMeta(data, function(providers){
492
+ if((providers == null || providers.length == 0) && !fallBackToProxy && !strict){
493
+ _resolveHostMeta(identifier, strict, callback, true);
494
+ }
495
+ else{
496
+ callback(providers);
497
+ }
498
+ });
499
+ }
500
+ });
501
+ }
502
+ }
503
+
504
+ openid.discover = function(identifier, strict, callback)
505
+ {
506
+ identifier = _normalizeIdentifier(identifier);
507
+ if(!identifier)
508
+ {
509
+ return callback({ message: 'Invalid identifier' }, null);
510
+ }
511
+ if(identifier.indexOf('http') !== 0)
512
+ {
513
+ // XRDS
514
+ identifier = 'https://xri.net/' + identifier + '?_xrd_r=application/xrds%2Bxml';
515
+ }
516
+
517
+ // Try XRDS/Yadis discovery
518
+ _resolveXri(identifier, function(providers)
519
+ {
520
+ if(providers == null || providers.length == 0)
521
+ {
522
+ // Fallback to HTML discovery
523
+ _resolveHtml(identifier, function(providers)
524
+ {
525
+ if(providers == null || providers.length == 0){
526
+ _resolveHostMeta(identifier, strict, function(providers){
527
+ callback(null, providers);
528
+ });
529
+ }
530
+ else{
531
+ callback(null, providers);
532
+ }
533
+ });
534
+ }
535
+ else
536
+ {
537
+ // Add claimed identifier to providers with local identifiers
538
+ // and OpenID 1.0/1.1 providers to ensure correct resolution
539
+ // of identities and services
540
+ for(var i = 0, len = providers.length; i < len; ++i)
541
+ {
542
+ var provider = providers[i];
543
+ if(!provider.claimedIdentifier &&
544
+ (provider.localIdentifier || provider.version.indexOf('2.0') === -1))
545
+ {
546
+ provider.claimedIdentifier = identifier;
547
+ }
548
+ }
549
+ callback(null, providers);
550
+ }
551
+ });
552
+ }
553
+
554
+ var _createDiffieHellmanKeyExchange = function(algorithm)
555
+ {
556
+ var defaultPrime = 'ANz5OguIOXLsDhmYmsWizjEOHTdxfo2Vcbt2I3MYZuYe91ouJ4mLBX+YkcLiemOcPym2CBRYHNOyyjmG0mg3BVd9RcLn5S3IHHoXGHblzqdLFEi/368Ygo79JRnxTkXjgmY0rxlJ5bU1zIKaSDuKdiI+XUkKJX8Fvf8W8vsixYOr';
557
+
558
+ var dh = crypto.createDiffieHellman(defaultPrime, 'base64');
559
+
560
+ dh.generateKeys();
561
+
562
+ return dh;
563
+ }
564
+
565
+ openid.associate = function(provider, callback, strict, algorithm)
566
+ {
567
+ var params = _generateAssociationRequestParameters(provider.version, algorithm);
568
+ if(!_isDef(algorithm))
569
+ {
570
+ algorithm = 'DH-SHA256';
571
+ }
572
+
573
+ var dh = null;
574
+ if(algorithm.indexOf('no-encryption') === -1)
575
+ {
576
+ dh = _createDiffieHellmanKeyExchange(algorithm);
577
+ params['openid.dh_modulus'] = _toBase64(dh.getPrime('binary'));
578
+ params['openid.dh_gen'] = _toBase64(dh.getGenerator('binary'));
579
+ params['openid.dh_consumer_public'] = _toBase64(dh.getPublicKey('binary'));
580
+ }
581
+
582
+ _post(provider.endpoint, params, function(data, headers, statusCode)
583
+ {
584
+ if ((statusCode != 200 && statusCode != 400) || data === null)
585
+ {
586
+ return callback({
587
+ message: 'HTTP request failed'
588
+ }, {
589
+ error: 'HTTP request failed',
590
+ error_code: '' + statusCode,
591
+ ns: 'http://specs.openid.net/auth/2.0'
592
+ });
593
+ }
594
+
595
+ data = _decodePostData(data);
596
+
597
+ if(data.error_code == 'unsupported-type' || !_isDef(data.ns))
598
+ {
599
+ if(algorithm == 'DH-SHA1')
600
+ {
601
+ if(strict && provider.endpoint.toLowerCase().indexOf('https:') !== 0)
602
+ {
603
+ return callback({ message: 'Channel is insecure and no encryption method is supported by provider' }, null);
604
+ }
605
+ else
606
+ {
607
+ return openid.associate(provider, callback, strict, 'no-encryption-256');
608
+ }
609
+ }
610
+ else if(algorithm == 'no-encryption-256')
611
+ {
612
+ if(strict && provider.endpoint.toLowerCase().indexOf('https:') !== 0)
613
+ {
614
+ return callback('Channel is insecure and no encryption method is supported by provider', null);
615
+ }
616
+ /*else if(provider.version.indexOf('2.0') === -1)
617
+ {
618
+ // 2011-07-22: This is an OpenID 1.0/1.1 provider which means
619
+ // HMAC-SHA1 has already been attempted with a blank session
620
+ // type as per the OpenID 1.0/1.1 specification.
621
+ // (See http://openid.net/specs/openid-authentication-1_1.html#mode_associate)
622
+ // However, providers like wordpress.com don't follow the
623
+ // standard and reject these requests, but accept OpenID 2.0
624
+ // style requests without a session type, so we have to give
625
+ // those a shot as well.
626
+ callback({ message: 'Provider is OpenID 1.0/1.1 and does not support OpenID 1.0/1.1 association.' });
627
+ }*/
628
+ else
629
+ {
630
+ return openid.associate(provider, callback, strict, 'no-encryption');
631
+ }
632
+ }
633
+ else if(algorithm == 'DH-SHA256')
634
+ {
635
+ return openid.associate(provider, callback, strict, 'DH-SHA1');
636
+ }
637
+ }
638
+
639
+ if (data.error)
640
+ {
641
+ callback({ message: data.error }, data);
642
+ }
643
+ else
644
+ {
645
+ var secret = null;
646
+
647
+ var hashAlgorithm = algorithm.indexOf('256') !== -1 ? 'sha256' : 'sha1';
648
+
649
+ if(algorithm.indexOf('no-encryption') !== -1)
650
+ {
651
+ secret = data.mac_key;
652
+ }
653
+ else
654
+ {
655
+ var serverPublic = _fromBase64(data.dh_server_public);
656
+ var sharedSecret = convert.btwoc(dh.computeSecret(serverPublic, 'binary', 'binary'));
657
+ var hash = crypto.createHash(hashAlgorithm);
658
+ hash.update(sharedSecret);
659
+ sharedSecret = hash.digest('binary');
660
+ var encMacKey = convert.base64.decode(data.enc_mac_key);
661
+ secret = convert.base64.encode(_xor(encMacKey, sharedSecret));
662
+ }
663
+
664
+ if (!_isDef(data.assoc_handle)) {
665
+ return callback({ message: 'OpenID provider does not seem to support association; you need to use stateless mode'}, null);
666
+ }
667
+
668
+ openid.saveAssociation(provider, hashAlgorithm,
669
+ data.assoc_handle, secret, data.expires_in * 1, function(error)
670
+ {
671
+ if(error)
672
+ {
673
+ return callback(error);
674
+ }
675
+ callback(null, data);
676
+ });
677
+ }
678
+ });
679
+ }
680
+
681
+ var _generateAssociationRequestParameters = function(version, algorithm)
682
+ {
683
+ var params = {
684
+ 'openid.mode' : 'associate',
685
+ };
686
+
687
+ if(version.indexOf('2.0') !== -1)
688
+ {
689
+ params['openid.ns'] = 'http://specs.openid.net/auth/2.0';
690
+ }
691
+
692
+ if(algorithm == 'DH-SHA1')
693
+ {
694
+ params['openid.assoc_type'] = 'HMAC-SHA1';
695
+ params['openid.session_type'] = 'DH-SHA1';
696
+ }
697
+ else if(algorithm == 'no-encryption-256')
698
+ {
699
+ if(version.indexOf('2.0') === -1)
700
+ {
701
+ params['openid.session_type'] = ''; // OpenID 1.0/1.1 requires blank
702
+ params['openid.assoc_type'] = 'HMAC-SHA1';
703
+ }
704
+ else
705
+ {
706
+ params['openid.session_type'] = 'no-encryption';
707
+ params['openid.assoc_type'] = 'HMAC-SHA256';
708
+ }
709
+ }
710
+ else if(algorithm == 'no-encryption')
711
+ {
712
+ if(version.indexOf('2.0') !== -1)
713
+ {
714
+ params['openid.session_type'] = 'no-encryption';
715
+ }
716
+ params['openid.assoc_type'] = 'HMAC-SHA1';
717
+ }
718
+ else
719
+ {
720
+ params['openid.assoc_type'] = 'HMAC-SHA256';
721
+ params['openid.session_type'] = 'DH-SHA256';
722
+ }
723
+
724
+ return params;
725
+ }
726
+
727
+ openid.authenticate = function(identifier, returnUrl, realm, immediate, stateless, callback, extensions, strict)
728
+ {
729
+ openid.discover(identifier, strict, function(error, providers)
730
+ {
731
+ if(error)
732
+ {
733
+ return callback(error);
734
+ }
735
+ if(!providers || providers.length === 0)
736
+ {
737
+ return callback({ message: 'No providers found for the given identifier' }, null);
738
+ }
739
+
740
+ var providerIndex = -1;
741
+
742
+ (function chooseProvider(error, authUrl)
743
+ {
744
+ if(!error && authUrl)
745
+ {
746
+ var provider = providers[providerIndex];
747
+
748
+ if(provider.claimedIdentifier)
749
+ {
750
+ var useLocalIdentifierAsKey = provider.version.indexOf('2.0') === -1 && provider.localIdentifier && provider.claimedIdentifier != provider.localIdentifier;
751
+
752
+ return openid.saveDiscoveredInformation(useLocalIdentifierAsKey ? provider.localIdentifier : provider.claimedIdentifier,
753
+ provider, function(error)
754
+ {
755
+ if(error)
756
+ {
757
+ return callback(error);
758
+ }
759
+ return callback(null, authUrl);
760
+ });
761
+ }
762
+ else if(provider.version.indexOf('2.0') !== -1)
763
+ {
764
+ return callback(null, authUrl);
765
+ }
766
+ else
767
+ {
768
+ chooseProvider({ message: 'OpenID 1.0/1.1 provider cannot be used without a claimed identifier' });
769
+ }
770
+ }
771
+ if(++providerIndex >= providers.length)
772
+ {
773
+ return callback({ message: 'No usable providers found for the given identifier' }, null);
774
+ }
775
+
776
+ var currentProvider = providers[providerIndex];
777
+ if(stateless)
778
+ {
779
+ _requestAuthentication(currentProvider, null, returnUrl,
780
+ realm, immediate, extensions || {}, chooseProvider);
781
+ }
782
+
783
+ else
784
+ {
785
+ openid.associate(currentProvider, function(error, answer)
786
+ {
787
+ if(error || !answer || answer.error)
788
+ {
789
+ chooseProvider(error || answer.error, null);
790
+ }
791
+ else
792
+ {
793
+ _requestAuthentication(currentProvider, answer.assoc_handle, returnUrl,
794
+ realm, immediate, extensions || {}, chooseProvider);
795
+ }
796
+ });
797
+
798
+ }
799
+ })();
800
+ });
801
+ }
802
+
803
+ var _requestAuthentication = function(provider, assoc_handle, returnUrl, realm, immediate, extensions, callback)
804
+ {
805
+ var params = {
806
+ 'openid.mode' : immediate ? 'checkid_immediate' : 'checkid_setup'
807
+ };
808
+
809
+ if(provider.version.indexOf('2.0') !== -1)
810
+ {
811
+ params['openid.ns'] = 'http://specs.openid.net/auth/2.0';
812
+ }
813
+
814
+ for (var i in extensions)
815
+ {
816
+ if(!hasOwnProperty(extensions, i))
817
+ {
818
+ continue;
819
+ }
820
+
821
+ var extension = extensions[i];
822
+ for (var key in extension.requestParams)
823
+ {
824
+ if (!hasOwnProperty(extension.requestParams, key)) { continue; }
825
+ params[key] = extension.requestParams[key];
826
+ }
827
+ }
828
+
829
+ if(provider.claimedIdentifier)
830
+ {
831
+ params['openid.claimed_id'] = provider.claimedIdentifier;
832
+ if(provider.localIdentifier)
833
+ {
834
+ params['openid.identity'] = provider.localIdentifier;
835
+ }
836
+ else
837
+ {
838
+ params['openid.identity'] = provider.claimedIdentifier;
839
+ }
840
+ }
841
+ else if(provider.version.indexOf('2.0') !== -1)
842
+ {
843
+ params['openid.claimed_id'] = params['openid.identity'] =
844
+ 'http://specs.openid.net/auth/2.0/identifier_select';
845
+ }
846
+ else {
847
+ return callback({ message: 'OpenID 1.0/1.1 provider cannot be used without a claimed identifier' });
848
+ }
849
+
850
+ if(assoc_handle)
851
+ {
852
+ params['openid.assoc_handle'] = assoc_handle;
853
+ }
854
+
855
+ if(returnUrl)
856
+ {
857
+ // Value should be missing if RP does not want
858
+ // user to be sent back
859
+ params['openid.return_to'] = returnUrl;
860
+ }
861
+
862
+ if(realm)
863
+ {
864
+ if(provider.version.indexOf('2.0') !== -1) {
865
+ params['openid.realm'] = realm;
866
+ }
867
+ else {
868
+ params['openid.trust_root'] = realm;
869
+ }
870
+ }
871
+ else if(!returnUrl)
872
+ {
873
+ return callback({ message: 'No return URL or realm specified' });
874
+ }
875
+
876
+ callback(null, _buildUrl(provider.endpoint, params));
877
+ }
878
+
879
+ openid.verifyAssertion = function(requestOrUrl, callback, stateless, extensions, strict)
880
+ {
881
+ extensions = extensions || {};
882
+ var assertionUrl = requestOrUrl;
883
+ if(typeof(requestOrUrl) !== typeof(''))
884
+ {
885
+ if(requestOrUrl.method == 'POST') {
886
+ if((requestOrUrl.headers['content-type'] || '').toLowerCase().indexOf('application/x-www-form-urlencoded') === 0) {
887
+ // POST response received
888
+ var data = '';
889
+
890
+ requestOrUrl.on('data', function(chunk) {
891
+ data += chunk;
892
+ });
893
+
894
+ requestOrUrl.on('end', function() {
895
+ var params = querystring.parse(data);
896
+ return _verifyAssertionData(params, callback, stateless, extensions, strict);
897
+ });
898
+ }
899
+ else {
900
+ return callback({ message: 'Invalid POST response from OpenID provider' });
901
+ }
902
+
903
+ return; // Avoid falling through to GET method assertion
904
+ }
905
+ else if(requestOrUrl.method != 'GET') {
906
+ return callback({ message: 'Invalid request method from OpenID provider' });
907
+ }
908
+ assertionUrl = requestOrUrl.url;
909
+ }
910
+
911
+ assertionUrl = url.parse(assertionUrl, true);
912
+ var params = assertionUrl.query;
913
+
914
+ return _verifyAssertionData(params, callback, stateless, extensions, strict);
915
+ }
916
+
917
+ <<<<<<< HEAD
918
+ =======
919
+ var _verifyReturnUrl = function (assertionUrl, originalReturnUrl) {
920
+ var receivedReturnUrl = assertionUrl.query['openid.return_to'];
921
+ if (!_isDef(receivedReturnUrl)) {
922
+ return false;
923
+ }
924
+
925
+ receivedReturnUrl = url.parse(receivedReturnUrl, true);
926
+ if (!receivedReturnUrl) {
927
+ return false;
928
+ }
929
+ originalReturnUrl = url.parse(originalReturnUrl, true);
930
+ if (!originalReturnUrl) {
931
+ return false;
932
+ }
933
+
934
+ if (originalReturnUrl.protocol !== receivedReturnUrl.protocol || // Verify scheme against original return URL
935
+ originalReturnUrl.host !== receivedReturnUrl.host || // Verify authority against original return URL
936
+ originalReturnUrl.pathname !== receivedReturnUrl.pathname) { // Verify path against current request URL
937
+ return false;
938
+ }
939
+
940
+ // Any query parameters that are present in the "openid.return_to" URL MUST also be present
941
+ // with the same values in the URL of the HTTP request the RP received
942
+ for (var param in receivedReturnUrl.query) {
943
+ if (hasOwnProperty(receivedReturnUrl.query, param) && receivedReturnUrl.query[param] !== assertionUrl.query[param]) {
944
+ return false;
945
+ }
946
+ }
947
+
948
+ return true;
949
+ }
950
+
951
+ >>>>>>> 2e4b4f0... Fix a possible typo in `verifyReturnUrl` routine
952
+ var _verifyAssertionData = function(params, callback, stateless, extensions, strict) {
953
+ var assertionError = _getAssertionError(params);
954
+ if(assertionError)
955
+ {
956
+ return callback({ message: assertionError }, { authenticated: false });
957
+ }
958
+
959
+ if (!_invalidateAssociationHandleIfRequested(params)) {
960
+ return callback({ message: 'Unable to invalidate association handle'});
961
+ }
962
+
963
+ // TODO: Check nonce if OpenID 2.0
964
+ _verifyDiscoveredInformation(params, stateless, extensions, strict, function(error, result)
965
+ {
966
+ return callback(error, result);
967
+ });
968
+ };
969
+
970
+ var _getAssertionError = function(params)
971
+ {
972
+ if(!_isDef(params))
973
+ {
974
+ return 'Assertion request is malformed';
975
+ }
976
+ else if(params['openid.mode'] == 'error')
977
+ {
978
+ return params['openid.error'];
979
+ }
980
+ else if(params['openid.mode'] == 'cancel')
981
+ {
982
+ return 'Authentication cancelled';
983
+ }
984
+
985
+ return null;
986
+ }
987
+
988
+ var _invalidateAssociationHandleIfRequested = function(params)
989
+ {
990
+ if (params['is_valid'] == 'true' && _isDef(params['openid.invalidate_handle'])) {
991
+ if(!openid.removeAssociation(params['openid.invalidate_handle'])) {
992
+ return false;
993
+ }
994
+ }
995
+
996
+ return true;
997
+ }
998
+
999
+ var _verifyDiscoveredInformation = function(params, stateless, extensions, strict, callback)
1000
+ {
1001
+ var claimedIdentifier = params['openid.claimed_id'];
1002
+ var useLocalIdentifierAsKey = false;
1003
+ if(!_isDef(claimedIdentifier))
1004
+ {
1005
+ if(!_isDef(params['openid.ns']))
1006
+ {
1007
+ // OpenID 1.0/1.1 response without a claimed identifier
1008
+ // We need to load discovered information using the
1009
+ // local identifier
1010
+ useLocalIdentifierAsKey = true;
1011
+ }
1012
+ else {
1013
+ // OpenID 2.0+:
1014
+ // If there is no claimed identifier, then the
1015
+ // assertion is not about an identity
1016
+ return callback(null, { authenticated: false });
1017
+ }
1018
+ }
1019
+
1020
+ if (useLocalIdentifierAsKey) {
1021
+ claimedIdentifier = params['openid.identity'];
1022
+ }
1023
+
1024
+ claimedIdentifier = _getCanonicalClaimedIdentifier(claimedIdentifier);
1025
+ openid.loadDiscoveredInformation(claimedIdentifier, function(error, provider)
1026
+ {
1027
+ if(error)
1028
+ {
1029
+ return callback({ message: 'An error occured when loading previously discovered information about the claimed identifier' });
1030
+ }
1031
+
1032
+ if(provider)
1033
+ {
1034
+ return _verifyAssertionAgainstProviders([provider], params, stateless, extensions, callback);
1035
+ }
1036
+ else if (useLocalIdentifierAsKey) {
1037
+ return callback({ message: 'OpenID 1.0/1.1 response received, but no information has been discovered about the provider. It is likely that this is a fraudulent authentication response.' });
1038
+ }
1039
+
1040
+ openid.discover(claimedIdentifier, strict, function(error, providers)
1041
+ {
1042
+ if(error)
1043
+ {
1044
+ return callback(error);
1045
+ }
1046
+ if(!providers || !providers.length)
1047
+ {
1048
+ return callback({ message: 'No OpenID provider was discovered for the asserted claimed identifier' });
1049
+ }
1050
+
1051
+ _verifyAssertionAgainstProviders(providers, params, stateless, extensions, callback);
1052
+ });
1053
+ });
1054
+ }
1055
+
1056
+ var _verifyAssertionAgainstProviders = function(providers, params, stateless, extensions, callback)
1057
+ {
1058
+ for(var i = 0; i < providers.length; ++i)
1059
+ {
1060
+ var provider = providers[i];
1061
+ if(!!params['openid.ns'] && (!provider.version || provider.version.indexOf(params['openid.ns']) !== 0))
1062
+ {
1063
+ continue;
1064
+ }
1065
+
1066
+ if(!!provider.version && provider.version.indexOf('2.0') !== -1)
1067
+ {
1068
+ var endpoint = params['openid.op_endpoint'];
1069
+ if (provider.endpoint != endpoint)
1070
+ {
1071
+ continue;
1072
+ }
1073
+ if(provider.claimedIdentifier) {
1074
+ var claimedIdentifier = _getCanonicalClaimedIdentifier(params['openid.claimed_id']);
1075
+ if(provider.claimedIdentifier != claimedIdentifier) {
1076
+ return callback({ message: 'Claimed identifier in assertion response does not match discovered claimed identifier' });
1077
+ }
1078
+ }
1079
+ }
1080
+
1081
+ if(!!provider.localIdentifier && provider.localIdentifier != params['openid.identity'])
1082
+ {
1083
+ return callback({ message: 'Identity in assertion response does not match discovered local identifier' });
1084
+ }
1085
+
1086
+ return _checkSignature(params, provider, stateless, function(error, result)
1087
+ {
1088
+ if(error)
1089
+ {
1090
+ return callback(error);
1091
+ }
1092
+ if(extensions && result.authenticated)
1093
+ {
1094
+ for(var ext in extensions)
1095
+ {
1096
+ if (!hasOwnProperty(extensions, ext))
1097
+ {
1098
+ continue;
1099
+ }
1100
+ var instance = extensions[ext];
1101
+ instance.fillResult(params, result);
1102
+ }
1103
+ }
1104
+
1105
+ return callback(null, result);
1106
+ });
1107
+ }
1108
+
1109
+ callback({ message: 'No valid providers were discovered for the asserted claimed identifier' });
1110
+ }
1111
+
1112
+ var _checkSignature = function(params, provider, stateless, callback)
1113
+ {
1114
+ if(!_isDef(params['openid.signed']) ||
1115
+ !_isDef(params['openid.sig']))
1116
+ {
1117
+ return callback({ message: 'No signature in response' }, { authenticated: false });
1118
+ }
1119
+
1120
+ if(stateless)
1121
+ {
1122
+ _checkSignatureUsingProvider(params, provider, callback);
1123
+ }
1124
+ else
1125
+ {
1126
+ _checkSignatureUsingAssociation(params, callback);
1127
+ }
1128
+ }
1129
+
1130
+ var _checkSignatureUsingAssociation = function(params, callback)
1131
+ {
1132
+ if (!_isDef(params['openid.assoc_handle']))
1133
+ {
1134
+ return callback({ message: 'No association handle in provider response. Find out whether the provider supports associations and/or use stateless mode.' });
1135
+ }
1136
+ openid.loadAssociation(params['openid.assoc_handle'], function(error, association)
1137
+ {
1138
+ if(error)
1139
+ {
1140
+ return callback({ message: 'Error loading association' }, { authenticated: false });
1141
+ }
1142
+ if(!association)
1143
+ {
1144
+ return callback({ message:'Invalid association handle' }, { authenticated: false });
1145
+ }
1146
+ if(association.provider.version.indexOf('2.0') !== -1 && association.provider.endpoint !== params['openid.op_endpoint'])
1147
+ {
1148
+ return callback({ message:'Association handle does not match provided endpoint' }, {authenticated: false});
1149
+ }
1150
+
1151
+ var message = '';
1152
+ var signedParams = params['openid.signed'].split(',');
1153
+ for(var i = 0; i < signedParams.length; i++)
1154
+ {
1155
+ var param = signedParams[i];
1156
+ var value = params['openid.' + param];
1157
+ if(!_isDef(value))
1158
+ {
1159
+ return callback({ message: 'At least one parameter referred in signature is not present in response' }, { authenticated: false });
1160
+ }
1161
+ message += param + ':' + value + '\n';
1162
+ }
1163
+
1164
+ var hmac = crypto.createHmac(association.type, convert.base64.decode(association.secret));
1165
+ hmac.update(message, 'utf8');
1166
+ var ourSignature = hmac.digest('base64');
1167
+
1168
+ if(ourSignature == params['openid.sig'])
1169
+ {
1170
+ callback(null, { authenticated: true, claimedIdentifier: association.provider.version.indexOf('2.0') !== -1 ? params['openid.claimed_id'] : association.provider.claimedIdentifier });
1171
+ }
1172
+ else
1173
+ {
1174
+ callback({ message: 'Invalid signature' }, { authenticated: false });
1175
+ }
1176
+ });
1177
+ }
1178
+
1179
+ var _checkSignatureUsingProvider = function(params, provider, callback)
1180
+ {
1181
+ var requestParams =
1182
+ {
1183
+ 'openid.mode' : 'check_authentication'
1184
+ };
1185
+ for(var key in params)
1186
+ {
1187
+ if(hasOwnProperty(params, key) && key != 'openid.mode')
1188
+ {
1189
+ requestParams[key] = params[key];
1190
+ }
1191
+ }
1192
+
1193
+ _post(_isDef(params['openid.ns']) ? (params['openid.op_endpoint'] || provider.endpoint) : provider.endpoint, requestParams, function(data, headers, statusCode)
1194
+ {
1195
+ if(statusCode != 200 || data == null)
1196
+ {
1197
+ return callback({ message: 'Invalid assertion response from provider' }, { authenticated: false });
1198
+ }
1199
+ else
1200
+ {
1201
+ data = _decodePostData(data);
1202
+ if(data['is_valid'] == 'true')
1203
+ {
1204
+ return callback(null, { authenticated: true, claimedIdentifier: provider.version.indexOf('2.0') !== -1 ? params['openid.claimed_id'] : params['openid.identity'] });
1205
+ }
1206
+ else
1207
+ {
1208
+ return callback({ message: 'Invalid signature' }, { authenticated: false });
1209
+ }
1210
+ }
1211
+ });
1212
+
1213
+ }
1214
+
1215
+
1216
+ var _getCanonicalClaimedIdentifier = function(claimedIdentifier) {
1217
+ if(!claimedIdentifier) {
1218
+ return claimedIdentifier;
1219
+ }
1220
+
1221
+ var index = claimedIdentifier.indexOf('#');
1222
+ if (index !== -1) {
1223
+ return claimedIdentifier.substring(0, index);
1224
+ }
1225
+
1226
+ return claimedIdentifier;
1227
+ };
1228
+
1229
+ /* ==================================================================
1230
+ * Extensions
1231
+ * ==================================================================
1232
+ */
1233
+
1234
+ var _getExtensionAlias = function(params, ns)
1235
+ {
1236
+ for (var k in params)
1237
+ if (params[k] == ns)
1238
+ return k.replace("openid.ns.", "");
1239
+ }
1240
+
1241
+ /*
1242
+ * Simple Registration Extension
1243
+ * http://openid.net/specs/openid-simple-registration-extension-1_1-01.html
1244
+ */
1245
+
1246
+ var sreg_keys = ['nickname', 'email', 'fullname', 'dob', 'gender', 'postcode', 'country', 'language', 'timezone'];
1247
+
1248
+ openid.SimpleRegistration = function SimpleRegistration(options)
1249
+ {
1250
+ this.requestParams = {'openid.ns.sreg': 'http://openid.net/extensions/sreg/1.1'};
1251
+ if (options.policy_url)
1252
+ this.requestParams['openid.sreg.policy_url'] = options.policy_url;
1253
+ var required = [];
1254
+ var optional = [];
1255
+ for (var i = 0; i < sreg_keys.length; i++)
1256
+ {
1257
+ var key = sreg_keys[i];
1258
+ if (options[key])
1259
+ {
1260
+ if (options[key] == 'required')
1261
+ {
1262
+ required.push(key);
1263
+ }
1264
+ else
1265
+ {
1266
+ optional.push(key);
1267
+ }
1268
+ }
1269
+ if (required.length)
1270
+ {
1271
+ this.requestParams['openid.sreg.required'] = required.join(',');
1272
+ }
1273
+ if (optional.length)
1274
+ {
1275
+ this.requestParams['openid.sreg.optional'] = optional.join(',');
1276
+ }
1277
+ }
1278
+ };
1279
+
1280
+ openid.SimpleRegistration.prototype.fillResult = function(params, result)
1281
+ {
1282
+ var extension = _getExtensionAlias(params, 'http://openid.net/extensions/sreg/1.1') || 'sreg';
1283
+ for (var i = 0; i < sreg_keys.length; i++)
1284
+ {
1285
+ var key = sreg_keys[i];
1286
+ if (params['openid.' + extension + '.' + key])
1287
+ {
1288
+ result[key] = params['openid.' + extension + '.' + key];
1289
+ }
1290
+ }
1291
+ };
1292
+
1293
+ /*
1294
+ * User Interface Extension
1295
+ * http://svn.openid.net/repos/specifications/user_interface/1.0/trunk/openid-user-interface-extension-1_0.html
1296
+ */
1297
+ openid.UserInterface = function UserInterface(options)
1298
+ {
1299
+ if (typeof(options) != 'object')
1300
+ {
1301
+ options = { mode: options || 'popup' };
1302
+ }
1303
+
1304
+ this.requestParams = {'openid.ns.ui': 'http://specs.openid.net/extensions/ui/1.0'};
1305
+ for (var k in options)
1306
+ {
1307
+ this.requestParams['openid.ui.' + k] = options[k];
1308
+ }
1309
+ };
1310
+
1311
+ openid.UserInterface.prototype.fillResult = function(params, result)
1312
+ {
1313
+ // TODO: Fill results
1314
+ }
1315
+
1316
+ /*
1317
+ * Attribute Exchange Extension
1318
+ * http://openid.net/specs/openid-attribute-exchange-1_0.html
1319
+ * Also see:
1320
+ * - http://www.axschema.org/types/
1321
+ * - http://code.google.com/intl/en-US/apis/accounts/docs/OpenID.html#Parameters
1322
+ */
1323
+
1324
+ var attributeMapping =
1325
+ {
1326
+ 'http://axschema.org/contact/country/home': 'country'
1327
+ , 'http://axschema.org/contact/email': 'email'
1328
+ , 'http://axschema.org/namePerson/first': 'firstname'
1329
+ , 'http://axschema.org/pref/language': 'language'
1330
+ , 'http://axschema.org/namePerson/last': 'lastname'
1331
+ // The following are not in the Google document:
1332
+ , 'http://axschema.org/namePerson/friendly': 'nickname'
1333
+ , 'http://axschema.org/namePerson': 'fullname'
1334
+ };
1335
+
1336
+ openid.AttributeExchange = function AttributeExchange(options)
1337
+ {
1338
+ this.requestParams = {'openid.ns.ax': 'http://openid.net/srv/ax/1.0',
1339
+ 'openid.ax.mode' : 'fetch_request'};
1340
+ var required = [];
1341
+ var optional = [];
1342
+ for (var ns in options)
1343
+ {
1344
+ if (!hasOwnProperty(options, ns)) { continue; }
1345
+ if (options[ns] == 'required')
1346
+ {
1347
+ required.push(ns);
1348
+ }
1349
+ else
1350
+ {
1351
+ optional.push(ns);
1352
+ }
1353
+ }
1354
+ var self = this;
1355
+ required = required.map(function(ns, i)
1356
+ {
1357
+ var attr = attributeMapping[ns] || 'req' + i;
1358
+ self.requestParams['openid.ax.type.' + attr] = ns;
1359
+ return attr;
1360
+ });
1361
+ optional = optional.map(function(ns, i)
1362
+ {
1363
+ var attr = attributeMapping[ns] || 'opt' + i;
1364
+ self.requestParams['openid.ax.type.' + attr] = ns;
1365
+ return attr;
1366
+ });
1367
+ if (required.length)
1368
+ {
1369
+ this.requestParams['openid.ax.required'] = required.join(',');
1370
+ }
1371
+ if (optional.length)
1372
+ {
1373
+ this.requestParams['openid.ax.if_available'] = optional.join(',');
1374
+ }
1375
+ }
1376
+
1377
+ openid.AttributeExchange.prototype.fillResult = function(params, result)
1378
+ {
1379
+ var extension = _getExtensionAlias(params, 'http://openid.net/srv/ax/1.0') || 'ax';
1380
+ var regex = new RegExp('^openid\\.' + extension + '\\.(value|type|count)\\.(\\w+)(\\.(\\d+)){0,1}$');
1381
+ var aliases = {};
1382
+ var counters = {};
1383
+ var values = {};
1384
+ for (var k in params)
1385
+ {
1386
+ if (!hasOwnProperty(params, k)) { continue; }
1387
+ var matches = k.match(regex);
1388
+ if (!matches)
1389
+ {
1390
+ continue;
1391
+ }
1392
+ if (matches[1] == 'type')
1393
+ {
1394
+ aliases[params[k]] = matches[2];
1395
+ }
1396
+ else if (matches[1] == 'count')
1397
+ {
1398
+ //counter sanitization
1399
+ var count = parseInt(params[k], 10);
1400
+
1401
+ // values number limitation (potential attack by overflow ?)
1402
+ counters[matches[2]] = (count < AX_MAX_VALUES_COUNT) ? count : AX_MAX_VALUES_COUNT ;
1403
+ }
1404
+ else
1405
+ {
1406
+ if (matches[3])
1407
+ {
1408
+ //matches multi-value, aka "count" aliases
1409
+
1410
+ //counter sanitization
1411
+ var count = parseInt(matches[4], 10);
1412
+
1413
+ // "in bounds" verification
1414
+ if (count > 0 && count <= (counters[matches[2]] || AX_MAX_VALUES_COUNT))
1415
+ {
1416
+ if (!values[matches[2]]) {
1417
+ values[matches[2]] = [];
1418
+ }
1419
+ values[matches[2]][count-1] = params[k];
1420
+ }
1421
+ }
1422
+ else
1423
+ {
1424
+ //matches single-value aliases
1425
+ values[matches[2]] = params[k];
1426
+ }
1427
+ }
1428
+ }
1429
+ for (var ns in aliases)
1430
+ {
1431
+ if (aliases[ns] in values)
1432
+ {
1433
+ result[aliases[ns]] = values[aliases[ns]];
1434
+ result[ns] = values[aliases[ns]];
1435
+ }
1436
+ }
1437
+ }
1438
+
1439
+ openid.OAuthHybrid = function(options)
1440
+ {
1441
+ this.requestParams = {
1442
+ 'openid.ns.oauth' : 'http://specs.openid.net/extensions/oauth/1.0',
1443
+ 'openid.oauth.consumer' : options['consumerKey'],
1444
+ 'openid.oauth.scope' : options['scope']};
1445
+ }
1446
+
1447
+ openid.OAuthHybrid.prototype.fillResult = function(params, result)
1448
+ {
1449
+ var extension = _getExtensionAlias(params, 'http://specs.openid.net/extensions/oauth/1.0') || 'oauth'
1450
+ , token_attr = 'openid.' + extension + '.request_token';
1451
+
1452
+
1453
+ if(params[token_attr] !== undefined)
1454
+ {
1455
+ result['request_token'] = params[token_attr];
1456
+ }
1457
+ };
1458
+
1459
+ /*
1460
+ * Provider Authentication Policy Extension (PAPE)
1461
+ * http://openid.net/specs/openid-provider-authentication-policy-extension-1_0.html
1462
+ *
1463
+ * Note that this extension does not validate that the provider is obeying the
1464
+ * authentication request, it only allows the request to be made.
1465
+ *
1466
+ * TODO: verify requested 'max_auth_age' against response 'auth_time'
1467
+ * TODO: verify requested 'auth_level.ns.<cust>' (etc) against response 'auth_level.ns.<cust>'
1468
+ * TODO: verify requested 'preferred_auth_policies' against response 'auth_policies'
1469
+ *
1470
+ */
1471
+
1472
+ /* Just the keys that aren't open to customisation */
1473
+ var pape_request_keys = ['max_auth_age', 'preferred_auth_policies', 'preferred_auth_level_types' ];
1474
+ var pape_response_keys = ['auth_policies', 'auth_time']
1475
+
1476
+ /* Some short-hand mappings for auth_policies */
1477
+ var papePolicyNameMap =
1478
+ {
1479
+ 'phishing-resistant': 'http://schemas.openid.net/pape/policies/2007/06/phishing-resistant',
1480
+ 'multi-factor': 'http://schemas.openid.net/pape/policies/2007/06/multi-factor',
1481
+ 'multi-factor-physical': 'http://schemas.openid.net/pape/policies/2007/06/multi-factor-physical',
1482
+ 'none' : 'http://schemas.openid.net/pape/policies/2007/06/none'
1483
+ }
1484
+
1485
+ openid.PAPE = function PAPE(options)
1486
+ {
1487
+ this.requestParams = {'openid.ns.pape': 'http://specs.openid.net/extensions/pape/1.0'};
1488
+ for (var k in options)
1489
+ {
1490
+ if (k === 'preferred_auth_policies') {
1491
+ this.requestParams['openid.pape.' + k] = _getLongPolicyName(options[k]);
1492
+ } else {
1493
+ this.requestParams['openid.pape.' + k] = options[k];
1494
+ }
1495
+ }
1496
+ var util = require('util');
1497
+ };
1498
+
1499
+ /* you can express multiple pape 'preferred_auth_policies', so replace each
1500
+ * with the full policy URI as per papePolicyNameMapping.
1501
+ */
1502
+ var _getLongPolicyName = function(policyNames) {
1503
+ var policies = policyNames.split(' ');
1504
+ for (var i=0; i<policies.length; i++) {
1505
+ if (policies[i] in papePolicyNameMap) {
1506
+ policies[i] = papePolicyNameMap[policies[i]];
1507
+ }
1508
+ }
1509
+ return policies.join(' ');
1510
+ }
1511
+
1512
+ var _getShortPolicyName = function(policyNames) {
1513
+ var policies = policyNames.split(' ');
1514
+ for (var i=0; i<policies.length; i++) {
1515
+ for (shortName in papePolicyNameMap) {
1516
+ if (papePolicyNameMap[shortName] === policies[i]) {
1517
+ policies[i] = shortName;
1518
+ }
1519
+ }
1520
+ }
1521
+ return policies.join(' ');
1522
+ }
1523
+
1524
+ openid.PAPE.prototype.fillResult = function(params, result)
1525
+ {
1526
+ var extension = _getExtensionAlias(params, 'http://specs.openid.net/extensions/pape/1.0') || 'pape';
1527
+ var paramString = 'openid.' + extension + '.';
1528
+ var thisParam;
1529
+ for (var p in params) {
1530
+ if (hasOwnProperty(params, p)) {
1531
+ if (p.substr(0, paramString.length) === paramString) {
1532
+ thisParam = p.substr(paramString.length);
1533
+ if (thisParam === 'auth_policies') {
1534
+ result[thisParam] = _getShortPolicyName(params[p]);
1535
+ } else {
1536
+ result[thisParam] = params[p];
1537
+ }
1538
+ }
1539
+ }
1540
+ }
1541
+ }