From f990d3f674a661dddd3735d8d794c8bdde26ba29 Mon Sep 17 00:00:00 2001 From: Kyle Klaus Date: Fri, 10 Apr 2020 16:38:54 -0700 Subject: [PATCH] add access list clients to back-end --- backend/internal/access-list.js | 81 +++++++++++++++---- .../20200410143839_access_list_client.js | 46 +++++++++++ backend/models/access_list.js | 20 ++++- backend/models/access_list_client.js | 54 +++++++++++++ backend/schema/endpoints/access-lists.json | 40 +++++++++ 5 files changed, 220 insertions(+), 21 deletions(-) create mode 100644 backend/migrations/20200410143839_access_list_client.js create mode 100644 backend/models/access_list_client.js diff --git a/backend/internal/access-list.js b/backend/internal/access-list.js index bfecf613..0411b59d 100644 --- a/backend/internal/access-list.js +++ b/backend/internal/access-list.js @@ -1,14 +1,15 @@ -const _ = require('lodash'); -const fs = require('fs'); -const batchflow = require('batchflow'); -const logger = require('../logger').access; -const error = require('../lib/error'); -const accessListModel = require('../models/access_list'); -const accessListAuthModel = require('../models/access_list_auth'); -const proxyHostModel = require('../models/proxy_host'); -const internalAuditLog = require('./audit-log'); -const internalNginx = require('./nginx'); -const utils = require('../lib/utils'); +const _ = require('lodash'); +const fs = require('fs'); +const batchflow = require('batchflow'); +const logger = require('../logger').access; +const error = require('../lib/error'); +const accessListModel = require('../models/access_list'); +const accessListAuthModel = require('../models/access_list_auth'); +const accessListClientModel = require('../models/access_list_client'); +const proxyHostModel = require('../models/proxy_host'); +const internalAuditLog = require('./audit-log'); +const internalNginx = require('./nginx'); +const utils = require('../lib/utils'); function omissions () { return ['is_deleted']; @@ -35,8 +36,9 @@ const internalAccessList = { .then((row) => { data.id = row.id; - // Now add the items let promises = []; + + // Now add the items data.items.map((item) => { promises.push(accessListAuthModel .query() @@ -48,13 +50,27 @@ const internalAccessList = { ); }); + // Now add the clients + if (typeof data.clients !== 'undefined' && data.clients) { + data.clients.map((client) => { + promises.push(accessListClientModel + .query() + .insert({ + access_list_id: row.id, + address: client.address, + directive: client.directive + }) + ); + }); + } + return Promise.all(promises); }) .then(() => { // re-fetch with expansions return internalAccessList.get(access, { id: data.id, - expand: ['owner', 'items'] + expand: ['owner', 'items', 'clients'] }, true /* <- skip masking */); }) .then((row) => { @@ -152,6 +168,37 @@ const internalAccessList = { } }); } + + // Check for clients and add/update/remove them + if (typeof data.clients !== 'undefined' && data.clients) { + let promises = []; + + data.clients.map(function (client) { + if (client.address) { + promises.push(accessListAuthModel + .query() + .insert({ + access_list_id: data.id, + address: client.address, + directive: client.directive + }) + ); + } + }); + + let query = accessListClientModel + .query() + .delete() + .where('access_list_id', data.id); + + return query + .then(() => { + // Add new items + if (promises.length) { + return Promise.all(promises); + } + }); + } }) .then(() => { // Add to audit log @@ -166,7 +213,7 @@ const internalAccessList = { // re-fetch with expansions return internalAccessList.get(access, { id: data.id, - expand: ['owner', 'items'] + expand: ['owner', 'items', 'clients'] }, true /* <- skip masking */); }) .then((row) => { @@ -204,7 +251,7 @@ const internalAccessList = { .joinRaw('LEFT JOIN `proxy_host` ON `proxy_host`.`access_list_id` = `access_list`.`id` AND `proxy_host`.`is_deleted` = 0') .where('access_list.is_deleted', 0) .andWhere('access_list.id', data.id) - .allowEager('[owner,items,proxy_hosts]') + .allowEager('[owner,items,clients,proxy_hosts]') .omit(['access_list.is_deleted']) .first(); @@ -246,7 +293,7 @@ const internalAccessList = { delete: (access, data) => { return access.can('access_lists:delete', data.id) .then(() => { - return internalAccessList.get(access, {id: data.id, expand: ['proxy_hosts', 'items']}); + return internalAccessList.get(access, {id: data.id, expand: ['proxy_hosts', 'items', 'clients']}); }) .then((row) => { if (!row) { @@ -330,7 +377,7 @@ const internalAccessList = { .where('access_list.is_deleted', 0) .groupBy('access_list.id') .omit(['access_list.is_deleted']) - .allowEager('[owner,items]') + .allowEager('[owner,items,clients]') .orderBy('access_list.name', 'ASC'); if (access_data.permission_visibility !== 'all') { diff --git a/backend/migrations/20200410143839_access_list_client.js b/backend/migrations/20200410143839_access_list_client.js new file mode 100644 index 00000000..2432d773 --- /dev/null +++ b/backend/migrations/20200410143839_access_list_client.js @@ -0,0 +1,46 @@ +const migrate_name = 'access_list_client'; +const logger = require('../logger').migrate; + +/** + * Migrate + * + * @see http://knexjs.org/#Schema + * + * @param {Object} knex + * @param {Promise} Promise + * @returns {Promise} + */ +exports.up = function (knex/*, Promise*/) { + + logger.info('[' + migrate_name + '] Migrating Up...'); + + return knex.schema.createTable('access_list_client', (table) => { + table.increments().primary(); + table.dateTime('created_on').notNull(); + table.dateTime('modified_on').notNull(); + table.integer('access_list_id').notNull().unsigned(); + table.string('address').notNull(); + table.string('directive').notNull(); + table.json('meta').notNull(); + + }) + .then(function () { + logger.info('[' + migrate_name + '] access_list_client Table created'); + }); +}; + +/** + * Undo Migrate + * + * @param {Object} knex + * @param {Promise} Promise + * @returns {Promise} + */ +exports.down = function (knex/*, Promise*/) { + logger.info('[' + migrate_name + '] Migrating Down...'); + + return knex.schema.dropTable('access_list_client') + .then(() => { + logger.info('[' + migrate_name + '] access_list_client Table dropped'); + }); +}; diff --git a/backend/models/access_list.js b/backend/models/access_list.js index 7704893f..43a2f128 100644 --- a/backend/models/access_list.js +++ b/backend/models/access_list.js @@ -1,10 +1,11 @@ // Objection Docs: // http://vincit.github.io/objection.js/ -const db = require('../db'); -const Model = require('objection').Model; -const User = require('./user'); -const AccessListAuth = require('./access_list_auth'); +const db = require('../db'); +const Model = require('objection').Model; +const User = require('./user'); +const AccessListAuth = require('./access_list_auth'); +const AccessListClient = require('./access_list_client'); Model.knex(db); @@ -62,6 +63,17 @@ class AccessList extends Model { qb.omit(['id', 'created_on', 'modified_on', 'access_list_id', 'meta']); } }, + clients: { + relation: Model.HasManyRelation, + modelClass: AccessListClient, + join: { + from: 'access_list.id', + to: 'access_list_client.access_list_id' + }, + modify: function (qb) { + qb.omit(['id', 'created_on', 'modified_on', 'access_list_id', 'meta']); + } + }, proxy_hosts: { relation: Model.HasManyRelation, modelClass: ProxyHost, diff --git a/backend/models/access_list_client.js b/backend/models/access_list_client.js new file mode 100644 index 00000000..1bee2426 --- /dev/null +++ b/backend/models/access_list_client.js @@ -0,0 +1,54 @@ +// Objection Docs: +// http://vincit.github.io/objection.js/ + +const db = require('../db'); +const Model = require('objection').Model; + +Model.knex(db); + +class AccessListClient extends Model { + $beforeInsert () { + this.created_on = Model.raw('NOW()'); + this.modified_on = Model.raw('NOW()'); + + // Default for meta + if (typeof this.meta === 'undefined') { + this.meta = {}; + } + } + + $beforeUpdate () { + this.modified_on = Model.raw('NOW()'); + } + + static get name () { + return 'AccessListClient'; + } + + static get tableName () { + return 'access_list_client'; + } + + static get jsonAttributes () { + return ['meta']; + } + + static get relationMappings () { + return { + access_list: { + relation: Model.HasOneRelation, + modelClass: require('./access_list'), + join: { + from: 'access_list_client.access_list_id', + to: 'access_list.id' + }, + modify: function (qb) { + qb.where('access_list.is_deleted', 0); + qb.omit(['created_on', 'modified_on', 'is_deleted', 'access_list_id']); + } + } + }; + } +} + +module.exports = AccessListClient; diff --git a/backend/schema/endpoints/access-lists.json b/backend/schema/endpoints/access-lists.json index da90a050..f2ff254d 100644 --- a/backend/schema/endpoints/access-lists.json +++ b/backend/schema/endpoints/access-lists.json @@ -19,6 +19,14 @@ "type": "string", "description": "Name of the Access List" }, + "directive": { + "type": "string", + "enum": ["allow", "deny"] + }, + "address": { + "type": "string", + "format": "ipv4" + }, "meta": { "type": "object" } @@ -96,6 +104,22 @@ } } }, + "clients": { + "type": "array", + "minItems": 0, + "items": { + "type": "object", + "additionalProperties": false, + "properties": { + "address": { + "$ref": "#/definitions/address" + }, + "directive": { + "$ref": "#/definitions/directive" + } + } + } + }, "meta": { "$ref": "#/definitions/meta" } @@ -141,6 +165,22 @@ } } } + }, + "clients": { + "type": "array", + "minItems": 0, + "items": { + "type": "object", + "additionalProperties": false, + "properties": { + "address": { + "$ref": "#/definitions/address" + }, + "directive": { + "$ref": "#/definitions/directive" + } + } + } } } },