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