From 1fecff3c6d34e59e99ee8ff83be0e2c7b90168e2 Mon Sep 17 00:00:00 2001 From: Dan Jones <dan.jones@noc.ac.uk> Date: Thu, 12 Jan 2023 10:17:40 +0000 Subject: [PATCH] refactor: use constructor config in adapter auth Commit built dist files for now We can remove these if we publish to npm --- .gitignore | 3 - dist/adapter.esm.js | 167 ++++++++++++++++++++++++++++++++++++++++++ dist/adapter.js | 169 +++++++++++++++++++++++++++++++++++++++++++ dist/protocol.esm.js | 71 ++++++++++++++++++ dist/protocol.js | 73 +++++++++++++++++++ package.json | 3 +- src/adapter/index.js | 16 ++-- 7 files changed, 489 insertions(+), 13 deletions(-) create mode 100644 dist/adapter.esm.js create mode 100644 dist/adapter.js create mode 100644 dist/protocol.esm.js create mode 100644 dist/protocol.js diff --git a/.gitignore b/.gitignore index d5a9aeb..6f70c11 100644 --- a/.gitignore +++ b/.gitignore @@ -26,9 +26,6 @@ coverage # Dependency directories node_modules/ -# Build files -dist/ - # Optional npm cache directory .npm diff --git a/dist/adapter.esm.js b/dist/adapter.esm.js new file mode 100644 index 0000000..a7e1c8d --- /dev/null +++ b/dist/adapter.esm.js @@ -0,0 +1,167 @@ +import Validator from 'swagger-model-validator'; +import axios from 'axios'; + +/** + * Handle authentication and send/receive with the backbone + */ +class Adapter { + constructor(protocol, config) { + this.config = config; + this.protocol = protocol; + this.axios = axios; + this.validator = new Validator(protocol.schema); + this.auth(); + } + + /** + * Test parsing the message based on the provided protocol schema + * + * The message must be successfully json decoded into an object + * prior to validation + * + * At present this returns the validation result which is an + * object containing a boolean valid field as well as details + * of any errors. + * @param {object} message + * @returns {object} + */ + validate(message) { + return this.validator.validate( + message, + this.protocol.schema.definitions.Message + ); + } + + /** + * Ensure the token in this.credentials is still valid + * @returns {boolean} + */ + tokenValid() { + let valid = false; + if (this.credentials) { + const now = new Date().toISOString(); + valid = this.credentials.expiry > now; + } + return valid; + } + + /** + * Return an http headers object containing a bearer token authorisation header + * @returns {object} + */ + getAuthorizationHeader() { + if (!this.tokenValid()) + return this.auth().then((response) => { + return { + Authorization: `Bearer ${this.credentials.token}`, + }; + }); + else { + return Promise.resolve({ + Authorization: `Bearer ${this.credentials.token}`, + }); + } + } + + /** + * Do a client credentials grant and store in this.credentials + * @returns {object} + */ + auth() { + return this.axios + .get(`{this.config.api}/token`, { + params: { + client_id: this.config.client_id, + secret: this.config.secret, + }, + }) + .then((response) => { + this.credentials = response.data; + return response; + }); + } + + /** + * Call the GET /receive endpoint and process the messages with decode + * + * Returns the response + * @returns {object} + */ + poll() { + return this.getAuthorizationHeader() + .then((headers) => { + return this.axios.get(`{this.config.api}/receive`, { + headers, + }); + }) + .then((response) => { + response.data.forEach((message) => { + const parsed = JSON.parse(message.message); + const validation = this.validate(parsed); + if (validation.valid) { + const type = this.protocol.getType(parsed); + this.protocol.decode(type, parsed); + } + }); + return response; + }); + } + + /** + * Publish a message to the backbone with the specified topic + * + * Messages should be passed through encode before sending + * @param {string} topic + * @param {string} body + * @returns + */ + publish(topic, body) { + return this.getAuthorizationHeader() + .then((headers) => { + return this.axios.post( + `{this.config.api}/send`, + { + topic, + body, + }, + { + headers, + } + ); + }) + .then((response) => { + return response; + }); + } + + /** + * Broadcast the message on the backbone + * + * Broadcast messages bypass the normal publisher queues + * this means they can be used to deliver messages more + * quickly in an emergency scenario. + * + * Messages should be passed through encode before sending + * @param {*} body + * @returns + */ + broadcast(body) { + return this.getAuthorizationHeader() + .then((headers) => { + return this.axios.post( + `{this.config.api}/notify`, + { + body, + }, + { + headers, + } + ); + }) + .then((response) => { + return response; + }); + } +} + +export { Adapter }; diff --git a/dist/adapter.js b/dist/adapter.js new file mode 100644 index 0000000..031bbba --- /dev/null +++ b/dist/adapter.js @@ -0,0 +1,169 @@ +'use strict'; + +var Validator = require('swagger-model-validator'); +var axios = require('axios'); + +/** + * Handle authentication and send/receive with the backbone + */ +class Adapter { + constructor(protocol, config) { + this.config = config; + this.protocol = protocol; + this.axios = axios; + this.validator = new Validator(protocol.schema); + this.auth(); + } + + /** + * Test parsing the message based on the provided protocol schema + * + * The message must be successfully json decoded into an object + * prior to validation + * + * At present this returns the validation result which is an + * object containing a boolean valid field as well as details + * of any errors. + * @param {object} message + * @returns {object} + */ + validate(message) { + return this.validator.validate( + message, + this.protocol.schema.definitions.Message + ); + } + + /** + * Ensure the token in this.credentials is still valid + * @returns {boolean} + */ + tokenValid() { + let valid = false; + if (this.credentials) { + const now = new Date().toISOString(); + valid = this.credentials.expiry > now; + } + return valid; + } + + /** + * Return an http headers object containing a bearer token authorisation header + * @returns {object} + */ + getAuthorizationHeader() { + if (!this.tokenValid()) + return this.auth().then((response) => { + return { + Authorization: `Bearer ${this.credentials.token}`, + }; + }); + else { + return Promise.resolve({ + Authorization: `Bearer ${this.credentials.token}`, + }); + } + } + + /** + * Do a client credentials grant and store in this.credentials + * @returns {object} + */ + auth() { + return this.axios + .get(`{this.config.api}/token`, { + params: { + client_id: this.config.client_id, + secret: this.config.secret, + }, + }) + .then((response) => { + this.credentials = response.data; + return response; + }); + } + + /** + * Call the GET /receive endpoint and process the messages with decode + * + * Returns the response + * @returns {object} + */ + poll() { + return this.getAuthorizationHeader() + .then((headers) => { + return this.axios.get(`{this.config.api}/receive`, { + headers, + }); + }) + .then((response) => { + response.data.forEach((message) => { + const parsed = JSON.parse(message.message); + const validation = this.validate(parsed); + if (validation.valid) { + const type = this.protocol.getType(parsed); + this.protocol.decode(type, parsed); + } + }); + return response; + }); + } + + /** + * Publish a message to the backbone with the specified topic + * + * Messages should be passed through encode before sending + * @param {string} topic + * @param {string} body + * @returns + */ + publish(topic, body) { + return this.getAuthorizationHeader() + .then((headers) => { + return this.axios.post( + `{this.config.api}/send`, + { + topic, + body, + }, + { + headers, + } + ); + }) + .then((response) => { + return response; + }); + } + + /** + * Broadcast the message on the backbone + * + * Broadcast messages bypass the normal publisher queues + * this means they can be used to deliver messages more + * quickly in an emergency scenario. + * + * Messages should be passed through encode before sending + * @param {*} body + * @returns + */ + broadcast(body) { + return this.getAuthorizationHeader() + .then((headers) => { + return this.axios.post( + `{this.config.api}/notify`, + { + body, + }, + { + headers, + } + ); + }) + .then((response) => { + return response; + }); + } +} + +exports.Adapter = Adapter; diff --git a/dist/protocol.esm.js b/dist/protocol.esm.js new file mode 100644 index 0000000..bd50069 --- /dev/null +++ b/dist/protocol.esm.js @@ -0,0 +1,71 @@ +import Validator from 'swagger-model-validator'; + +/** + * GenericProtocol defines a simple passthru handler for messages + * This can be extended to handle different schemas + * + * The assunption is that all messages conform to a single + * wrapper schema definition. Different payloads are handled + * by a oneOf definitions within the schema determined by a + * field value. + * + * This class can be extended and overridden to handle + * different schemas and to implement the client specific + * logic related to be executed when invoked for a given + * message type. + * + * By default encode and decode are just passthru stubs + * + * decode is invoked when receiving a message from the backbone + * encode is invoked before delivering a message to the backbone + * + * The intention is that these allow you to transform the message + * and call invoke internal functions and services as required + */ +class GenericProtocol { + constructor(schema, services) { + this.schema = schema; + this.validator = new Validator(schema); + this.services = services; + } + + /** + * Validate that a message meets the reqiured schema + * @param {object} message + * @returns {object} + */ + validate(message) { + return this.validator.validate(message, this.schema.definitions.Message); + } + + /** + * Identify the payload type from the message content + * @param {object} message + * @returns {string} + */ + getType(message) { + return message.message_type; + } + + /** + * Invoked on receiving a message from the backbone + * @param {string} type + * @param {object} message + * @returns {*} + */ + decode(type, message) { + return message; + } + + /** + * Optionally invoked before delivering a message to the backbone + * @param {string} type + * @param {*} message + * @returns {object} + */ + encode(type, message) { + return message; + } +} + +export { GenericProtocol }; diff --git a/dist/protocol.js b/dist/protocol.js new file mode 100644 index 0000000..4ced40e --- /dev/null +++ b/dist/protocol.js @@ -0,0 +1,73 @@ +'use strict'; + +var Validator = require('swagger-model-validator'); + +/** + * GenericProtocol defines a simple passthru handler for messages + * This can be extended to handle different schemas + * + * The assunption is that all messages conform to a single + * wrapper schema definition. Different payloads are handled + * by a oneOf definitions within the schema determined by a + * field value. + * + * This class can be extended and overridden to handle + * different schemas and to implement the client specific + * logic related to be executed when invoked for a given + * message type. + * + * By default encode and decode are just passthru stubs + * + * decode is invoked when receiving a message from the backbone + * encode is invoked before delivering a message to the backbone + * + * The intention is that these allow you to transform the message + * and call invoke internal functions and services as required + */ +class GenericProtocol { + constructor(schema, services) { + this.schema = schema; + this.validator = new Validator(schema); + this.services = services; + } + + /** + * Validate that a message meets the reqiured schema + * @param {object} message + * @returns {object} + */ + validate(message) { + return this.validator.validate(message, this.schema.definitions.Message); + } + + /** + * Identify the payload type from the message content + * @param {object} message + * @returns {string} + */ + getType(message) { + return message.message_type; + } + + /** + * Invoked on receiving a message from the backbone + * @param {string} type + * @param {object} message + * @returns {*} + */ + decode(type, message) { + return message; + } + + /** + * Optionally invoked before delivering a message to the backbone + * @param {string} type + * @param {*} message + * @returns {object} + */ + encode(type, message) { + return message; + } +} + +exports.GenericProtocol = GenericProtocol; diff --git a/package.json b/package.json index aca596b..4545242 100644 --- a/package.json +++ b/package.json @@ -24,8 +24,7 @@ "lintfix": "prettier --write --list-different . && yarn lint:js --fix", "prepare": "husky install", "test": "jest", - "build": "cross-env NODE_ENV=production rollup -c", - "install": "yarn build" + "build": "cross-env NODE_ENV=production rollup -c" }, "lint-staged": { "*.{js,vue}": "eslint --cache", diff --git a/src/adapter/index.js b/src/adapter/index.js index 33b6849..6b449f3 100644 --- a/src/adapter/index.js +++ b/src/adapter/index.js @@ -5,8 +5,8 @@ import axios from 'axios'; * Handle authentication and send/receive with the backbone */ export class Adapter { - constructor(protocol, soarConfig) { - this.apiRoot = soarConfig.api; + constructor(protocol, config) { + this.config = config; this.protocol = protocol; this.axios = axios; this.validator = new Validator(protocol.schema); @@ -69,10 +69,10 @@ export class Adapter { */ auth() { return this.axios - .get(`{this.apiRoot}/token`, { + .get(`{this.config.api}/token`, { params: { - client_id: soarConfig.client_id, - secret: soarConfig.secret, + client_id: this.config.client_id, + secret: this.config.secret, }, }) .then((response) => { @@ -90,7 +90,7 @@ export class Adapter { poll() { return this.getAuthorizationHeader() .then((headers) => { - return this.axios.get(`{this.apiRoot}/receive`, { + return this.axios.get(`{this.config.api}/receive`, { headers, }); }) @@ -119,7 +119,7 @@ export class Adapter { return this.getAuthorizationHeader() .then((headers) => { return this.axios.post( - `{this.apiRoot}/send`, + `{this.config.api}/send`, { topic, body, @@ -149,7 +149,7 @@ export class Adapter { return this.getAuthorizationHeader() .then((headers) => { return this.axios.post( - `{this.apiRoot}/notify`, + `{this.config.api}/notify`, { body, }, -- GitLab