diff --git a/nodejs/jsdoc/adminSecurity.html b/nodejs/jsdoc/adminSecurity.html index 8c67208..eda2b0d 100644 --- a/nodejs/jsdoc/adminSecurity.html +++ b/nodejs/jsdoc/adminSecurity.html @@ -31,7 +31,7 @@ -

Home

Namespaces

+

Home

Namespaces

@@ -257,7 +257,7 @@

- Documentation generated by JSDoc 4.0.2 on Wed Sep 20 2023 08:41:41 GMT+0000 (Coordinated Universal Time) using the docdash theme. + Documentation generated by JSDoc 4.0.2 on Wed Sep 20 2023 10:42:27 GMT+0000 (Coordinated Universal Time) using the docdash theme.
diff --git a/nodejs/jsdoc/components_admin_security.js.html b/nodejs/jsdoc/components_admin_security.js.html index 16782a5..bc2333e 100644 --- a/nodejs/jsdoc/components_admin_security.js.html +++ b/nodejs/jsdoc/components_admin_security.js.html @@ -31,7 +31,7 @@ -

Home

Namespaces

+

Home

Namespaces

@@ -85,7 +85,7 @@

components/admin/security.js


- Documentation generated by JSDoc 4.0.2 on Wed Sep 20 2023 08:41:41 GMT+0000 (Coordinated Universal Time) using the docdash theme. + Documentation generated by JSDoc 4.0.2 on Wed Sep 20 2023 10:42:27 GMT+0000 (Coordinated Universal Time) using the docdash theme.
diff --git a/nodejs/jsdoc/components_items_backend.js.html b/nodejs/jsdoc/components_items_backend.js.html index 1a20cb5..71c2fe1 100644 --- a/nodejs/jsdoc/components_items_backend.js.html +++ b/nodejs/jsdoc/components_items_backend.js.html @@ -31,7 +31,7 @@ -

Home

Namespaces

+

Home

Namespaces

@@ -185,22 +185,18 @@

components/items/backend.js

const response = await fetch(HOST + '/rest/items?recursive=false&fields=name%2C%20tags', { headers: headers }); const allItems = await response.json(); let filteredItems = []; - if (org.includes(ADMIN_OU)) { - //Member of ADMIN_OU has full access - filteredItems = allItems; - } else { - for (const i in allItems) { - for (const j in allItems[i].tags) { - if (allItems[i].tags[j].startsWith(ACL_PREFIX)) { - if (allItems[i].tags[j].substring(ACL_PREFIX.length) === user || - org.includes(allItems[i].tags[j].substring(ACL_PREFIX.length)) || - allItems[i].tags[j].substring(ACL_PREFIX.length) === EVERYONE_OU) { - //Access allow when tags include user name, user org or EVERYONE_OU - filteredItems.push(allItems[i].name); - } - } - } - } + for (const i in allItems) { + for (const j in allItems[i].tags) { + if (allItems[i].tags[j].startsWith(ACL_PREFIX)) { + if (allItems[i].tags[j].substring(ACL_PREFIX.length) === user || + org.includes(allItems[i].tags[j].substring(ACL_PREFIX.length)) || + org.includes(ADMIN_OU) || + allItems[i].tags[j].substring(ACL_PREFIX.length) === EVERYONE_OU) { + //Access allow when tags include user name, user org or EVERYONE_OU, Member of ADMIN_OU has full access + if (!filteredItems.includes(allItems[i].name)) filteredItems.push(allItems[i].name); + } + } + } } itemsForUserDb.insert({ name: user, lastupdate: now, items: filteredItems }); const status = response.status; @@ -315,7 +311,8 @@

components/items/backend.js

logger.error(error); error(); } -}; +}; + @@ -329,7 +326,7 @@

components/items/backend.js


- Documentation generated by JSDoc 4.0.2 on Wed Sep 20 2023 08:41:41 GMT+0000 (Coordinated Universal Time) using the docdash theme. + Documentation generated by JSDoc 4.0.2 on Wed Sep 20 2023 10:42:27 GMT+0000 (Coordinated Universal Time) using the docdash theme.
diff --git a/nodejs/jsdoc/components_items_routes.js.html b/nodejs/jsdoc/components_items_routes.js.html index fbd2412..9e41f74 100644 --- a/nodejs/jsdoc/components_items_routes.js.html +++ b/nodejs/jsdoc/components_items_routes.js.html @@ -31,7 +31,7 @@ -

Home

Namespaces

+

Home

Namespaces

@@ -51,6 +51,7 @@

components/items/routes.js

import { requireHeader } from './../middleware.js'; import { backendInfo } from '../../server.js'; import { getAllItems, getItem, getItemState, getItemSemantic, sendItemCommand, sendEventsItems } from './backend.js'; +import proxy from 'express-http-proxy'; const itemAccess = () => { return async function (req, res, next) { @@ -70,7 +71,7 @@

components/items/routes.js

}; /** - * Provide required /items routes. + * Provides required /items routes. * * @memberof routes * @param {*} app expressjs app @@ -114,8 +115,12 @@

components/items/routes.js

app.get('/auth/items', requireHeader('X-OPENHAB-USER'), requireHeader('X-ORIGINAL-URI'), async (req, res) => { const org = req.headers['x-openhab-org'] || ''; const user = req.headers['x-openhab-user']; - const regex = /\/items\/([a-zA-Z_0-9]+)/; - const itemname = regex.exec(req.headers['x-original-uri']); + const regex1 = /(\?|&)items=([a-zA-Z_0-9]+)[&]?/; + const regex2 = /\/items\/([a-zA-Z_0-9]+)/; + const itemname1 = regex1.exec(req.headers['x-original-uri']); + const itemname2 = regex2.exec(req.headers['x-original-uri']); + const itemname = (itemname1 == null) ? itemname2 : itemname1; + if (itemname == null) return res.status(403).send(); try { const allowed = await itemAllowedForClient(backendInfo.HOST, req, user, org, itemname[1]); @@ -136,7 +141,7 @@

components/items/routes.js

* summary: Get all available Items. * tags: [Items] * parameters: - * - in: parameters + * - in: query * name: parameters * required: false * description: Query parameters from API (metadata, recursive, type, tags, fields) @@ -172,17 +177,17 @@

components/items/routes.js

const allItems = await getAllItems(backendInfo.HOST, req); let filteredItems = []; for (const i in allItems) { - if (await itemAllowedForClient(backendInfo.HOST, req, user, org, allItems[i].name) === true) { - let tempItem = allItems[i]; - const tempItem2 = allItems[i].members; - //recursive filtering of member items - if (Array.isArray(tempItem2)) { - tempItem.members = []; - const tempMembers = await itemsFilterForClient(backendInfo.HOST, req, user, org, tempItem2); - tempItem.members = tempMembers; - } - filteredItems.push(tempItem); - } + if (await itemAllowedForClient(backendInfo.HOST, req, user, org, allItems[i].name) === true) { + let tempItem = allItems[i]; + const tempItem2 = allItems[i].members; + //recursive filtering of member items + if (Array.isArray(tempItem2)) { + tempItem.members = []; + const tempMembers = await itemsFilterForClient(backendInfo.HOST, req, user, org, tempItem2); + tempItem.members = tempMembers; + } + filteredItems.push(tempItem); + } } res.status(200).send(filteredItems); } catch (e) { @@ -205,10 +210,10 @@

components/items/routes.js

* schema: * type: string * style: form - * - in: parameters + * - in: query * name: parameters * required: false - * description: Query parameters from API (metadate, recursive) + * description: Query parameters from API (metadata, recursive) * schema: * type: string * style: form @@ -243,13 +248,13 @@

components/items/routes.js

const user = req.headers['x-openhab-user']; try { let response = await getItem(backendInfo.HOST, req, req.params.itemname); - const tempItem = response.json.members; - //recursive filtering of member items - if (Array.isArray(tempItem)) { - response.json.members = []; - const tempMembers = await itemsFilterForClient(backendInfo.HOST, req, user, org, tempItem); - response.json.members = tempMembers; - } + const tempItem = response.json.members; + //recursive filtering of member items + if (Array.isArray(tempItem)) { + response.json.members = []; + const tempMembers = await itemsFilterForClient(backendInfo.HOST, req, user, org, tempItem); + response.json.members = tempMembers; + } res.status(response.status).send(response.json); } catch (e) { console.info(e); @@ -452,9 +457,9 @@

components/items/routes.js

try { let filteredItems = []; for (const i in allItems) { - if (await itemAllowedForClient(backendInfo.HOST, req, user, org, allItems[i]) === true) { - filteredItems.push(allItems[i]); - } + if (await itemAllowedForClient(backendInfo.HOST, req, user, org, allItems[i]) === true) { + filteredItems.push(allItems[i]); + } } const status = await sendEventsItems(backendInfo.HOST, req, req.params.connectionId, filteredItems); res.status(status).send(); @@ -464,6 +469,131 @@

components/items/routes.js

} }); + + /** + * @swagger + * /chart?items={itemname}: + * get: + * summary: Gets BasicUI OH chart. + * tags: [Sitemaps] + * parameters: + * - in: query + * name: parameters + * required: true + * description: Query parameters (e.g. items) + * schema: + * type: string + * style: form + * - in: header + * name: X-OPENHAB-USER + * required: true + * description: Name of user + * schema: + * type: string + * style: form + * - in: header + * name: X-OPENHAB-ORG + * required: false + * description: Organisations the user is member of + * schema: + * type: string + * style: form + * responses: + * 200: + * description: OK + * 404: + * description: Chart not found + */ + app.get('/chart', requireHeader('X-OPENHAB-USER'), proxy(backendInfo.HOST + '/chart', { + proxyReqPathResolver: async (req) => { + const org = req.headers['x-openhab-org'] || ''; + const user = req.headers['x-openhab-user']; + const reqPath = req.path; + let reqQuery = req.query; + + //filtering of chart items + if (reqQuery.hasOwnProperty('items')) { + let allItems = reqQuery.items; + if (typeof allItems === 'string') allItems = allItems.toString().split(','); + let filteredItems = []; + for (const i in allItems) { + if (await itemAllowedForClient(backendInfo.HOST, req, user, org, allItems[i]) === true) filteredItems.push(allItems[i]); + } + reqQuery.items = filteredItems.toString(); + } + let updatedQuery = Object.entries(reqQuery).reduce((str, [p, val]) => { + return `${str}&${p}=${val}`; + }, ''); + updatedQuery = updatedQuery.substring(1); + + return reqPath + ((updatedQuery) ? '?' + updatedQuery : ''); + }, + proxyErrorHandler: function(err, res, next) { + console.info(err); + res.status(500).send(); + } + })); + + /** + * @swagger + * /analyzer/: + * get: + * summary: Analyze Item(s) using built-in OH analyzer. Requires nginx. + * tags: [Items] + * parameters: + * - in: query + * name: items + * required: true + * description: Item name + * schema: + * type: string + * style: form + * - in: query + * name: parameters + * required: false + * description: Analyzer parameters (e.g. chartType) + * schema: + * type: string + * style: form + * responses: + * 200: + * description: OK + * 404: + * description: Unknown Item or persistence service + */ + + /** + * @swagger + * /rest/persistence/items/{itemname}: + * get: + * summary: Gets item persistence data from the persistence service. Requires nginx. + * tags: [Items] + * parameters: + * - in: path + * name: itemname + * required: true + * description: Item name + * schema: + * type: string + * style: form + * - in: query + * name: parameters + * required: false + * description: Query parameters (e.g. serviceId, starttime, endtime, page, pagelength, boundary) + * schema: + * type: string + * style: form + * responses: + * 200: + * description: OK + * content: + * application/json: + * schema: + * type: object + * 404: + * description: Unknown Item or persistence service + */ + }; export default items; @@ -481,7 +611,7 @@

components/items/routes.js


- Documentation generated by JSDoc 4.0.2 on Wed Sep 20 2023 08:41:41 GMT+0000 (Coordinated Universal Time) using the docdash theme. + Documentation generated by JSDoc 4.0.2 on Wed Sep 20 2023 10:42:27 GMT+0000 (Coordinated Universal Time) using the docdash theme.
diff --git a/nodejs/jsdoc/components_items_security.js.html b/nodejs/jsdoc/components_items_security.js.html index 71da26b..088ee74 100644 --- a/nodejs/jsdoc/components_items_security.js.html +++ b/nodejs/jsdoc/components_items_security.js.html @@ -31,7 +31,7 @@ -

Home

Namespaces

+

Home

Namespaces

@@ -70,6 +70,8 @@

components/items/security.js

* @returns {Boolean} whether Item access is allowed or not */ export const itemAllowedForClient = async function (HOST, expressReq, user, org, itemname) { + if (!user) throw Error('Parameter user is required!'); + if (!org) org = []; if (typeof org === 'string') org = org.toString().split('.'); if (org.includes(ADMIN_OU)) { logger.info({ user: user, orgs: org }, `itemAllowedForClient(): Item ${itemname} allowed: true due to admin privileges`); @@ -86,7 +88,6 @@

components/items/security.js

} }; - /** * Filter Items allowed for client - used for recursive filtering of group members. * Must be used with await in async functions. @@ -100,10 +101,12 @@

components/items/security.js

* @returns {String|Array<String>} list of filtered items */ export const itemsFilterForClient = async function (HOST, expressReq, user, org, allItems) { + if (!user) throw Error('Parameter user is required!'); + if (!org) org = []; if (typeof org === 'string') org = org.toString().split('.'); if (org.includes(ADMIN_OU)) { for (const i in allItems) { - logger.info({ user: user, orgs: org }, `itemsFilterForClient(): Item ${allItems[i].name} allowed: true due to admin privileges`); + logger.info({ user: user, orgs: org }, `itemsFilterForClient(): Item ${allItems[i].name} allowed: true due to admin privileges`); } return allItems; } @@ -111,20 +114,20 @@

components/items/security.js

const userItems = await getItemsForUser(HOST, expressReq, user, org); let filteredItems = []; for (const i in allItems) { - //filter current item - const allowed = userItems.includes(allItems[i].name); + //filter current item + const allowed = userItems.includes(allItems[i].name); if (allowed === true) { - let tempItem = allItems[i]; - const tempItem2 = allItems[i].members; - //recursive filtering of member items - if (Array.isArray(tempItem2)) { - tempItem.members = []; - const tempMembers = await itemsFilterForClient(HOST, expressReq, user, org, tempItem2); - tempItem.members = tempMembers; - } - filteredItems.push(tempItem); - } - logger.info({ user: user, orgs: org }, `itemsFilterForClient(): Item ${allItems[i].name} allowed: ${allowed}`); + let tempItem = allItems[i]; + const tempItem2 = allItems[i].members; + //recursive filtering of member items + if (Array.isArray(tempItem2)) { + tempItem.members = []; + const tempMembers = await itemsFilterForClient(HOST, expressReq, user, org, tempItem2); + tempItem.members = tempMembers; + } + filteredItems.push(tempItem); + } + logger.info({ user: user, orgs: org }, `itemsFilterForClient(): Item ${allItems[i].name} allowed: ${allowed}`); } return filteredItems; } catch (err) { @@ -146,7 +149,7 @@

components/items/security.js


- Documentation generated by JSDoc 4.0.2 on Wed Sep 20 2023 08:41:41 GMT+0000 (Coordinated Universal Time) using the docdash theme. + Documentation generated by JSDoc 4.0.2 on Wed Sep 20 2023 10:42:27 GMT+0000 (Coordinated Universal Time) using the docdash theme.
diff --git a/nodejs/jsdoc/components_middleware.js.html b/nodejs/jsdoc/components_middleware.js.html index 1b073bb..c2e0839 100644 --- a/nodejs/jsdoc/components_middleware.js.html +++ b/nodejs/jsdoc/components_middleware.js.html @@ -31,7 +31,7 @@ -

Home

Namespaces

+

Home

Namespaces

@@ -115,7 +115,7 @@

components/middleware.js


- Documentation generated by JSDoc 4.0.2 on Wed Sep 20 2023 08:41:41 GMT+0000 (Coordinated Universal Time) using the docdash theme. + Documentation generated by JSDoc 4.0.2 on Wed Sep 20 2023 10:42:27 GMT+0000 (Coordinated Universal Time) using the docdash theme.
diff --git a/nodejs/jsdoc/components_pages_backend.js.html b/nodejs/jsdoc/components_pages_backend.js.html index 5752d0c..b5cfe78 100644 --- a/nodejs/jsdoc/components_pages_backend.js.html +++ b/nodejs/jsdoc/components_pages_backend.js.html @@ -31,7 +31,7 @@ -

Home

Namespaces

+

Home

Namespaces

@@ -154,27 +154,23 @@

components/pages/backend.js

const response = await fetch(HOST + '/rest/ui/components/ui%3Apage?summary=true', { headers: headers }); const allPages = await response.json(); let filteredPages = []; - if (org.includes(ADMIN_OU)) { - //Member of ADMIN_OU has full access - filteredPages = allPages; - } else { - for (const i in allPages) { - if (allPages[i].uid === 'home' || allPages[i].uid === 'overview') { - //Default OH page 'home' and 'overview' is allways allowed, otherwise MainUI does not load - filteredPages.push(allPages[i].uid); - } else { - for (const j in allPages[i].tags) { - if (allPages[i].tags[j].startsWith(ACL_PREFIX)) { - if (allPages[i].tags[j].substring(ACL_PREFIX.length) === user || - org.includes(allPages[i].tags[j].substring(ACL_PREFIX.length)) || - allPages[i].tags[j].substring(ACL_PREFIX.length) === EVERYONE_OU) { - //Access allow when tags include user name, user org or EVERYONE_OU - filteredPages.push(allPages[i].uid); - } - } - } - } - } + for (const i in allPages) { + if (allPages[i].uid === 'home' || allPages[i].uid === 'overview') { + //Default OH page 'home' and 'overview' is allways allowed, otherwise MainUI does not load + filteredPages.push(allPages[i].uid); + } else { + for (const j in allPages[i].tags) { + if (allPages[i].tags[j].startsWith(ACL_PREFIX)) { + if (allPages[i].tags[j].substring(ACL_PREFIX.length) === user || + org.includes(allPages[i].tags[j].substring(ACL_PREFIX.length)) || + org.includes(ADMIN_OU) || + allPages[i].tags[j].substring(ACL_PREFIX.length) === EVERYONE_OU) { + //Access allow when tags include user name, user org or EVERYONE_OU, Member of ADMIN_OU has full access + if (!filteredPages.includes(allPages[i].name)) filteredPages.push(allPages[i].uid); + } + } + } + } } pagesForUserDb.insert({ name: user, lastupdate: now, pages: filteredPages }); const status = response.status; @@ -185,7 +181,8 @@

components/pages/backend.js

logger.error(error); error(); } -}; +}; + @@ -199,7 +196,7 @@

components/pages/backend.js


- Documentation generated by JSDoc 4.0.2 on Wed Sep 20 2023 08:41:41 GMT+0000 (Coordinated Universal Time) using the docdash theme. + Documentation generated by JSDoc 4.0.2 on Wed Sep 20 2023 10:42:27 GMT+0000 (Coordinated Universal Time) using the docdash theme.
diff --git a/nodejs/jsdoc/components_pages_routes.js.html b/nodejs/jsdoc/components_pages_routes.js.html index 69b834f..993a705 100644 --- a/nodejs/jsdoc/components_pages_routes.js.html +++ b/nodejs/jsdoc/components_pages_routes.js.html @@ -31,7 +31,7 @@ -

Home

Namespaces

+

Home

Namespaces

@@ -70,7 +70,7 @@

components/pages/routes.js

}; /** - * Provide required /pages routes. + * Provides required /pages routes. * * @memberof routes * @param {*} app expressjs app @@ -165,14 +165,14 @@

components/pages/routes.js

const allPages = await getAllPages(backendInfo.HOST, req); let filteredPages = []; for (const i in allPages) { - if (await pageAllowedForClient(backendInfo.HOST, req, user, org, allPages[i].uid) === true) { - if (allPages[i].uid === 'home') { - const filteredHome = await pageFilterHome(backendInfo.HOST, req, user, org, allPages[i]); - filteredPages.push(filteredHome); - } else { - filteredPages.push(allPages[i]); - } - } + if (await pageAllowedForClient(backendInfo.HOST, req, user, org, allPages[i].uid) === true) { + if (allPages[i].uid === 'home') { + const filteredHome = await pageFilterHome(backendInfo.HOST, req, user, org, allPages[i]); + filteredPages.push(filteredHome); + } else { + filteredPages.push(allPages[i]); + } + } } res.status(200).send(filteredPages); } catch (e) { @@ -227,8 +227,8 @@

components/pages/routes.js

try { const response = await getPage(backendInfo.HOST, req, req.params.pageUid); if(req.params.pageUid === 'home') { - const filteredHome = await pageFilterHome(backendInfo.HOST, req, user, org, response.json); - res.status(response.status).send(filteredHome); + const filteredHome = await pageFilterHome(backendInfo.HOST, req, user, org, response.json); + res.status(response.status).send(filteredHome); } else { res.status(response.status).send(response.json); } @@ -237,6 +237,64 @@

components/pages/routes.js

res.status(500).send(); } }); + + /** + * @swagger + * /rest/ui/components/{namespace}/{componentUID}: + * get: + * summary: Gets other Main UI components. Requires nginx. + * tags: [Pages] + * parameters: + * - in: path + * name: namespace + * required: true + * description: Component namespace + * schema: + * type: string + * style: form + * - in: path + * name: componentUID + * required: false + * description: Component UID + * schema: + * type: string + * style: form + * - in: parameters + * name: parameters + * required: true + * description: Query parameters (e.g. summary) + * schema: + * type: string + * style: form + * responses: + * 200: + * description: OK + * content: + * application/json: + * schema: + * type: object + * 404: + * description: Component not found + */ + + /** + * @swagger + * /page/{pageUID}: + * get: + * summary: Gets Main UI page. Requires nginx. + * tags: [Pages] + * parameters: + * - in: path + * name: pageUID + * required: true + * description: Page UID + * schema: + * type: string + * style: form + * responses: + * 200: + * description: OK + */ }; @@ -256,7 +314,7 @@

components/pages/routes.js


- Documentation generated by JSDoc 4.0.2 on Wed Sep 20 2023 08:41:41 GMT+0000 (Coordinated Universal Time) using the docdash theme. + Documentation generated by JSDoc 4.0.2 on Wed Sep 20 2023 10:42:27 GMT+0000 (Coordinated Universal Time) using the docdash theme.
diff --git a/nodejs/jsdoc/components_pages_security.js.html b/nodejs/jsdoc/components_pages_security.js.html index 9c4b734..d4954bc 100644 --- a/nodejs/jsdoc/components_pages_security.js.html +++ b/nodejs/jsdoc/components_pages_security.js.html @@ -31,7 +31,7 @@ -

Home

Namespaces

+

Home

Namespaces

@@ -71,6 +71,8 @@

components/pages/security.js

* @returns {Boolean} whether Page access is allowed or not */ export const pageAllowedForClient = async function (HOST, expressReq, user, org, pageUid) { + if (!user) throw Error('Parameter user is required!'); + if (!org) org = []; if (typeof org === 'string') org = org.toString().split('.'); if (org.includes(ADMIN_OU)) { logger.info({ user: user, orgs: org }, `pageAllowedForClient(): Page ${pageUid} allowed: true due to admin privileges`); @@ -88,7 +90,7 @@

components/pages/security.js

}; /** - * Filter home page to include only location allowed for the client + * Filter home page to include only locations allowed for the client * Must be used with await in async functions. * * @memberof pagesSecurity @@ -100,6 +102,8 @@

components/pages/security.js

* @returns {String} filtered home page */ export const pageFilterHome = async function (HOST, expressReq, user, org, origHome) { + if (!user) throw Error('Parameter user is required!'); + if (!org) org = []; if (typeof org === 'string') org = org.toString().split('.'); if (org.includes(ADMIN_OU)) { logger.info({ user: user, orgs: org }, `pageFilterHome(): Home page allowed in full due to admin privileges`); @@ -112,33 +116,33 @@

components/pages/security.js

let filteredCards = []; let excludedCards = origHome.slots.locations[0].config.excludedCards; for (let i = 0; i < allCards.length; i++) { - if (allCards[i].hasOwnProperty('separator')) { - //separator - filteredCards.push(allCards[i]); - } else { - //filter current location item - const allowed = userItems.includes(allCards[i]); - if (allowed === true) { - filteredCards.push(allCards[i]); - } else { - excludedCards.push(allCards[i]); - } - logger.info({ user: user, orgs: org }, `pageFilterHome(): Card ${allCards[i]} allowed: ${allowed}`); - } + if (allCards[i].hasOwnProperty('separator')) { + //separator + filteredCards.push(allCards[i]); + } else { + //filter current location item + const allowed = userItems.includes(allCards[i]); + if (allowed === true) { + filteredCards.push(allCards[i]); + } else { + excludedCards.push(allCards[i]); + } + logger.info({ user: user, orgs: org }, `pageFilterHome(): Card ${allCards[i]} allowed: ${allowed}`); + } } if (HOME_SEPARATOR == 'true') { - //remove separator for empty location section - let tempCards = []; - for (let i = 0; i < filteredCards.length; i++) { - if (filteredCards[i].hasOwnProperty('separator')) { - if (i < (filteredCards.length - 1)) { - if (!(filteredCards[i+1].hasOwnProperty('separator'))) tempCards.push(filteredCards[i]); - } - } else { - tempCards.push(filteredCards[i]); - } - } - filteredCards = tempCards; + //remove separator for empty location section + let tempCards = []; + for (let i = 0; i < filteredCards.length; i++) { + if (filteredCards[i].hasOwnProperty('separator')) { + if (i < (filteredCards.length - 1)) { + if (!(filteredCards[i+1].hasOwnProperty('separator'))) tempCards.push(filteredCards[i]); + } + } else { + tempCards.push(filteredCards[i]); + } + } + filteredCards = tempCards; } filteredHome.slots.locations[0].config.cardOrder = filteredCards; filteredHome.slots.locations[0].config.excludedCards = excludedCards; @@ -162,7 +166,7 @@

components/pages/security.js


- Documentation generated by JSDoc 4.0.2 on Wed Sep 20 2023 08:41:41 GMT+0000 (Coordinated Universal Time) using the docdash theme. + Documentation generated by JSDoc 4.0.2 on Wed Sep 20 2023 10:42:27 GMT+0000 (Coordinated Universal Time) using the docdash theme.
diff --git a/nodejs/jsdoc/components_routes.js.html b/nodejs/jsdoc/components_routes.js.html index 20dd243..801e5ab 100644 --- a/nodejs/jsdoc/components_routes.js.html +++ b/nodejs/jsdoc/components_routes.js.html @@ -31,7 +31,7 @@ -

Home

Namespaces

+

Home

Namespaces

@@ -69,11 +69,6 @@

components/routes.js

export default (app) => { /** * @swagger - tags: - name: Auth - name: Items - name: Pages - name: Sitemaps * /: * get: * summary: Retrieve server information. @@ -163,7 +158,7 @@

components/routes.js


- Documentation generated by JSDoc 4.0.2 on Wed Sep 20 2023 08:41:41 GMT+0000 (Coordinated Universal Time) using the docdash theme. + Documentation generated by JSDoc 4.0.2 on Wed Sep 20 2023 10:42:27 GMT+0000 (Coordinated Universal Time) using the docdash theme.
diff --git a/nodejs/jsdoc/components_sitemaps_backend.js.html b/nodejs/jsdoc/components_sitemaps_backend.js.html index fee416d..168b600 100644 --- a/nodejs/jsdoc/components_sitemaps_backend.js.html +++ b/nodejs/jsdoc/components_sitemaps_backend.js.html @@ -31,7 +31,7 @@ -

Home

Namespaces

+

Home

Namespaces

@@ -50,8 +50,8 @@

components/sitemaps/backend.js

import logger from './../../logger.js';
 import fetch from 'node-fetch';
 import { getHeaders, findKeyInObj } from '../../utils.js';
-import { itemsOfSitemapDb, sitemapsListDb } from '../../db.js';
-import { CACHE_TIME } from '../../server.js';
+import { itemsOfSitemapDb, sitemapsListDb, sitemapsForUserDb } from '../../db.js';
+import { CACHE_TIME, CACHE_TIME_ACL, ADMIN_OU, EVERYONE_OU, ORG_SEPARATOR } from '../../server.js';
 
 /**
  * Sitemaps backend namespace. Providing access to the openHAB backend.
@@ -122,6 +122,40 @@ 

components/sitemaps/backend.js

} }; +/** + * Get a single Page of Sitemap by name and pageid. + * + * @memberof sitemapsBackend + * @param {String} HOST hostname of openHAB server + * @param {*} expressReq request object from expressjs + * @param {String} sitemapname Sitemap name + * @param {String} pageid Page id + * @returns {Object} Sitemap + */ +export const getSitemapPage = async function (HOST, expressReq, sitemapname, pageid) { + const headers = await getHeaders(expressReq); + + //process only query parameters defined in API + let query = ''; + if (expressReq.query.subscriptionid) query = 'subscriptionid=' + expressReq.query.subscriptionid; + query = (query) ? '?' + query + '&includeHidden=true' : '?includeHidden=true'; + + try { + const response = await fetch(HOST + '/rest/sitemaps/' + sitemapname + '/' + pageid + query, { headers: headers }); + const json = await response.json(); + const status = response.status; + logger.debug(`getSitemapPage(): Successfully requested backend ${HOST + '/rest/sitemaps/' + sitemapname + '/' + pageid + query}, HTTP response code ${status}'}`); + return { + json: json, + status: status + }; + } catch (err) { + const error = new Error(`getSitemapPage(): An error occurred when requesting backend ${HOST + '/rest/sitemaps/' + sitemapname + '/' + pageid + query}: ${err}`); + logger.error(error); + error(); + } +}; + /** * Get names of all Items in Sitemap. * Utilising LokiJS to cache the Items for better performance. @@ -154,6 +188,70 @@

components/sitemaps/backend.js

throw Error(err); } }; + +/** + * Gets sitemapnames's of all allowed Sitemaps for a user. + * Utilising LokiJS to cache filtered Sitemaps list for better performance. + * + * @memberof sitemapsBackend + * @param {String} HOST hostname of openHAB server + * @param {*} expressReq request object from expressjs + * @param {String} user username + * @param {String|Array<String>} org organizations the user is member of + * @returns {Array<String>} sitemapname's of sitemaps allowed for a user + */ +export const getSitemapsForUser = async function (HOST, expressReq, user, org) { + if (!user) throw Error('Parameter user is required!'); + if (!org) org = []; + if (typeof org === 'string') org = org.toString().split('.'); + + const now = Date.now(); + const storedSitemaps = sitemapsForUserDb.findOne({ name: user }); + if (storedSitemaps) { + if (now < storedSitemaps.lastupdate + CACHE_TIME_ACL) { + // Currently stored version not older than CACHE_TIME_ACL. + logger.debug('getSitemapsForUser(): Found in database and not older than CACHE_TIME_ACL.'); + return storedSitemaps.sitemaps; + } + sitemapsForUserDb.findAndRemove({ name: user }); + } + + const headers = await getHeaders(expressReq); + try { + const response = await fetch(HOST + '/rest/sitemaps', { headers: headers }); + const allSitemaps = await response.json(); + let filteredSitemaps = []; + for (const i in allSitemaps) { + // For Sitemaps created in MainUI strip "uicomponents_" from sitemapname + // If Sitemap name includes ORG_SEPARATOR, return string before ORG_SEPARATOR, else return Sitemap name. + let nameOfSitemap = ''; + let orgOfSitemap = '' + if (allSitemaps[i].name.substring(0,13) == "uicomponents_") { + nameOfSitemap = allSitemaps[i].name.substring(13); + orgOfSitemap = (nameOfSitemap.includes(ORG_SEPARATOR)) ? nameOfSitemap.split(ORG_SEPARATOR)[0] : nameOfSitemap; + } else { + nameOfSitemap = allSitemaps[i].name; + orgOfSitemap = (nameOfSitemap.includes(ORG_SEPARATOR)) ? nameOfSitemap.split(ORG_SEPARATOR)[0] : nameOfSitemap; + } + logger.trace(`getSitemapsForUser(): Organization of Sitemap ${allSitemaps[i].name} is ${orgOfSitemap}`); + if (nameOfSitemap === user || + org.includes(orgOfSitemap) || + org.includes(ADMIN_OU) || + orgOfSitemap === EVERYONE_OU) { + //Access allow when sitename is user name, user org or EVERYONE_OU, Member of ADMIN_OU has full access + if (!filteredSitemaps.includes(allSitemaps[i].name)) filteredSitemaps.push(allSitemaps[i].name); + } + } + sitemapsForUserDb.insert({ name: user, lastupdate: now, sitemaps: filteredSitemaps }); + const status = response.status; + logger.debug(`getSitemapsForUser(): Successfully requested backend ${HOST + '/rest/sitemaps'}, HTTP response code ${status}`); + return filteredSitemaps; + } catch (err) { + const error = new Error(`getSitemapsForUser(): An error occurred while getting all Sitemaps from ${HOST + '/rest/sitemaps'}: ${err}`); + logger.error(error); + error(); + } +};
@@ -168,7 +266,7 @@

components/sitemaps/backend.js


- Documentation generated by JSDoc 4.0.2 on Wed Sep 20 2023 08:41:41 GMT+0000 (Coordinated Universal Time) using the docdash theme. + Documentation generated by JSDoc 4.0.2 on Wed Sep 20 2023 10:42:27 GMT+0000 (Coordinated Universal Time) using the docdash theme.
diff --git a/nodejs/jsdoc/components_sitemaps_routes.js.html b/nodejs/jsdoc/components_sitemaps_routes.js.html index 6d180e0..7db58a6 100644 --- a/nodejs/jsdoc/components_sitemaps_routes.js.html +++ b/nodejs/jsdoc/components_sitemaps_routes.js.html @@ -31,7 +31,7 @@ -

Home

Namespaces

+

Home

Namespaces

@@ -47,10 +47,10 @@

components/sitemaps/routes.js

-
import { sitemapAllowedForClient } from './security.js';
+            
import { sitemapAllowedForClient, widgetsFilterForClient } from './security.js';
 import { requireHeader } from './../middleware.js';
 import { backendInfo } from '../../server.js';
-import { getAllSitemaps, getSitemap } from './backend.js';
+import { getAllSitemaps, getSitemap, getSitemapPage } from './backend.js';
 import proxy from 'express-http-proxy';
 
 const sitemapAccess = () => {
@@ -58,7 +58,7 @@ 

components/sitemaps/routes.js

const org = req.headers['x-openhab-org'] || ''; const user = req.headers['x-openhab-user']; try { - const allowed = await sitemapAllowedForClient(user, org, req.params.sitemapname); + const allowed = await sitemapAllowedForClient(backendInfo.HOST, req, user, org, req.params.sitemapname); if (allowed === true) { next(); } else { @@ -112,14 +112,17 @@

components/sitemaps/routes.js

* 403: * description: Forbidden */ - app.get('/auth/sitemaps', requireHeader('X-OPENHAB-USER'), requireHeader('X-ORIGINAL-URI'), (req, res, next) => { + app.get('/auth/sitemaps', requireHeader('X-OPENHAB-USER'), requireHeader('X-ORIGINAL-URI'), async (req, res) => { const org = req.headers['x-openhab-org'] || ''; const user = req.headers['x-openhab-user']; - const regex = /(\?|&)sitemap=([a-zA-Z_0-9]+)[&]?/; - const sitemapname = regex.exec(req.headers['x-original-uri']); + const regex1 = /(\?|&)sitemap=([a-zA-Z_0-9]+)[&]?/; + const regex2 = /\/sitemaps\/([a-zA-Z_0-9]+)/; + const sitemapname1 = regex1.exec(req.headers['x-original-uri']); + const sitemapname2 = regex2.exec(req.headers['x-original-uri']); + const sitemapname = (sitemapname1 == null) ? sitemapname2 : sitemapname1; if (sitemapname == null) return res.status(403).send(); try { - const allowed = sitemapAllowedForClient(user, org, sitemapname[2]); + const allowed = await sitemapAllowedForClient(backendInfo.HOST, req, user, org, sitemapname[2]); if (allowed === true) { res.status(200).send(); } else { @@ -168,9 +171,9 @@

components/sitemaps/routes.js

const allSitemaps = await getAllSitemaps(backendInfo.HOST, req); let filteredSitemaps = []; for (const i in allSitemaps) { - if (await sitemapAllowedForClient(user, org, allSitemaps[i].name) === true) { - filteredSitemaps.push(allSitemaps[i]); - } + if (await sitemapAllowedForClient(backendInfo.HOST, req, user, org, allSitemaps[i].name) === true) { + filteredSitemaps.push(allSitemaps[i]); + } } res.status(200).send(filteredSitemaps); } catch (e) { @@ -220,8 +223,17 @@

components/sitemaps/routes.js

* description: Sitemap not found */ app.get('/rest/sitemaps/:sitemapname', requireHeader('X-OPENHAB-USER'), sitemapAccess(), async (req, res) => { + const org = req.headers['x-openhab-org'] || ''; + const user = req.headers['x-openhab-user']; try { const response = await getSitemap(backendInfo.HOST, req, req.params.sitemapname); + const tempWidget = response.json.homepage.widgets; + //recursive filtering of child widgets + if (Array.isArray(tempWidget)) { + response.json.homepage.widgets = []; + const tempChWidgets = await widgetsFilterForClient(backendInfo.HOST, req, user, org, tempWidget); + response.json.homepage.widgets = tempChWidgets; + } res.status(response.status).send(response.json); } catch (e) { console.info(e); @@ -250,6 +262,13 @@

components/sitemaps/routes.js

* schema: * type: string * style: form + * - in: query + * name: parameters + * required: false + * description: Query parameters from API (subscriptionid) + * schema: + * type: string + * style: form * - in: header * name: X-OPENHAB-USER * required: true @@ -271,13 +290,34 @@

components/sitemaps/routes.js

* application/json: * schema: * type: object + * 403: + * description: Sitemap access forbidden + * 404: + * description: Sitemap not found */ - //app.get('/rest/sitemaps/:sitemapname/:pageid', requireHeader('X-OPENHAB-USER'), sitemapAccess(), proxy(backendInfo.HOST + '/rest/sitemaps/')); - + app.get('/rest/sitemaps/:sitemapname/:pageid', requireHeader('X-OPENHAB-USER'), sitemapAccess(), async (req, res) => { + const org = req.headers['x-openhab-org'] || ''; + const user = req.headers['x-openhab-user']; + try { + const response = await getSitemapPage(backendInfo.HOST, req, req.params.sitemapname, req.params.pageid); + const tempWidget = response.json.widgets; + //recursive filtering of child widgets + if (Array.isArray(tempWidget)) { + response.json.widgets = []; + const tempChWidgets = await widgetsFilterForClient(backendInfo.HOST, req, user, org, tempWidget); + response.json.widgets = tempChWidgets; + } + res.status(response.status).send(response.json); + } catch (e) { + console.info(e); + res.status(500).send(); + } + }); + /** * @swagger * /rest/sitemaps/events/{subscriptionid}: - * post: + * get: * summary: Get Sitemap events. Requires nginx. * tags: [Sitemaps] * parameters: @@ -323,6 +363,33 @@

components/sitemaps/routes.js

* 503: * description: Subscriptions limit reached. */ + + /** + * @swagger + * /basicui/: + * get: + * summary: Gets BasicUI. Requires nginx. + * tags: [Sitemaps] + * parameters: + * - in: path + * name: app + * required: true + * description: basic UI app and other components + * schema: + * type: string + * style: form + * - in: query + * name: parameters + * required: false + * description: Query parameters (e.g. sitemap, w) + * schema: + * type: string + * style: form + * responses: + * 200: + * description: OK + */ + }; export default sitemaps; @@ -340,7 +407,7 @@

components/sitemaps/routes.js


- Documentation generated by JSDoc 4.0.2 on Wed Sep 20 2023 08:41:41 GMT+0000 (Coordinated Universal Time) using the docdash theme. + Documentation generated by JSDoc 4.0.2 on Wed Sep 20 2023 10:42:27 GMT+0000 (Coordinated Universal Time) using the docdash theme.
diff --git a/nodejs/jsdoc/components_sitemaps_security.js.html b/nodejs/jsdoc/components_sitemaps_security.js.html index d52581e..a3b0c2b 100644 --- a/nodejs/jsdoc/components_sitemaps_security.js.html +++ b/nodejs/jsdoc/components_sitemaps_security.js.html @@ -31,7 +31,7 @@ -

Home

Namespaces

+

Home

Namespaces

@@ -48,10 +48,12 @@

components/sitemaps/security.js

import logger from './../../logger.js';
-import { ORG_SEPARATOR, ADMIN_OU, EVERYONE_OU } from '../../server.js';
+import { getSitemapsForUser } from './backend.js';
+import { getItemsForUser } from './../items/backend.js';
+import { ADMIN_OU, SITEMAPS_DISABLE } from '../../server.js';
 
 /**
- * Items security namespace. Provides security checks for Item access.
+ * Sitemaps security namespace. Provides security checks for Sitemaps access.
  *
  * @namespace sitemapsSecurity
  */
@@ -61,30 +63,107 @@ 

components/sitemaps/security.js

* Must be used with await in async functions. * * @memberof sitemapsSecurity + * @param {String} HOST hostname of openHAB server + * @param {*} expressReq request object from expressjs* * @param {String} user username * @param {String|Array<String>} org organizations the client is member of * @param {String} sitemapname name of Sitemap * @returns {Boolean} whether Sitemap access is allowed or not */ -export const sitemapAllowedForClient = (user, org, sitemapname) => { +export const sitemapAllowedForClient = async function (HOST, expressReq, user, org, sitemapname) { + if (!user) throw Error('Parameter user is required!'); + if (!org) org = []; if (typeof org === 'string') org = org.toString().split('.'); - // If Sitemap name includes ORG_SEPARATOR, return string before ORG_SEPARATOR, else return Sitemap name. - const orgOfSitemap = (sitemapname.includes(ORG_SEPARATOR)) ? sitemapname.split(ORG_SEPARATOR)[0] : sitemapname; - logger.trace(`sitemapAllowedForClient(): Organization of Sitemap ${sitemapname} is ${orgOfSitemap}`); - let allowed; + if (SITEMAPS_DISABLE == 'true') { + //Sitemaps disabled for all clients + logger.info({ user: user, orgs: org }, `sitemapAllowedForClient(): Sitemap ${sitemapname} allowed: false - Sitemaps disabled for all clients`); + return false; + } if (org.includes(ADMIN_OU)) { logger.info({ user: user, orgs: org }, `sitemapAllowedForClient(): Sitemap ${sitemapname} allowed: true due to admin privileges`); return true; } - if (sitemapname === user || org.includes(orgOfSitemap) || orgOfSitemap === EVERYONE_OU) { - //Access allow when sitename is user name, user org or EVERYONE_OU - allowed = true; - } else { - allowed = false; + try { + const userSitemaps = await getSitemapsForUser(HOST, expressReq, user, org); + const allowed = userSitemaps.includes(sitemapname); + logger.info({ user: user, orgs: org }, `sitemapAllowedForClient(): Sitemap ${sitemapname} allowed: ${allowed}`); + return allowed; + } catch (err) { + logger.error(err); + return false; } - logger.info({ user: user, orgs: org }, `sitemapAllowedForClient(): Sitemap ${sitemapname} allowed: ${allowed}`); - return allowed; }; + +/** + * Filter Items in widgets allowed for client - used for recursive filtering of Sitemap widgets. + * Must be used with await in async functions. + * + * @memberof sitemapsSecurity + * @param {String} HOST hostname of openHAB server + * @param {*} expressReq request object from expressjs + * @param {String} user username + * @param {String|Array<String>} org organizations the client is member of + * @param {String|Array<String>} list of widgets to filter + * @returns {String|Array<String>} list of filtered widgets + */ +export const widgetsFilterForClient = async function (HOST, expressReq, user, org, allWidgets) { + if (!user) throw Error('Parameter user is required!'); + if (!org) org = []; + if (typeof org === 'string') org = org.toString().split('.'); + if (org.includes(ADMIN_OU)) { + for (const i in allWidgets) { + if (allWidgets[i].hasOwnProperty('item')) logger.info({ user: user, orgs: org }, `widgetsFilterForClient(): Widget ${allWidgets[i].label} with Item ${allWidgets[i].item.name} allowed: true due to admin privileges`); + } + return allWidgets; + } + try { + const userItems = await getItemsForUser(HOST, expressReq, user, org); + let filteredWidgets = []; + for (const i in allWidgets) { + //filter current widget + let tempWidget = allWidgets[i]; + if (tempWidget.hasOwnProperty('item')) { + const tempItem = tempWidget.item; + const allowed = userItems.includes(tempItem.name); + if (allowed === true) { + //recursive filtering of child widgets + const tempWidget2 = tempWidget.widgets; + if (Array.isArray(tempWidget2)) { + tempWidget.widgets = []; + const tempChWidgets = await widgetsFilterForClient(HOST, expressReq, user, org, tempWidget2); + tempWidget.widgets = tempChWidgets; + } + filteredWidgets.push(tempWidget); + } + logger.info({ user: user, orgs: org }, `widgetsFilterForClient(): Item ${tempItem.name} allowed: ${allowed}`); + } else if (tempWidget.hasOwnProperty('linkedPage')) { + //recursive filtering of linkedPage and its child widgets + const tempLinkedPage = tempWidget.linkedPage; + const tempWidget2 = tempLinkedPage.widgets; + if (Array.isArray(tempWidget2)) { + tempWidget.linkedPage.widgets = []; + const tempChWidgets = await widgetsFilterForClient(HOST, expressReq, user, org, tempWidget2); + tempWidget.linkedPage.widgets = tempChWidgets; + } + filteredWidgets.push(tempWidget); + } else { + //recursive filtering of child widgets + const tempWidget2 = tempWidget.widgets; + if (Array.isArray(tempWidget2)) { + tempWidget.widgets = []; + const tempChWidgets = await widgetsFilterForClient(HOST, expressReq, user, org, tempWidget2); + tempWidget.widgets = tempChWidgets; + } + filteredWidgets.push(tempWidget); + } + } + return filteredWidgets; + } catch (err) { + logger.error(err); + return []; + } +}; +
@@ -99,7 +178,7 @@

components/sitemaps/security.js


- Documentation generated by JSDoc 4.0.2 on Wed Sep 20 2023 08:41:41 GMT+0000 (Coordinated Universal Time) using the docdash theme. + Documentation generated by JSDoc 4.0.2 on Wed Sep 20 2023 10:42:27 GMT+0000 (Coordinated Universal Time) using the docdash theme.
diff --git a/nodejs/jsdoc/db.js.html b/nodejs/jsdoc/db.js.html index 2db630e..5c2ee72 100644 --- a/nodejs/jsdoc/db.js.html +++ b/nodejs/jsdoc/db.js.html @@ -31,7 +31,7 @@ -

Home

Namespaces

+

Home

Namespaces

@@ -107,6 +107,18 @@

db.js

autosave: false, autosaveInterval: 10000 }); +/** + * LokiJS database that holds the Sitemaps allowed for a user. + * + * @memberof lokijs + */ +export const sitemapsForUserDb = db.addCollection('sitemapsForUser', { + exact: ['name', 'lastupdate', 'sitemaps'], + indices: ['name'], + autoload: false, + autosave: false, + autosaveInterval: 10000 +}); /** * LokiJS database that holds the Pages allowed for a user. * @@ -145,7 +157,7 @@

db.js


- Documentation generated by JSDoc 4.0.2 on Wed Sep 20 2023 08:41:41 GMT+0000 (Coordinated Universal Time) using the docdash theme. + Documentation generated by JSDoc 4.0.2 on Wed Sep 20 2023 10:42:27 GMT+0000 (Coordinated Universal Time) using the docdash theme.
diff --git a/nodejs/jsdoc/global.html b/nodejs/jsdoc/global.html index 0aaa6ce..86f32b1 100644 --- a/nodejs/jsdoc/global.html +++ b/nodejs/jsdoc/global.html @@ -31,7 +31,7 @@ -

Home

Namespaces

+

Home

Namespaces

@@ -350,7 +350,7 @@

Source:
@@ -419,7 +419,7 @@

Source:
@@ -472,6 +472,144 @@

(constant) ORG_SEPARATOR

+ + + + + +
+ +
Description:
+
  • Separates the organization name at beginning of Sitemap name from the full name. +Defaults to org

+ + + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Separates the organization name at beginning of Sitemap name from the full name. +Defaults to org

+
+ + + + + + + + + + +

(constant) SITEMAPS_DISABLE

+ + + + + +
+ +
Description:
+
  • Disable/filter all Sitemaps for all clients. Usefull in case only MainUI is used. +Defaults to true

+ + + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Disable/filter all Sitemaps for all clients. Usefull in case only MainUI is used. +Defaults to true

+
+ + + + + + + + + +

(constant) HOME_SEPARATOR

@@ -561,7 +699,7 @@

- Documentation generated by JSDoc 4.0.2 on Wed Sep 20 2023 08:41:41 GMT+0000 (Coordinated Universal Time) using the docdash theme. + Documentation generated by JSDoc 4.0.2 on Wed Sep 20 2023 10:42:27 GMT+0000 (Coordinated Universal Time) using the docdash theme.
diff --git a/nodejs/jsdoc/index.html b/nodejs/jsdoc/index.html index 0c77d3c..c952b1d 100644 --- a/nodejs/jsdoc/index.html +++ b/nodejs/jsdoc/index.html @@ -31,7 +31,7 @@ -

Home

Namespaces

+

Home

Namespaces

@@ -46,7 +46,7 @@

Home

Namespaces

@@ -62,14 +62,14 @@

openhab-multiuser-proxy 2.0.1

-

openHAB Multi-User support for the REST API v 2.0.1

-

NOTE: This is fork of the archived openhab-multiuser-proxy project and it is utilizing great work done by Florian Hotze. Version 2 of openHAB MultiUser Proxy is expanded and adjusted to fully support MainUI of openHAB 3+ including filtering of Items and Pages.

+

openHAB Multi-User support for the REST API v 2.0.2

+

NOTE: This is fork of the archived openhab-multiuser-proxy project and it is utilizing great work done by Florian Hotze. Version 2 of openHAB MultiUser Proxy is expanded and adjusted to fully support MainUI of openHAB 3 and 4 including filtering of Items and Pages.

This project aims to provide a secure multiuser support for the openHAB REST API. It is utilising a NodeJS application and the popular NGINX webserver to proxy and filter requests to the REST API.

Opposite to the visibleTo property available in openHAB, filtering provided by the openHAB MultiUser Proxy is enabling true authorized access to Items/Pages/Sitemaps including access via REST API.

js-semistandard-style npm version

-

DISCLAIMER: I DO NOT GUARANTEE that this project has no vulnerabilities a attacker could use. +

DISCLAIMER: I DO NOT GUARANTEE that this project has no vulnerabilities an attacker could use. Use it at your own responsibility. As GPL-3.0 says, this project comes without any liability or warranty.

Table of Contents

Introduction

-

This project allows to set granular access control to Items, Pages and Sitemaps provided by openHAB 3+. It allows to distinguish between individual users and provide them access only to approved Items/Pages/Sitemaps.

+

This project allows to set granular access control to Items, Pages and Sitemaps provided by openHAB 3 and 4. It allows to distinguish between individual users and provide them access only to approved Items/Pages/Sitemaps.

Authentication of users is not utilizing internal authentication in openHAB. It relies on mTLS (client certificate auth) to get user id and org memberships used for the access authorization. For openHAB application all users are utilizing the implicit user role (need to be turned ON in API security setting of openHAB). OpenHAB authentication to access openHAB setting is possible only via the dedicated admin server instance.

Each client has its own username (as Common Name) and can be in multiple organizations (as dot seperated list in Organizational Unit), for certificate config refer to mTLS Certificate Authority.

ACL Tags

Version 2 of openHAB MultiUser Proxy is utilizing openHAB tagging to set access of a client to Pages and Items.

ACL Tag added to a Page or Item always consists of ACL_PREFIX (default acl:) followed user id or org that this ACL Tag grants access to. Example: acl:john, acl:guests.

+

sample_tag

If no ACL Tag is added to a Page or an Item, than only clients that are members of ADMIN_OU do have access to it.

There are two special orgs defined with pre-defined meaning:

    @@ -106,7 +107,9 @@

    ACL Tags

ADMIN_OU and EVERYONE_OU is defined in configuration options.

Tags can be added to Items by several means, e.g. in Items file, using MainUI, via rules and via Rest API. -Tags to Pages can be currently added only via Rest API.

+Tags to Pages can be currently added only via Rest API. However, there is PR submitted to enable adding such Tags to Pages via MainUI.

+

sample_edit +sample_api

Access to MainUI pages

A client can access MainUI page if at least one of the following conditions is fulfilled:

    @@ -116,9 +119,15 @@

    Access to MainUI pages

  • Client is member of ADMIN_OU org;
  • Page requested is home or overview that is necessary for load of MainUI.
-

Page home, that is displaying generated model tabs with Locations, Equipment and Properties, is filtered so that the client is provided only with authorized Items (including Location Items). It can also filter separators of empty sections in Locations tab.

-

Access to MainUI pages is filtered both when accessed directly via /page/{componentUID} as well as when requested via REST API. If the Page is displayed at MainUI Sidebar, it is filtered out of the menu if access to the Page is not authorized.

-

Items displayed on pages are filtered only in case list of displayed Items is generated via REST API. Hardcoded Items on authorized Page are always displayed, however, their state as well access to command is filtered.

+

Page home, that is displaying generated model tabs with Locations, Equipment and Properties, is filtered so that the client is provided only with authorized Items (including Location Items). It can also filter separators of empty sections in Locations tab.

+

Access to MainUI pages is filtered both when they are accessed directly via /page/{componentUID} as well as when requested via REST API. If the Page is displayed at MainUI Sidebar, it is filtered out of the menu if access to the Page is not authorized.

+

Items displayed on pages are filtered only in case list of displayed Items is generated via REST API (e.g. automatically generated Home site based on semantic model). Hardcoded Items on authorized Page are always displayed, however, their state as well access to commands is filtered. Only authorized Items are requested in the SSE event listeners connection. Commands are sent only to authorized Items.

+

Only the following Page operations are allowed:

+
    +
  • Get all MainUI Pages;
  • +
  • Get a single MainUI Page;
  • +
  • Display Page in Main UI.
  • +

Access to Items

A client can access Item if at least one of the following conditions is fulfilled:

    @@ -132,13 +141,15 @@

    Access to Items

  • Get all available Items;
  • Get a single Item;
  • Get the state of an Item;
  • -
  • Get the item which defines the requested semantics of an Item;
  • +
  • Get the Item which defines the requested semantics of an Item;
  • +
  • Get item persistence data from the persistence service;
  • +
  • Analyze an Item;
  • Send a command to an Item;
  • Initiate and change Item state tracker connection.

Access to Sitemaps

-

NOT MAINTAINED: This is legacy Basic UI Sitemaps functionality and it is no longer maintained in version 2 of openHAB MultiUser Proxy. -Functionality is provided and shall be working, however by default these routes are turned off in nginx.

+

NOT ACTIVELY MAINTAINED: This is legacy Basic UI Sitemaps functionality and it is no longer maintained in version 2 of openHAB MultiUser Proxy. +Functionality is provided and shall be working, however by default these routes are turned off in nginx and NodeJS.

A client can access a Sitemap if at least one of the following conditions is fulfilled:

  • Sitemap name exactly matches with the client's user id;
  • @@ -152,7 +163,18 @@

    Access to Sitemaps

  • a Sitemap named family or administration,
  • every Sitemap whose name starts with familiy_org_ or administration_org_.
-

WARNING: Opposite to Pages, Items on a Sitemap are not filtered. If Item is included in approved Sitemap, its state will be shown even if the client is not having authorized access to this Item.

+

Sitemap provided via REST API is filtered and only widgets with authorized Items are provided. If item is not authorized, entire widget is filtered.

+

POTENTIAL DATA LEAK: Filtering of Sitemap is not working reliably when using Basic UI app. Item and its state is displayed even if it is not authorized. However, commands are sent only to authorized Items.

+

POTENTIAL DATA LEAK: Events are sent for all Sitemap Items regardless of Item authorization. Therefore, events are sent to Sitemap SSE listener even for not authorized Sitemap Items.

+

Only the following Sitemap operations are allowed:

+
    +
  • Get all available Sitemaps;
  • +
  • Get a single Sitemap;
  • +
  • Polls the data for a sitemap;
  • +
  • Chart an Item;
  • +
  • Initiate and get Sitemap Items state tracker connection;
  • +
  • Display Sitemap in Basic UI App.
  • +

Admin user

The admin user, identified by $ADMIN_OU in his OU, can access all Items/Pages/Sitemaps.

Furthermore, administrators have unfiltered access to the openHAB server at https://admin.$servername.

@@ -245,8 +267,8 @@

Configuration options

CACHE_TIME_ACL Time (in milliseconds) for caching of ACL for Items/Pages/Sitemaps. none -CACHE_TIME=3600000 -CACHE_TIME +CACHE_TIME_ACL +CACHE_TIME_ACL=3600000 3600000 = 60 min @@ -266,6 +288,14 @@

Configuration options

_org_ +SITEMAP_DISABLE +Disable/filter all Sitemaps for all clients. Usefull in case only MainUI is used. +none +SITEMAP_DISABLE +SITEMAP_DISABLE=true +true + + HOME_SEPARATOR Remove separators of empty section in filtered home page. none @@ -302,7 +332,7 @@

Firewall setup


- Documentation generated by JSDoc 4.0.2 on Wed Sep 20 2023 08:41:41 GMT+0000 (Coordinated Universal Time) using the docdash theme. + Documentation generated by JSDoc 4.0.2 on Wed Sep 20 2023 10:42:27 GMT+0000 (Coordinated Universal Time) using the docdash theme.
diff --git a/nodejs/jsdoc/itemsBackend.html b/nodejs/jsdoc/itemsBackend.html index 9f21ae8..a6aad51 100644 --- a/nodejs/jsdoc/itemsBackend.html +++ b/nodejs/jsdoc/itemsBackend.html @@ -31,7 +31,7 @@ -

Home

Namespaces

+

Home

Namespaces

@@ -388,7 +388,7 @@

Source:
@@ -455,7 +455,7 @@

Source:
@@ -522,7 +522,7 @@

Source:
@@ -589,7 +589,7 @@

Source:
@@ -661,7 +661,7 @@

- Documentation generated by JSDoc 4.0.2 on Wed Sep 20 2023 08:41:41 GMT+0000 (Coordinated Universal Time) using the docdash theme. + Documentation generated by JSDoc 4.0.2 on Wed Sep 20 2023 10:42:27 GMT+0000 (Coordinated Universal Time) using the docdash theme.
diff --git a/nodejs/jsdoc/itemsSecurity.html b/nodejs/jsdoc/itemsSecurity.html index c9c929d..563d33a 100644 --- a/nodejs/jsdoc/itemsSecurity.html +++ b/nodejs/jsdoc/itemsSecurity.html @@ -31,7 +31,7 @@ -

Home

Namespaces

+

Home

Namespaces

@@ -253,7 +253,7 @@

Source:
@@ -326,7 +326,7 @@

- Documentation generated by JSDoc 4.0.2 on Wed Sep 20 2023 08:41:41 GMT+0000 (Coordinated Universal Time) using the docdash theme. + Documentation generated by JSDoc 4.0.2 on Wed Sep 20 2023 10:42:27 GMT+0000 (Coordinated Universal Time) using the docdash theme.
diff --git a/nodejs/jsdoc/lokijs.html b/nodejs/jsdoc/lokijs.html index ecdf07d..0d67b3b 100644 --- a/nodejs/jsdoc/lokijs.html +++ b/nodejs/jsdoc/lokijs.html @@ -31,7 +31,7 @@ -

Home

Namespaces

+

Home

Namespaces

@@ -442,6 +442,73 @@

(static, constant) exports.sitemapsForUserDb

+ + + + + +
+ +
Description:
+
  • LokiJS database that holds the Sitemaps allowed for a user.

+ + + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

LokiJS database that holds the Sitemaps allowed for a user.

+
+ + + + + + + + + +

(static, constant) exports.pagesForUserDb

@@ -457,7 +524,7 @@

Source:
@@ -524,7 +591,7 @@

Source:
@@ -596,7 +663,7 @@

- Documentation generated by JSDoc 4.0.2 on Wed Sep 20 2023 08:41:41 GMT+0000 (Coordinated Universal Time) using the docdash theme. + Documentation generated by JSDoc 4.0.2 on Wed Sep 20 2023 10:42:27 GMT+0000 (Coordinated Universal Time) using the docdash theme.
diff --git a/nodejs/jsdoc/middlewares.html b/nodejs/jsdoc/middlewares.html index e50f233..80177cb 100644 --- a/nodejs/jsdoc/middlewares.html +++ b/nodejs/jsdoc/middlewares.html @@ -31,7 +31,7 @@ -

Home

Namespaces

+

Home

Namespaces

@@ -391,7 +391,7 @@

- Documentation generated by JSDoc 4.0.2 on Wed Sep 20 2023 08:41:41 GMT+0000 (Coordinated Universal Time) using the docdash theme. + Documentation generated by JSDoc 4.0.2 on Wed Sep 20 2023 10:42:27 GMT+0000 (Coordinated Universal Time) using the docdash theme.
diff --git a/nodejs/jsdoc/pagesBackend.html b/nodejs/jsdoc/pagesBackend.html index 7d22239..5be719a 100644 --- a/nodejs/jsdoc/pagesBackend.html +++ b/nodejs/jsdoc/pagesBackend.html @@ -31,7 +31,7 @@ -

Home

Namespaces

+

Home

Namespaces

@@ -393,7 +393,7 @@

- Documentation generated by JSDoc 4.0.2 on Wed Sep 20 2023 08:41:41 GMT+0000 (Coordinated Universal Time) using the docdash theme. + Documentation generated by JSDoc 4.0.2 on Wed Sep 20 2023 10:42:27 GMT+0000 (Coordinated Universal Time) using the docdash theme.
diff --git a/nodejs/jsdoc/pagesSecurity.html b/nodejs/jsdoc/pagesSecurity.html index 4d328d9..7f46bc3 100644 --- a/nodejs/jsdoc/pagesSecurity.html +++ b/nodejs/jsdoc/pagesSecurity.html @@ -31,7 +31,7 @@ -

Home

Namespaces

+

Home

Namespaces

@@ -246,14 +246,14 @@

Description:
-