From 1cceba3f8b61ed5cfb7986fe7df2cf8a825350ef Mon Sep 17 00:00:00 2001 From: Shweelan Samson Date: Sat, 12 Jan 2019 01:51:43 +0300 Subject: [PATCH] Move UI configs to DB --- bin/genieacs-ui | 15 +- build/build.js | 1 - config/config-sample.json | 1 + config/config-ui-sample.json | 313 ------------------------------- lib/config.js | 1 + lib/local-cache.js | 67 ++++++- lib/ui.js | 49 +++-- lib/ui/api-functions.js | 28 ++- lib/ui/config.js | 36 ---- lib/ui/db.js | 23 +-- package.json | 2 +- tools/configure-ui | 175 +++++++++++++++++ ui/components/all-parameters.js | 6 +- ui/components/container.js | 8 +- ui/components/overview-dot.js | 6 +- ui/components/parameter-table.js | 19 +- ui/components/parameter.js | 4 +- ui/components/summon-button.js | 7 +- ui/devices-page.js | 7 +- ui/overview-page.js | 6 +- ui/pie-chart-component.js | 8 +- ui/smart-query.js | 3 +- 22 files changed, 337 insertions(+), 448 deletions(-) delete mode 100644 config/config-ui-sample.json delete mode 100644 lib/ui/config.js create mode 100755 tools/configure-ui diff --git a/bin/genieacs-ui b/bin/genieacs-ui index de9f54c..ff864a3 100755 --- a/bin/genieacs-ui +++ b/bin/genieacs-ui @@ -11,6 +11,7 @@ const cluster = require("../lib/cluster"); const server = require("../lib/server"); const ui = require("../lib/ui"); const db = require("../lib/ui/db"); +const db2 = require("../lib/db"); const cache = require("../lib/cache"); const VERSION = require("../package.json").version; @@ -22,6 +23,7 @@ const SERVICE_PORT = config.get("UI_PORT"); function exitWorkerGracefully() { db.disconnect(); + db2.disconnect(); cache.disconnect(); cluster.worker.disconnect(); } @@ -105,8 +107,17 @@ if (!cluster.worker) { ui.listener(req, res); }; - cache.connect(err => { + db2.connect(err => { if (err) throw err; - server.start(SERVICE_PORT, SERVICE_ADDRESS, ssl, listener, ui.onConnection); + cache.connect(err => { + if (err) throw err; + server.start( + SERVICE_PORT, + SERVICE_ADDRESS, + ssl, + listener, + ui.onConnection + ); + }); }); } diff --git a/build/build.js b/build/build.js index c30f364..3ed7979 100644 --- a/build/build.js +++ b/build/build.js @@ -78,7 +78,6 @@ async function copyStatic() { "CHANGELOG.md", "npm-shrinkwrap.json", "config/config-sample.json", - "config/config-ui-sample.json", "config/auth-sample.js", "config/ext-sample.js", "public/logo.svg", diff --git a/config/config-sample.json b/config/config-sample.json index 28f8c03..51251e3 100644 --- a/config/config-sample.json +++ b/config/config-sample.json @@ -10,5 +10,6 @@ "FS_HOSTNAME" : "acs.example.com", "UI_INTERFACE" : "0.0.0.0", "UI_PORT" : 3000, + "UI_JWT_SECRET": "", "DEBUG" : false } diff --git a/config/config-ui-sample.json b/config/config-ui-sample.json deleted file mode 100644 index e2950b1..0000000 --- a/config/config-ui-sample.json +++ /dev/null @@ -1,313 +0,0 @@ -{ - "server": { - "nbi": "http://127.0.0.1:7557/", - "mongodbConnectionUrl": "mongodb://127.0.0.1:27017", - "jwtSecret": "jwtSecret" - }, - "auth": { - "simple": { - "users": { - "admin": { - "password": "admin", - "roles": "admin" - }, - "view": { - "password": "view", - "roles": "view" - } - } - } - }, - "permissions": { - "admin": [ - { - "devices": { - "access": 3, - "validate": { - "pass": true - } - }, - "faults": { - "access": 3, - "validate": { - "pass": true - } - }, - "presets": { - "access": 3, - "validate": { - "pass": true - } - }, - "provisions" : { - "access": 3, - "validate": { - "pass": true - } - }, - "virtualParameters" : { - "access": 3, - "validate": { - "pass": true - } - }, - "files": { - "access": 3, - "validate": { - "pass": true - } - } - } - ], - "view": [ - { - "devices": { - "access": 2 - }, - "faults": { - "access": 2 - }, - "presets": { - "access": 2 - }, - "provisions": { - "access": 2 - }, - "virtualParameters": { - "access": 2 - }, - "files": { - "access": 2 - } - } - ] - }, - "ui": { - "pageSize": 10, - "filters": [ - { - "label": "Serial number", - "parameter": "DeviceID.SerialNumber", - "type": "string" - }, - { - "label": "Product class", - "parameter": "DeviceID.ProductClass", - "type": "string" - }, - { - "label": "Tag", - "parameter": "tag", - "type": "string" - } - ], - "index": [ - { - "label": "Serial number", - "parameter": "DeviceID.SerialNumber", - "type": "device-link", - "components": [ - { - "type": "parameter" - } - ] - }, - { - "label": "Product class", - "parameter": "DeviceID.ProductClass" - }, - { - "label": "Software version", - "parameter": "InternetGatewayDevice.DeviceInfo.SoftwareVersion" - }, - { - "label": "IP", - "parameter": "InternetGatewayDevice.WANDevice.1.WANConnectionDevice.1.WANIPConnection.1.ExternalIPAddress" - }, - { - "label": "SSID", - "parameter": "InternetGatewayDevice.LANDevice.1.WLANConfiguration.1.SSID" - }, - { - "label": "Last inform", - "parameter": "DATE_STRING(Events.Inform)", - "type": "container", - "element": "span.inform", - "components": [ - { - "type": "parameter" - }, - { - "type": "overview-dot", - "chart": "online" - } - ] - }, - { - "label": "Tags", - "parameter": "Tags", - "type": "tags", - "writable": false, - "unsortable": true - } - ], - "overview": { - "groups": { - "online": { - "label": "", - "charts": [ - "online" - ] - } - }, - "charts": { - "online": { - "label": "Online status", - "slices": { - "onlineNow": { - "label": "Online now", - "color": "#31a354", - "filter": "Events.Inform > NOW() - 5 * 60 * 1000" - }, - "past24": { - "label": "Past 24 hours", - "color": "#addd8e", - "filter": "Events.Inform > (NOW() - 5 * 60 * 1000) - (24 * 60 * 60 * 1000) AND Events.Inform < (NOW() - 5 * 60 * 1000)" - }, - "others": { - "label": "Others", - "color": "#f7fcb9", - "filter": "Events.Inform < (NOW() - 5 * 60 * 1000) - (24 * 60 * 60 * 1000)" - } - } - } - } - }, - "device": [ - { - "type": "tags" - }, - { - "type": "ping" - }, - { - "type": "parameter-list", - "parameters": [ - { - "type": "container", - "label": "Last inform", - "element": "span.inform", - "parameter": "DATE_STRING(Events.Inform)", - "components": [ - { - "type": "parameter" - }, - { - "type": "overview-dot", - "chart": "online" - }, - { - "type": "summon-button", - "parameters": [ - "InternetGatewayDevice.DeviceInfo.HardwareVersion", - "InternetGatewayDevice.DeviceInfo.SoftwareVersion", - "InternetGatewayDevice.WANDevice.1.WANConnectionDevice.1.WANIPConnection.1.MACAddress", - "InternetGatewayDevice.WANDevice.1.WANConnectionDevice.1.WANIPConnection.1.ExternalIPAddress", - "InternetGatewayDevice.LANDevice.1.WLANConfiguration.1.SSID", - "InternetGatewayDevice.LANDevice.1.WLANConfiguration.1.KeyPassphrase", - "InternetGatewayDevice.LANDevice.1.Hosts.Host" - ] - } - ] - }, - { - "label": "Serial number", - "parameter": "InternetGatewayDevice.DeviceInfo.SerialNumber" - }, - { - "label": "Product class", - "parameter": "InternetGatewayDevice.DeviceInfo.ProductClass" - }, - { - "label": "OUI", - "parameter": "InternetGatewayDevice.DeviceInfo.ManufacturerOUI" - }, - { - "label": "Manufacturer", - "parameter": "InternetGatewayDevice.DeviceInfo.Manufacturer" - }, - { - "label": "Hardware version", - "parameter": "InternetGatewayDevice.DeviceInfo.HardwareVersion" - }, - { - "label": "Software version", - "parameter": "InternetGatewayDevice.DeviceInfo.SoftwareVersion" - }, - { - "label": "MAC", - "parameter": "InternetGatewayDevice.WANDevice.1.WANConnectionDevice.1.WANIPConnection.1.MACAddress" - }, - { - "label": "IP", - "parameter": "InternetGatewayDevice.WANDevice.1.WANConnectionDevice.1.WANIPConnection.1.ExternalIPAddress" - }, - { - "label": "WLAN SSID", - "parameter": "InternetGatewayDevice.LANDevice.1.WLANConfiguration.1.SSID" - }, - { - "label": "WLAN passphrase", - "parameter": "InternetGatewayDevice.LANDevice.1.WLANConfiguration.1.KeyPassphrase" - } - ] - }, - { - "type": "parameter-table", - "label": "WLAN hosts", - "parameter": "InternetGatewayDevice.LANDevice.1.Hosts.Host", - "childParameters": [ - { - "label": "Host name", - "parameter": "HostName" - }, - { - "label": "IP address", - "parameter": "IPAddress" - }, - { - "label": "MAC address", - "parameter": "MACAddress" - } - ] - }, - { - "type": "container", - "element": "div", - "components": [ - { - "type": "container", - "element": "h3", - "components": ["Faults"] - }, - {"type": "device-faults"} - ] - }, - { - "type": "container", - "element": "div.container-full-width", - "components": [ - { - "type": "container", - "element": "h3", - "components": ["All parameters"] - }, - {"type": "all-parameters"} - ] - }, - { - "type": "device-actions" - } - ] - } -} diff --git a/lib/config.js b/lib/config.js index cf9fe8c..aab78f7 100644 --- a/lib/config.js +++ b/lib/config.js @@ -59,6 +59,7 @@ const options = { UI_SSL: { type: "bool", default: false }, UI_LOG_FILE: { type: "path", default: "" }, UI_ACCESS_LOG_FILE: { type: "path", default: "" }, + UI_JWT_SECRET: { type: "string", default: "" }, UDP_CONNECTION_REQUEST_PORT: { type: "int", default: 0 }, diff --git a/lib/local-cache.js b/lib/local-cache.js index 90e9933..dd2ce54 100644 --- a/lib/local-cache.js +++ b/lib/local-cache.js @@ -307,8 +307,30 @@ function refresh(callback) { db.getConfig((err, conf) => { if (err) return void reject(err); + conf.sort((a, b) => (a.id > b.id ? 1 : a.id < b.id ? -1 : 0)); + const _config = {}; - for (const c of conf) _config[c.id] = c.value; + + for (const c of conf) { + if ( + c.id.startsWith("auth.") || + c.id.startsWith("permissions.") || + c.id.startsWith("ui.") + ) { + const keys = c.id.split("."); + let ref = _config; + while (keys.length > 1) { + const k = keys.shift(); + if (typeof ref[k] !== "object") ref[k] = {}; + ref = ref[k]; + } + // Evaluate expressions to simplify them + ref[keys[0]] = expression.evaluate(c.value); + } else { + // Evaluate expressions to simplify them + _config[c.id] = expression.evaluate(c.value); + } + } resolve(_config); }); @@ -348,9 +370,22 @@ function refresh(callback) { } function getCurrentSnapshot(callback) { - if (Date.now() < nextRefresh) return void callback(null, currentSnapshot); - refresh(err => { - callback(err, currentSnapshot); + if (Date.now() < nextRefresh) { + if (callback) return callback(null, currentSnapshot); + return Promise.resolve(currentSnapshot); + } + + if (callback) { + return refresh(err => { + callback(err, currentSnapshot); + }); + } + + return new Promise((resolve, reject) => { + refresh(err => { + if (err) return void reject(err); + resolve(currentSnapshot); + }); }); } @@ -410,6 +445,27 @@ function getConfig(snapshotKey, key, context, now) { return expression.evaluate(snapshot.config[key], context, now || Date.now()); } +function getAuthConfig(snapshotKey) { + const snapshot = snapshots.get(snapshotKey); + if (!snapshot) throw new Error("Cache snapshot does not exist"); + + return snapshot.config.auth; +} + +function getPermissionsConfig(snapshotKey) { + const snapshot = snapshots.get(snapshotKey); + if (!snapshot) throw new Error("Cache snapshot does not exist"); + + return snapshot.config.permissions; +} + +function getUiConfig(snapshotKey) { + const snapshot = snapshots.get(snapshotKey); + if (!snapshot) throw new Error("Cache snapshot does not exist"); + + return snapshot.config.ui; +} + exports.getCurrentSnapshot = getCurrentSnapshot; exports.hasSnapshot = hasSnapshot; exports.getPresets = getPresets; @@ -417,3 +473,6 @@ exports.getProvisions = getProvisions; exports.getVirtualParameters = getVirtualParameters; exports.getFiles = getFiles; exports.getConfig = getConfig; +exports.getAuthConfig = getAuthConfig; +exports.getPermissionsConfig = getPermissionsConfig; +exports.getUiConfig = getUiConfig; diff --git a/lib/ui.js b/lib/ui.js index 8a7dd83..2d65e18 100644 --- a/lib/ui.js +++ b/lib/ui.js @@ -8,21 +8,24 @@ const koaBodyParser = require("koa-bodyparser"); const koaJwt = require("koa-jwt"); const jwt = require("jsonwebtoken"); -const config = require("./ui/config"); +const config = require("./config"); const api = require("./ui/api"); const Authorizer = require("./common/authorizer"); const expression = require("./common/expression"); const logger = require("./logger"); +const localCache = require("./local-cache"); const koa = new Koa(); const router = new Router(); -const JWT_SECRET = config.server.jwtSecret; +const JWT_SECRET = config.get("UI_JWT_SECRET"); const JWT_COOKIE = "genieacs-ui-jwt"; -function getPermissionSets(roles) { - const allPermissions = config.permissions; - const permissionSets = roles.map(role => { +function getPermissionSets(ctx) { + const allPermissions = localCache.getPermissionsConfig( + ctx.state.configSnapshot + ); + const permissionSets = ctx.state.user.roles.map(role => { return Object.values(allPermissions[role] || {}).map(p => { p = Object.assign({}, p); for (let [k, v] of Object.entries(p)) { @@ -47,8 +50,9 @@ function getPermissionSets(roles) { return permissionSets; } -function authSimple(username, password) { - const user = config.auth.simple.users[username]; +function authSimple(ctx, username, password) { + const authConfig = localCache.getAuthConfig(ctx.state.configSnapshot); + const user = authConfig.simple.users[username]; if (user && user.password === password) { const roles = user.roles.split(",").map(s => s.trim()); return Promise.resolve(roles); @@ -60,6 +64,12 @@ koa.on("error", async err => { throw err; }); +koa.use(async (ctx, next) => { + const configSnapshot = await localCache.getCurrentSnapshot(); + ctx.state.configSnapshot = configSnapshot; + return next(); +}); + koa.use( koaJwt({ secret: JWT_SECRET, @@ -69,18 +79,21 @@ koa.use( ); koa.use(async (ctx, next) => { - if (ctx.state.user && ctx.state.user.roles) { - ctx.state.authorizer = new Authorizer( - getPermissionSets(ctx.state.user.roles) - ); - } else { - ctx.state.authorizer = new Authorizer([]); - } + if (ctx.state.user && ctx.state.user.roles) + ctx.state.authorizer = new Authorizer(getPermissionSets(ctx)); + else ctx.state.authorizer = new Authorizer([]); return next(); }); router.post("/login", async ctx => { + if (!JWT_SECRET) { + ctx.status = 500; + ctx.body = "UI_JWT_SECRET is not set"; + logger.error({ message: "UI_JWT_SECRET is not set" }); + return; + } + const username = ctx.request.body.username; const password = ctx.request.body.password; @@ -105,7 +118,7 @@ router.post("/login", async ctx => { logger.accessWarn(log); } - const roles = await authSimple(username, password); + const roles = await authSimple(ctx, username, password); if (roles) return void success(roles, "simple"); @@ -135,7 +148,7 @@ router.use("/api", api.routes(), api.allowedMethods()); router.get("/", async ctx => { let permissionSets = []; if (ctx.state.user && ctx.state.user.roles) - permissionSets = getPermissionSets(ctx.state.user.roles); + permissionSets = getPermissionSets(ctx); ctx.body = ` @@ -146,7 +159,9 @@ router.get("/", async ctx => {