1 |
|
2 |
|
3 | 'use strict';
|
4 |
|
5 | var helper = require('./test-helper');
|
6 | var config = helper.config();
|
7 |
|
8 | var debug = require('debug')('plugin:oauth:test');
|
9 | var http = require('http');
|
10 | var request = require('supertest');
|
11 | var chai = require('chai');
|
12 | var should = chai.should();
|
13 | var pem = require('pem');
|
14 | var jwt = require('jsonwebtoken');
|
15 | var util = require('util');
|
16 |
|
17 | chai.config.includeStack = true;
|
18 | chai.config.showDiff = true;
|
19 |
|
20 | describe('oauth', function() {
|
21 |
|
22 | var privateKey, publicKey;
|
23 |
|
24 | before(function(done) {
|
25 | var options = {
|
26 | selfSigned: true,
|
27 | days: 1
|
28 | };
|
29 | pem.createCertificate(options, function(err, keys) {
|
30 | if (err) { return done(err); }
|
31 |
|
32 | privateKey = keys.serviceKey;
|
33 | publicKey = keys.certificate;
|
34 |
|
35 | config.oauth.public_key = publicKey;
|
36 | config.oauth.allowNoAuthorization = false;
|
37 | config.oauth.allowInvalidAuthorization = false;
|
38 |
|
39 | done();
|
40 | });
|
41 | });
|
42 |
|
43 | var servers, proxy;
|
44 |
|
45 | before(function(done) {
|
46 | config.edgemicro.plugins.sequence = ['oauth'];
|
47 | helper.startServers(config, function(err, s) {
|
48 | if (err) { return done(err); }
|
49 | servers = s;
|
50 | proxy = servers.proxy;
|
51 | done();
|
52 | });
|
53 | });
|
54 |
|
55 | after(function() {
|
56 | servers.close();
|
57 | });
|
58 |
|
59 |
|
60 | describe('no authorization', function() {
|
61 |
|
62 | it('should fail when allowNoAuthorization is false', function(done) {
|
63 |
|
64 | config.oauth.allowNoAuthorization = false;
|
65 | config.oauth.allowInvalidAuthorization = false;
|
66 | request(proxy)
|
67 | .get('/')
|
68 | .expect(401)
|
69 | .end(function(err, res) {
|
70 | if (err) { return done(err); }
|
71 | should.not.exist(res.body.fromTarget);
|
72 | done();
|
73 | });
|
74 | });
|
75 |
|
76 | it('should succeed when allowNoAuthorization is true', function(done) {
|
77 |
|
78 | config.oauth.allowNoAuthorization = true;
|
79 | config.oauth.allowInvalidAuthorization = false;
|
80 | request(proxy)
|
81 | .get('/')
|
82 | .expect(200)
|
83 | .end(function(err, res) {
|
84 | if (err) { return done(err); }
|
85 | should.exist(res.body.fromTarget);
|
86 | should.not.exist(res.headers.authorization);
|
87 |
|
88 | should.not.exist(res.body.headers['authorization']);
|
89 | should.not.exist(res.body.headers['x-authorization-claims']);
|
90 | done();
|
91 | });
|
92 | });
|
93 | });
|
94 |
|
95 | describe('bad bearer token', function() {
|
96 |
|
97 | it('should fail with missing bearer token', function(done) {
|
98 |
|
99 | config.oauth.allowNoAuthorization = false;
|
100 | config.oauth.allowInvalidAuthorization = false;
|
101 | request(proxy)
|
102 | .get('/')
|
103 | .set('Authorization', 'Bearer')
|
104 | .expect(400)
|
105 | .end(function(err, res) {
|
106 | if (err) { return done(err); }
|
107 | should.not.exist(res.body.fromTarget);
|
108 | done();
|
109 | });
|
110 | });
|
111 |
|
112 | it('should fail when allowInvalidAuthorization is false', function(done) {
|
113 |
|
114 | config.oauth.allowNoAuthorization = false;
|
115 | config.oauth.allowInvalidAuthorization = false;
|
116 | request(proxy)
|
117 | .get('/')
|
118 | .set('Authorization', 'Bearer BadBearerToken')
|
119 | .expect(401)
|
120 | .end(function(err, res) {
|
121 | if (err) { return done(err); }
|
122 | should.not.exist(res.body.fromTarget);
|
123 | done();
|
124 | });
|
125 | });
|
126 |
|
127 | it('should succeed when allowInvalidAuthorization is true', function(done) {
|
128 |
|
129 | config.oauth.allowNoAuthorization = false;
|
130 | config.oauth.allowInvalidAuthorization = true;
|
131 | request(proxy)
|
132 | .get('/')
|
133 | .set('Authorization', 'Bearer BadBearerToken')
|
134 | .expect(200)
|
135 | .end(function(err, res) {
|
136 | if (err) { return done(err); }
|
137 | should.exist(res.body.fromTarget);
|
138 | should.not.exist(res.headers.authorization);
|
139 |
|
140 | should.not.exist(res.body.headers['authorization']);
|
141 | should.not.exist(res.body.headers['x-authorization-claims']);
|
142 | done();
|
143 | });
|
144 | });
|
145 | });
|
146 |
|
147 | describe('good bearer token', function() {
|
148 |
|
149 | it('should succeed if valid proxy path', function(done) {
|
150 |
|
151 | var options = { algorithm: 'RS256' };
|
152 | var payload = {
|
153 | application_name: 'app',
|
154 | client_id: 'client',
|
155 | scopes: ['scope1'],
|
156 | api_product_list: [ 'Test' ],
|
157 | test: 'test'
|
158 | };
|
159 | var token = jwt.sign(payload, privateKey, options);
|
160 |
|
161 | config.oauth.allowNoAuthorization = false;
|
162 | config.oauth.allowInvalidAuthorization = false;
|
163 | request(proxy)
|
164 | .get('/')
|
165 | .set('Authorization', 'Bearer ' + token)
|
166 | .expect(200)
|
167 | .end(function(err, res) {
|
168 | if (err) { return done(err); }
|
169 | should.exist(res.body.fromTarget);
|
170 | should.not.exist(res.headers.authorization);
|
171 |
|
172 | should.not.exist(res.body.headers['authorization']);
|
173 | should.exist(res.body.headers['x-authorization-claims']);
|
174 | var claims = JSON.parse(new Buffer(res.body.headers['x-authorization-claims'], 'base64').toString());
|
175 | claims.should.have.keys('scopes', 'test');
|
176 |
|
177 | done();
|
178 | });
|
179 | });
|
180 |
|
181 | it('should fail if missing required proxy path', function(done) {
|
182 |
|
183 | var options = { algorithm: 'RS256' };
|
184 | var payload = { test: 'test' };
|
185 | var token = jwt.sign(payload, privateKey, options);
|
186 |
|
187 | config.oauth.allowNoAuthorization = false;
|
188 | config.oauth.allowInvalidAuthorization = false;
|
189 | request(proxy)
|
190 | .get('/')
|
191 | .set('Authorization', 'Bearer ' + token)
|
192 | .expect(403)
|
193 | .end(function(err, res) {
|
194 | if (err) { return done(err); }
|
195 | should.not.exist(res.body.fromTarget);
|
196 | done();
|
197 | });
|
198 | });
|
199 |
|
200 | it('should fail if expired', function(done) {
|
201 |
|
202 | var options = { algorithm: 'RS256', expiresInSeconds: 1 };
|
203 | var payload = { test: 'test' };
|
204 | var token = jwt.sign(payload, privateKey, options);
|
205 |
|
206 | setTimeout(function() {
|
207 |
|
208 | config.oauth.allowNoAuthorization = false;
|
209 | config.oauth.allowInvalidAuthorization = false;
|
210 |
|
211 | request(proxy)
|
212 | .get('/')
|
213 | .set('Authorization', 'Bearer ' + token)
|
214 | .expect(403)
|
215 | .end(function(err, res) {
|
216 | if (err) { return done(err); }
|
217 | should.not.exist(res.body.fromTarget);
|
218 | done();
|
219 | });
|
220 | }, 1000);
|
221 | });
|
222 | });
|
223 |
|
224 | describe('configured auth header', function() {
|
225 |
|
226 | before(function() {
|
227 | config.oauth.allowNoAuthorization = false;
|
228 | config.oauth.allowInvalidAuthorization = false;
|
229 | config.oauth['authorization-header'] = 'x-proxy-auth';
|
230 | });
|
231 |
|
232 | after(function() {
|
233 | delete(config.oauth['authorization-header']);
|
234 | });
|
235 |
|
236 | it('should succeed if in configured header', function(done) {
|
237 |
|
238 | var options = { algorithm: 'RS256' };
|
239 | var payload = {
|
240 | application_name: 'app',
|
241 | client_id: 'client',
|
242 | scopes: ['scope1'],
|
243 | api_product_list: [ 'Test' ],
|
244 | test: 'test'
|
245 | };
|
246 | var token = jwt.sign(payload, privateKey, options);
|
247 |
|
248 | request(proxy)
|
249 | .get('/')
|
250 | .set('Authorization', 'Whatever')
|
251 | .set('x-proxy-auth', 'Bearer ' + token)
|
252 | .expect(200)
|
253 | .end(function(err, res) {
|
254 | if (err) { return done(err); }
|
255 | should.exist(res.body.fromTarget);
|
256 | should.not.exist(res.headers.authorization);
|
257 |
|
258 | should.exist(res.body.headers['authorization']);
|
259 | should.exist(res.body.headers['x-authorization-claims']);
|
260 | var claims = JSON.parse(new Buffer(res.body.headers['x-authorization-claims'], 'base64').toString());
|
261 | claims.should.have.keys('scopes', 'test');
|
262 |
|
263 | done();
|
264 | });
|
265 | });
|
266 |
|
267 | it('should fail if in default header', function(done) {
|
268 |
|
269 | var options = { algorithm: 'RS256' };
|
270 | var payload = {
|
271 | application_name: 'app',
|
272 | client_id: 'client',
|
273 | scopes: ['scope1'],
|
274 | api_product_list: [ 'Test' ],
|
275 | test: 'test'
|
276 | };
|
277 | var token = jwt.sign(payload, privateKey, options);
|
278 |
|
279 | request(proxy)
|
280 | .get('/')
|
281 | .set('Authorization', 'Bearer ' + token)
|
282 | .expect(401)
|
283 | .end(function(err, res) {
|
284 | if (err) { return done(err); }
|
285 | should.not.exist(res.body.fromTarget);
|
286 | done();
|
287 | });
|
288 |
|
289 | });
|
290 | });
|
291 |
|
292 | describe('apikey', function() {
|
293 |
|
294 | var verifier;
|
295 |
|
296 | var apiKey = '6gClRCKp0UCOZ8o9Q5S7X88nI5hgizGQ';
|
297 | var jwtTokenForApiKey =
|
298 | 'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJhcHBsaWNhdGlvbl9uYW1lIjoiSkRzIEFwcCIsImNsaWVudF9pZCI6IjZnQ2xSQ0twMFV' +
|
299 | 'DT1o4bzlRNVM3WDg4bkk1aGdpekdRIiwic2NvcGVzIjpbXSwiYXBpX3Byb2R1Y3RfbGlzdCI6WyJ0cmF2ZWwtYXBwIl0sImlhdCI6MTQ0NTU' +
|
300 | '1MjYxMH0.glQTK-Nh0YFYbWK-pr8nbJuIvt51p6zmLe53CbZ3JLEcG0QjHYcKmUPLPgDOGYfyjnYMUVMMfIDDZlhemy84fKQMGRptCgUfmga' +
|
301 | '9roLNPKPujhxFXb9GhkQ94KXxm8GChuvjYxn8K7K_nAhnzn4wB84rczvm91ytOwFPCeS_t6KbLS3uMrj6Nj1gITGeZVlm2QLAvUlJ5Abua0t' +
|
302 | 'OItHj7_nvzHHwClgN9Is1UZ7LW5f747kVWp10t1JbAmubyTP-01TSbaniDGfBCmi0JYOizUFZiMjdSVcZP-tqWvHAsLdQ2T9k4sKG1I1pKcK' +
|
303 | 'vh3dmS3j8PWhTHIV3FM1x7L5hig';
|
304 | var apiPublicKey =
|
305 | '-----BEGIN CERTIFICATE-----\n' +
|
306 | 'MIICpDCCAYwCCQDd9JO8DIvnqTANBgkqhkiG9w0BAQsFADAUMRIwEAYDVQQDEwls\n' +
|
307 | 'b2NhbGhvc3QwHhcNMTUwNjI1MDMxNTQ5WhcNMTUwNjI2MDMxNTQ5WjAUMRIwEAYD\n' +
|
308 | 'VQQDEwlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDY\n' +
|
309 | 'O0JHvd1m/H/VyGmQpuqY/CvZGbx/wRWrLG/YErYl1w30e615hJOBpT1neavXONGw\n' +
|
310 | '2kuiqjRon8WcWvjrmKSDitul1MhUqddEBC+JhMfZpgCr9axcrFgRxm4JcfrhWoqE\n' +
|
311 | 'LXAYLo/VXzNCCkGz4SLcp/azpnnPBeTm5m4AJBMW0YrznBQrVMJHGXXvd1b9q5Hp\n' +
|
312 | 'Ejui9VdIEknhNJ88bjP3Lq7j0efCbkj0IAYNigdbC+eIMbRHVHNyEaioUAU9pYAp\n' +
|
313 | '2v1tomnwlQbEhD3vVnWJprKWgLGtYf9pmwrffwtDu73gyO/qr+aVNSNlchQ4fOag\n' +
|
314 | '3xeYKmAUHOl6QpiIocjzAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAG9B1kST84u0\n' +
|
315 | 'yR5/CNqHO6wx2SiYKOfrhkvKbxsql/u59qxYd6jHPyE4adLkp/PPWz6DN4ucRT4J\n' +
|
316 | '2UULFD2biRvPK9Ua3J7IKLUMOuVnwU7JFCiAf51e0A9Weqh6L0+ATTkZMddUfKTA\n' +
|
317 | 'uLdVOBz43EPCizKAjUP39+RFuUhBJazNrXyibE4fP/r6pJ7EB4a57HnvTlNkPZXA\n' +
|
318 | 'z24Ihg42rqDXgFmNPE6X9p/pSOzA87iGLrOznbFQzTHJg/wLqmPEPVrjFwXWfzoI\n' +
|
319 | 'CAysSvrNWbfqSIoxxWPpkK+O10m2DqYaQMu3J73p9kgD0gV9KBSW2rMFDg6JnI4G\n' +
|
320 | 'BDvtgIzP58M=\n' +
|
321 | '-----END CERTIFICATE-----';
|
322 |
|
323 | before(function(done) {
|
324 | verifier = http.createServer(function(req, res) {
|
325 | var key = req.headers['x-dna-api-key'];
|
326 | if (key === apiKey) {
|
327 | res.statusCode = 200;
|
328 | res.end(jwtTokenForApiKey);
|
329 | } else {
|
330 | res.statusCode = 401;
|
331 | res.end();
|
332 | }
|
333 | });
|
334 | verifier.listen(function(err) {
|
335 | if (err) { return done(err); }
|
336 | config.oauth.verify_api_key_url = 'http://127.0.0.1:' + verifier.address().port;
|
337 | config.oauth.public_key = apiPublicKey;
|
338 | config.oauth.product_to_proxy = {};
|
339 | config.oauth.product_to_proxy['travel-app'] = ['microgateway_test'];
|
340 | done();
|
341 | });
|
342 | });
|
343 |
|
344 | after(function() {
|
345 | verifier.close();
|
346 | });
|
347 |
|
348 | it('in header', function(done) {
|
349 | request(proxy)
|
350 | .get('/')
|
351 | .set('x-api-key', apiKey)
|
352 | .expect(200)
|
353 | .end(function(err, res) {
|
354 | debug(res.body);
|
355 | if (err) { return done(err); }
|
356 | done();
|
357 | });
|
358 | });
|
359 |
|
360 | it('in custom header', function(done) {
|
361 | config.oauth['api-key-header'] = 'custom-api-key-header';
|
362 | request(proxy)
|
363 | .get('/')
|
364 | .set('custom-api-key-header', apiKey)
|
365 | .expect(200)
|
366 | .end(function(err, res) {
|
367 | debug(res.body);
|
368 | delete config.oauth['api-key-header'];
|
369 | if (err) { return done(err); }
|
370 | done();
|
371 | });
|
372 | });
|
373 |
|
374 | it('in url param', function(done) {
|
375 | request(proxy)
|
376 | .get('/?x-api-key=' + apiKey)
|
377 | .expect(200)
|
378 | .end(function(err, res) {
|
379 | debug(res.body);
|
380 | if (err) { return done(err); }
|
381 | done();
|
382 | });
|
383 | });
|
384 |
|
385 | it('in custom url param', function(done) {
|
386 | config.oauth['api-key-header'] = 'custom-api-key-header';
|
387 | request(proxy)
|
388 | .get('/?custom-api-key-header=' + apiKey)
|
389 | .expect(200)
|
390 | .end(function(err, res) {
|
391 | debug(res.body);
|
392 | delete config.oauth['api-key-header'];
|
393 | if (err) { return done(err); }
|
394 | done();
|
395 | });
|
396 | });
|
397 |
|
398 | it('in header (invalid)', function(done) {
|
399 | request(proxy)
|
400 | .get('/')
|
401 | .set('x-api-key', (apiKey + 'x'))
|
402 | .expect(403)
|
403 | .end(function(err, res) {
|
404 | debug(res.body);
|
405 | if (err) { return done(err); }
|
406 | done();
|
407 | });
|
408 | });
|
409 |
|
410 | it('in url (invalid)', function(done) {
|
411 | request(proxy)
|
412 | .get('/?x-api-key=' + (apiKey + 'x'))
|
413 | .expect(403)
|
414 | .end(function(err, res) {
|
415 | debug(res.body);
|
416 | if (err) { return done(err); }
|
417 | done();
|
418 | });
|
419 | });
|
420 |
|
421 | it('expiration', function(done) {
|
422 | config.oauth.public_key = publicKey;
|
423 |
|
424 | var payload = {};
|
425 | var options = { algorithm: 'RS256' };
|
426 |
|
427 | var savedToken = jwtTokenForApiKey;
|
428 | var decoded = jwt.decode(jwtTokenForApiKey);
|
429 | Object.keys(decoded).forEach(function(key) { payload[key] = decoded[key] });
|
430 | options.expiresIn = -1;
|
431 | jwtTokenForApiKey = jwt.sign(payload, privateKey, options);
|
432 |
|
433 | request(proxy)
|
434 | .get('/')
|
435 | .set('x-api-key', apiKey)
|
436 | .set('cache-control', 'no-cache')
|
437 | .expect(403)
|
438 | .end(function(err, res) {
|
439 | debug('expired: ' + util.inspect(res.body));
|
440 | if (err) { return done(err); }
|
441 |
|
442 | options.expiresIn = 30;
|
443 | jwtTokenForApiKey = jwt.sign(payload, privateKey, options);
|
444 |
|
445 | request(proxy)
|
446 | .get('/')
|
447 | .set('x-api-key', apiKey)
|
448 | .set('cache-control', 'no-cache')
|
449 | .expect(200)
|
450 | .end(function(err, res) {
|
451 | debug('restored: ' + util.inspect(res.body));
|
452 | jwtTokenForApiKey = savedToken;
|
453 | if (err) { return done(err); }
|
454 | done();
|
455 | });
|
456 | });
|
457 | });
|
458 |
|
459 | it('keep auth header', function(done) {
|
460 | config.oauth.keepAuthHeader = true;
|
461 | config.oauth.allowNoAuthorization = true;
|
462 | config.oauth.allowInvalidAuthorization = false;
|
463 |
|
464 | request(proxy)
|
465 | .get('/')
|
466 | .expect(200)
|
467 | .end(function(err, res) {
|
468 | if (err) { return done(err); }
|
469 | should.exist(res.body.fromTarget);
|
470 | should.exist(res.headers.authorization);
|
471 |
|
472 | should.not.exist(res.body.headers['authorization']);
|
473 | should.not.exist(res.body.headers['x-authorization-claims']);
|
474 | done();
|
475 | });
|
476 |
|
477 | });
|
478 |
|
479 | });
|
480 |
|
481 | });
|