X-Git-Url: http://git.squeep.com/?p=websub-hub;a=blobdiff_plain;f=test%2Fsrc%2Fauthenticator.js;fp=test%2Fsrc%2Fauthenticator.js;h=47313d84c875c84eb4ba6b2c12a9f108da6f4876;hp=8c174360b1b815790024c5c59640a57583c9edc7;hb=d0444b4b0ee8166d911e6b227218d110eb7eddf1;hpb=728dc0686aa5282e7fc9473c92fbbc617a2b93d7 diff --git a/test/src/authenticator.js b/test/src/authenticator.js index 8c17436..47313d8 100644 --- a/test/src/authenticator.js +++ b/test/src/authenticator.js @@ -2,37 +2,51 @@ 'use strict'; const assert = require('assert'); -const sinon = require('sinon'); +const sinon = require('sinon'); // eslint-disable-line node/no-unpublished-require const Authenticator = require('../../src/authenticator'); const stubLogger = require('../stub-logger'); const stubDb = require('../stub-db'); const Errors = require('../../src/errors'); const Enum = require('../../src/enum'); +const Config = require('../../config'); const noExpectedException = 'did not receive expected exception'; describe('Authenticator', function () { let authenticator, credential, ctx, identifier, password, options; + function _authMechanismRequired(a, m) { + if (!a.authn[m]) { // eslint-disable-line security/detect-object-injection + this.skip(); + } + }; + beforeEach(function () { - options = { - authenticator: { - basicRealm: 'realm', - secureAuthOnly: true, - }, - }; + options = Config('test'); authenticator = new Authenticator(stubLogger, stubDb, options); identifier = 'username'; credential = '$argon2id$v=19$m=4096,t=3,p=1$1a6zRlX4BI4$sZGcQ72BTpDOlxUI/j3DmE1PMcu+Cs5liZ/D6kk79Ew'; ctx = {}; password = 'badPassword'; + stubDb._reset(); }); afterEach(function () { sinon.restore(); }); + it('covers no auth mechanisms', function () { + options.authenticator.authnEnabled = []; + try { + authenticator = new Authenticator(stubLogger, stubDb, options); + assert.fail(noExpectedException); + } catch (e) { + assert.strictEqual(e.message, 'no authentication mechanisms available'); + } + }); + describe('isValidBasic', function () { it('succeeds', async function () { - sinon.stub(authenticator.db, 'authenticationGet').resolves({ + _authMechanismRequired(authenticator, 'argon2'); + authenticator.db.authenticationGet.resolves({ identifier, credential, }); @@ -42,7 +56,8 @@ describe('Authenticator', function () { assert.strictEqual(ctx.authenticationId, identifier); }); it('fails', async function () { - sinon.stub(authenticator.db, 'authenticationGet').resolves({ + _authMechanismRequired(authenticator, 'argon2'); + authenticator.db.authenticationGet.resolves({ identifier, credential, }); @@ -52,14 +67,14 @@ describe('Authenticator', function () { assert.strictEqual(ctx.authenticationId, undefined); }); it('covers no entry', async function() { - sinon.stub(authenticator.db, 'authenticationGet').resolves(); + authenticator.db.authenticationGet.resolves(); const authString = `${identifier}:wrongPassword}`; const result = await authenticator.isValidBasic(authString, ctx); assert.strictEqual(result, false); assert.strictEqual(ctx.authenticationId, undefined); }); it('covers unknown password hash', async function () { - sinon.stub(authenticator.db, 'authenticationGet').resolves({ + authenticator.db.authenticationGet.resolves({ identifier, credential: '$other$kind_of_credential', }); @@ -70,6 +85,95 @@ describe('Authenticator', function () { }); }); // isValidBasic + describe('isValidIdentifierCredential', function () { + it('succeeds', async function () { + _authMechanismRequired(authenticator, 'argon2'); + authenticator.db.authenticationGet.resolves({ + identifier, + credential, + }); + const result = await authenticator.isValidIdentifierCredential(identifier, password, ctx); + assert.strictEqual(result, true); + assert.strictEqual(ctx.authenticationId, identifier); + }); + it('fails', async function () { + _authMechanismRequired(authenticator, 'argon2'); + authenticator.db.authenticationGet.resolves({ + identifier, + credential, + }); + const result = await authenticator.isValidIdentifierCredential(identifier, 'wrongPassword', ctx); + assert.strictEqual(result, false); + assert.strictEqual(ctx.authenticationId, undefined); + }); + it('covers no entry', async function() { + authenticator.db.authenticationGet.resolves(); + const result = await authenticator.isValidIdentifierCredential(identifier, 'wrongPassword', ctx); + assert.strictEqual(result, false); + assert.strictEqual(ctx.authenticationId, undefined); + }); + it('covers unknown password hash', async function () { + authenticator.db.authenticationGet.resolves({ + identifier, + credential: '$other$kind_of_credential', + }); + const result = await authenticator.isValidIdentifierCredential(identifier, 'wrongPassword', ctx); + assert.strictEqual(result, false); + assert.strictEqual(ctx.authenticationId, undefined); + }); + it('covers PAM', async function () { + _authMechanismRequired(authenticator, 'pam'); + sinon.stub(authenticator, '_isValidPAMIdentifier').resolves(true); + authenticator.db.authenticationGet.resolves({ + identifier, + credential: '$PAM$', + }); + const result = await authenticator.isValidIdentifierCredential(identifier, password, ctx); + assert.strictEqual(result, true); + assert.strictEqual(ctx.authenticationId, identifier); + }); + it('covers debug', async function () { + authenticator.authnEnabled = ['DEBUG_ANY']; + const result = await authenticator.isValidIdentifierCredential(identifier, password, ctx); + assert.strictEqual(result, true); + assert.strictEqual(ctx.authenticationId, identifier); + }); + }); // isValidIdentifierCredential + + describe('_isValidPAMIdentifier', function () { + beforeEach(function () { + _authMechanismRequired(authenticator, 'pam'); + sinon.stub(authenticator.authn.pam, 'pamAuthenticatePromise'); + }); + it('covers success', async function () { + authenticator.authn.pam.pamAuthenticatePromise.resolves(true); + const result = await authenticator._isValidPAMIdentifier(identifier, credential); + assert.strictEqual(result, true); + }); + it('covers failure', async function () { + _authMechanismRequired(authenticator, 'pam'); + authenticator.authn.pam.pamAuthenticatePromise.rejects(new authenticator.authn.pam.PamError()); + const result = await authenticator._isValidPAMIdentifier(identifier, credential); + assert.strictEqual(result, false); + }); + it('covers error', async function () { + _authMechanismRequired(authenticator, 'pam'); + const expected = new Error('blah'); + authenticator.authn.pam.pamAuthenticatePromise.rejects(expected); + try { + await authenticator._isValidPAMIdentifier(identifier, credential); + assert.fail(noExpectedException); + } catch (e) { + assert.deepStrictEqual(e, expected); + } + }); + it('covers forbidden', async function () { + identifier = 'root'; + const result = await authenticator._isValidPAMIdentifier(identifier, credential); + assert.strictEqual(result, false); + }); + }); // _isValidPAMIdentifier + describe('isValidAuthorization', function () { it('handles basic', async function () { const expected = true; @@ -101,6 +205,35 @@ describe('Authenticator', function () { }); }); // requestBasic + describe('isValidCookieAuth', function () { + beforeEach(function () { + sinon.stub(authenticator.mysteryBox, 'unpack'); + }); + it('covers identifier success', async function () { + authenticator.mysteryBox.unpack.resolves({ + authenticatedIdentifier: 'identifier', + }); + const result = await authenticator.isValidCookieAuth(ctx, 'WSHas=dummy'); + assert.strictEqual(result, true); + }); + it('covers profile success', async function () { + authenticator.mysteryBox.unpack.resolves({ + authenticatedProfile: 'profile', + }); + const result = await authenticator.isValidCookieAuth(ctx, 'WSHas=dummy'); + assert.strictEqual(result, true); + }); + it('covers missing cookie', async function () { + const result = await authenticator.isValidCookieAuth(ctx, 'wrongCookie'); + assert.strictEqual(result, false); + }); + it('covers bad cookie', async function () { + authenticator.mysteryBox.unpack.rejects(); + const result = await authenticator.isValidCookieAuth(ctx, 'WSHas=dummy'); + assert.strictEqual(result, false); + }); + }); // isValidCookieAuth + describe('required', function () { let req, res; beforeEach(function () { @@ -109,6 +242,7 @@ describe('Authenticator', function () { getHeader: sinon.stub(), }; res = { + end: sinon.stub(), setHeader: sinon.stub(), } }); @@ -118,6 +252,12 @@ describe('Authenticator', function () { const result = await authenticator.required(req, res, ctx); assert.strictEqual(result, true); }); + it('covers valid cookie session', async function () { + req.getHeader.returns('WSHas=sessionCookie'); + sinon.stub(authenticator, 'isValidCookieAuth').resolves(true); + const result = await authenticator.required(req, res, ctx); + assert.strictEqual(result, true); + }); it('rejects insecure connection', async function () { ctx.clientProtocol = 'http'; try { @@ -139,5 +279,66 @@ describe('Authenticator', function () { assert.strictEqual(e.statusCode, Enum.ErrorResponse.Unauthorized.statusCode); } }); + it('redirects without any auth', async function () { + await authenticator.required(req, res, ctx); + assert(res.end.called); + assert(res.setHeader.called); + }); }); // required + + describe('requiredLocal', function () { + let req, res; + beforeEach(function () { + ctx.clientProtocol = 'https'; + req = { + getHeader: sinon.stub(), + }; + res = { + end: sinon.stub(), + setHeader: sinon.stub(), + } + }); + it('succeeds', async function() { + req.getHeader.returns('auth header'); + sinon.stub(authenticator, 'isValidAuthorization').resolves(true); + const result = await authenticator.requiredLocal(req, res, ctx); + assert.strictEqual(result, true); + }); + it('covers valid cookie session', async function () { + req.getHeader.returns('WSHas=sessionCookie'); + sinon.stub(authenticator, 'isValidCookieAuth').resolves(true); + ctx.session = { + authenticatedIdentifier: identifier, + }; + const result = await authenticator.requiredLocal(req, res, ctx); + assert.strictEqual(result, true); + }); + it('rejects insecure connection', async function () { + ctx.clientProtocol = 'http'; + try { + await authenticator.requiredLocal(req, res, ctx); + assert.fail(noExpectedException); + } catch (e) { + assert(e instanceof Errors.ResponseError); + assert.strictEqual(e.statusCode, Enum.ErrorResponse.Forbidden.statusCode); + } + }); + it('rejects invalid auth', async function () { + try { + req.getHeader.returns('auth header'); + sinon.stub(authenticator, 'isValidAuthorization').resolves(false); + await authenticator.requiredLocal(req, res, ctx); + assert.fail(noExpectedException); + } catch (e) { + assert(e instanceof Errors.ResponseError); + assert.strictEqual(e.statusCode, Enum.ErrorResponse.Unauthorized.statusCode); + } + }); + it('redirects without any auth', async function () { + await authenticator.requiredLocal(req, res, ctx); + assert(res.end.called); + assert(res.setHeader.called); + }); + }); // requiredLocal + }); // Authenticator