From 0989822279240dcb4ec48dd1438c824e5fff44b4 Mon Sep 17 00:00:00 2001 From: fisehara Date: Thu, 13 Oct 2022 15:47:16 +0200 Subject: [PATCH] Add basic auth header middleware Add basic permission tests Change-type: minor Signed-off-by: fisehara --- src/sbvr-api/permissions.ts | 46 ++++++++++ test/04-permissions.test.ts | 94 ++++++++++++++++++++ test/fixtures/04-permissions/config.ts | 36 ++++++++ test/fixtures/04-permissions/university.sbvr | 36 ++++++++ test/lib/pine-init.ts | 5 +- 5 files changed, 216 insertions(+), 1 deletion(-) create mode 100644 test/04-permissions.test.ts create mode 100644 test/fixtures/04-permissions/config.ts create mode 100644 test/fixtures/04-permissions/university.sbvr diff --git a/src/sbvr-api/permissions.ts b/src/sbvr-api/permissions.ts index 469b439fd..c208ea91d 100644 --- a/src/sbvr-api/permissions.ts +++ b/src/sbvr-api/permissions.ts @@ -1504,6 +1504,52 @@ export const customAuthorizationMiddleware = (expectedScheme = 'Bearer') => { // A default bearer middleware for convenience export const authorizationMiddleware = customAuthorizationMiddleware(); +export const resolveBasicAuthHeader = async ( + req: Express.Request, + expectedScheme = 'Basic', +): Promise => { + const auth = req.header('Authorization'); + if (!auth) { + return; + } + + const parts = auth.split(' '); + if (parts.length !== 2) { + return; + } + + const [scheme, basicAuthContentBase64] = parts; + if (scheme.toLowerCase() !== expectedScheme.toLowerCase()) { + return; + } + + const basicAuthContent = Buffer.from(basicAuthContentBase64, 'base64') + .toString() + .trim(); + const [username, password] = basicAuthContent.split(';'); + return checkPassword(username, password); +}; + +export const basicUserPasswordAuthorizationMiddleware = ( + expectedScheme = 'Basic', +) => { + expectedScheme = expectedScheme.toLowerCase(); + return async ( + req: Express.Request, + _res?: Express.Response, + next?: Express.NextFunction, + ): Promise => { + try { + const user = await resolveBasicAuthHeader(req, expectedScheme); + if (user) { + req.user = user; + } + } finally { + next?.(); + } + }; +}; + export const resolveApiKey = async ( req: HookReq | Express.Request, paramName = 'apikey', diff --git a/test/04-permissions.test.ts b/test/04-permissions.test.ts new file mode 100644 index 000000000..ae6a403d1 --- /dev/null +++ b/test/04-permissions.test.ts @@ -0,0 +1,94 @@ +import * as supertest from 'supertest'; +import { expect } from 'chai'; +const fixturePath = __dirname + '/fixtures/04-permissions/config'; +import { testInit, testDeInit, testLocalServer } from './lib/test-init'; + +const basicStudentAuthHeaderBase64 = + Buffer.from('student;student').toString('base64'); +const basicAdminAuthHeaderBase64 = + Buffer.from('admin;admin').toString('base64'); +describe('04 basic permission tests', function () { + let pineServer: Awaited>; + let request; + before(async () => { + pineServer = await testInit(fixturePath, true); + request = supertest.agent(testLocalServer); + }); + + after(async () => { + await testDeInit(pineServer); + }); + + describe('Basic', () => { + it('check /ping route is OK', async () => { + await request.get('/ping').expect(200, 'OK'); + }); + }); + + describe.only('university vocabular', () => { + it('check /university/student is served by pinejs', async () => { + const res = await request + .set('Authorization', 'Basic ' + basicStudentAuthHeaderBase64) + .get('/university/student') + .expect(200); + expect(res.body) + .to.be.an('object') + .that.has.ownProperty('d') + .to.be.an('array'); + }); + + it('create a student', async () => { + await request + .set('Authorization', 'Basic ' + basicStudentAuthHeaderBase64) + .post('/university/student') + .send({ + matrix_number: 1, + name: 'John', + lastname: 'Doe', + birthday: new Date(), + semester_credits: 10, + }) + .expect(201); + }); + + it('delete a student', async () => { + await request + .set('Authorization', 'Basic ' + basicStudentAuthHeaderBase64) + .delete('/university/student(1)') + .expect(401); + }); + + it('should fail to create a student with same matrix number ', async () => { + await request + .set('Authorization', 'Basic ' + basicStudentAuthHeaderBase64) + .post('/university/student') + .send({ + matrix_number: 1, + name: 'John', + lastname: 'Doe', + birthday: new Date(), + semester_credits: 10, + }) + .expect(409); + }); + + it('should fail to create a student with too few semester credits ', async () => { + const res = await request + .set('Authorization', 'Basic ' + basicStudentAuthHeaderBase64) + .post('/university/student') + .send({ + matrix_number: 2, + name: 'Jenny', + lastname: 'Dea', + birthday: new Date(), + semester_credits: 2, + }) + .expect(400); + expect(res.body) + .to.be.a('string') + .that.equals( + 'It is necessary that each student that has a semester credits, has a semester credits that is greater than or equal to 4 and is less than or equal to 16.', + ); + }); + }); +}); diff --git a/test/fixtures/04-permissions/config.ts b/test/fixtures/04-permissions/config.ts new file mode 100644 index 000000000..e9526f1a7 --- /dev/null +++ b/test/fixtures/04-permissions/config.ts @@ -0,0 +1,36 @@ +import type { ConfigLoader } from '../../../src/server-glue/module'; + +const apiRoot = 'university'; +const modelName = 'university'; +const modelFile = __dirname + '/university.sbvr'; + +export default { + models: [ + { + modelName, + modelFile, + apiRoot, + }, + ], + users: [ + { + username: 'guest', + password: ' ', + permissions: ['student.read'], + }, + { + username: 'student', + password: 'student', + permissions: [ + 'university.student.read', + 'university.student.create', + 'university.student.update', + ], + }, + { + username: 'admin', + password: 'admin', + permissions: ['resource.all'], + }, + ], +} as ConfigLoader.Config; diff --git a/test/fixtures/04-permissions/university.sbvr b/test/fixtures/04-permissions/university.sbvr new file mode 100644 index 000000000..2de68deec --- /dev/null +++ b/test/fixtures/04-permissions/university.sbvr @@ -0,0 +1,36 @@ +Vocabulary: university + +Term: name + Concept Type: Short Text (Type) + +Term: lastname + Concept Type: Short Text (Type) + +Term: birthday + Concept Type: Date Time (Type) + +Term: semester credits + Concept Type: Integer (Type) + +Term: matrix number + Concept Type: Integer (Type) + + +Term: student + +Fact Type: student has matrix number + Necessity: each student has exactly one matrix number + Necessity: each matrix number is of exactly one student + +Fact Type: student has name + Necessity: each student has exactly one name + +Fact Type: student has lastname + Necessity: each student has exactly one lastname + +Fact Type: student has birthday + Necessity: each student has exactly one birthday + +Fact Type: student has semester credits + Necessity: each student has at most one semester credits + Necessity: each student that has a semester credits, has a semester credits that is greater than or equal to 4 and is less than or equal to 16. diff --git a/test/lib/pine-init.ts b/test/lib/pine-init.ts index 3f6972a75..f906be4a9 100644 --- a/test/lib/pine-init.ts +++ b/test/lib/pine-init.ts @@ -1,6 +1,7 @@ import * as express from 'express'; import { exit } from 'process'; import * as pine from '../../src/server-glue/module'; +import { basicUserPasswordAuthorizationMiddleware } from '../../src/sbvr-api/permissions'; export async function init( initConfig: pine.ConfigLoader.Config, @@ -17,13 +18,15 @@ export async function init( try { await cleanInit(deleteDb); + app.use(basicUserPasswordAuthorizationMiddleware()); await pine.init(app, initConfig); + + // register default auth middleware for bearer token await new Promise((resolve) => { app.listen(initPort, () => { resolve('server started'); }); }); - return app; } catch (e) { console.log(`pineInit ${e}`); exit(1);