import { itemAllowedForClient, itemsFilterForClient } from './security.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) {
const org = req.headers['x-openhab-org'] || '';
const user = req.headers['x-openhab-user'];
try {
const allowed = await itemAllowedForClient(backendInfo.HOST, req, user, org, req.params.itemname);
if (allowed === true) {
next();
} else {
res.status(403).send();
}
} catch {
res.status(500).send();
}
};
};
/**
* Provides required /items routes.
*
* @memberof routes
* @param {*} app expressjs app
*/
const items = (app) => {
/**
* @swagger
* /auth/items:
* get:
* summary: Authorization endpoint for Item access.
* description: Used by NGINX auth_request.
* tags: [Auth]
* parameters:
* - 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
* - in: header
* name: X-ORIGINAL-URI
* required: true
* description: Original request URI
* schema:
* type: string
* style: form
* responses:
* 200:
* description: Allowed
* 403:
* description: Forbidden
*/
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 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]);
if (allowed === true) {
res.status(200).send();
} else {
res.status(403).send();
}
} catch {
res.status(500).send();
}
});
/**
* @swagger
* /rest/items:
* get:
* summary: Get all available Items.
* tags: [Items]
* parameters:
* - in: query
* name: parameters
* required: false
* description: Query parameters from API (metadata, recursive, type, tags, fields)
* 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
* content:
* application/json:
* schema:
* type: object
*/
app.get('/rest/items', requireHeader('X-OPENHAB-USER'), async (req, res) => {
const org = req.headers['x-openhab-org'] || '';
const user = req.headers['x-openhab-user'];
try {
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);
}
}
res.status(200).send(filteredItems);
} catch (e) {
console.info(e);
res.status(500).send();
}
});
/**
* @swagger
* /rest/items/{itemname}:
* get:
* summary: Gets a single Item.
* 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 from API (metadata, recursive)
* 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
* content:
* application/json:
* schema:
* type: object
* 403:
* description: Item access forbidden
* 404:
* description: Item not found
*/
app.get('/rest/items/:itemname', requireHeader('X-OPENHAB-USER'), itemAccess(), async (req, res) => {
const org = req.headers['x-openhab-org'] || '';
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;
}
res.status(response.status).send(response.json);
} catch (e) {
console.info(e);
res.status(500).send();
}
});
/**
* @swagger
* /rest/items/{itemname}/state:
* get:
* summary: Gets the state of an Item.
* tags: [Items]
* parameters:
* - in: path
* name: itemname
* required: true
* description: Item name
* 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
* content:
* text/plain:
* schema:
* type: string
* 403:
* description: Item access forbidden
* 404:
* description: Item not found
*/
app.get('/rest/items/:itemname/state', requireHeader('X-OPENHAB-USER'), itemAccess(), async (req, res) => {
const response = await getItemState(backendInfo.HOST, req, req.params.itemname);
res.status(response.status).send(response.state);
});
/**
* @swagger
* /rest/items/{itemname}/semantic/{semanticClass}:
* get:
* summary: Gets the item which defines the requested semantics of an Item.
* tags: [Items]
* parameters:
* - in: path
* name: itemname
* required: true
* description: Item name
* schema:
* type: string
* style: form
* - in: path
* name: semanticClass
* required: true
* description: Semantic class
* 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
* content:
* application/json:
* schema:
* type: object
* 403:
* description: Item access forbidden
* 404:
* description: Item not found
*/
app.get('/rest/items/:itemname/semantic/:semanticClass', requireHeader('X-OPENHAB-USER'), itemAccess(), async (req, res) => {
const response = await getItemSemantic(backendInfo.HOST, req, req.params.itemname, req.params.semanticClass);
res.status(response.status).send(response.json);
});
/**
* @swagger
* /rest/items/{itemname}:
* post:
* summary: Sends a command to an Item.
* tags: [Items]
* parameters:
* - in: path
* name: itemname
* required: true
* description: Item name
* 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
* requestBody:
* description: valid item command (e.g. ON, OFF, UP, DOWN, REFRESH)
* required: true
* content:
* text/plain:
* schema:
* type: string
* responses:
* 200:
* description: OK
* 400:
* description: Item command null
* 403:
* description: Item access forbidden
* 404:
* description: Item not found
*/
app.post('/rest/items/:itemname', requireHeader('X-OPENHAB-USER'), itemAccess(), async (req, res) => {
const status = await sendItemCommand(backendInfo.HOST, req, req.params.itemname, req.body);
res.status(status).send();
});
/**
* @swagger
* /rest/events/states/{connectionId}:
* post:
* summary: Changes list of items a SSE connection will receive state updates to.
* tags: [Items]
* parameters:
* - in: path
* name: connectionId
* required: true
* description: Connection ID
* 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
* requestBody:
* description: items list
* required: true
* content:
* text/plain:
* schema:
* type: string
* responses:
* 200:
* description: OK
* 404:
* description: Items not found / list is empty
*/
app.post('/rest/events/states/:connectionId', requireHeader('X-OPENHAB-USER'), async (req, res) => {
const org = req.headers['x-openhab-org'] || '';
const user = req.headers['x-openhab-user'];
const allItems = req.body;
try {
let filteredItems = [];
for (const i in allItems) {
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();
} catch (e) {
console.info(e);
res.status(500).send();
}
});
/**
* @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;