diff --git a/.gitignore b/.gitignore index d5a9aeb477d2e00b007fa30c3101803ecb83debc..6f70c11376c37ee4ff8d9b888e9e2b48b1a75fec 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 0000000000000000000000000000000000000000..a7e1c8d81f13ccd323f1c83d321d9141ffbd5036 --- /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 0000000000000000000000000000000000000000..031bbba0e742db6d0170ccddab409ef82dde28de --- /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 0000000000000000000000000000000000000000..bd50069d95117a2d8ff08f85b37dc4f8c878eef3 --- /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 0000000000000000000000000000000000000000..4ced40e9adc71012668693a495fee38e2cf30047 --- /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 aca596b87c2fd37097f0e8d1903673608fe874cf..454524210b54d6149a624640f705954574809c55 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 33b6849c47f0d7874b068775365d0649001d62aa..6b449f3b5d6e3274e9eb947db660e1b9900da008 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, },