From 14e1ee5391728a27d3db8f50f7633e7acdd9336e Mon Sep 17 00:00:00 2001 From: Florent Chehab Date: Tue, 12 May 2020 21:32:46 +0200 Subject: [PATCH] feat(backend): main handling of readonly sharing * Saving works as before * Don't broadcast drawevents from readonly whiteboard (prevents malicious use) --- scripts/server-backend.js | 36 ++++++++--- scripts/services/ReadOnlyBackendService.js | 71 ++++++++++++++++++++++ 2 files changed, 100 insertions(+), 7 deletions(-) create mode 100644 scripts/services/ReadOnlyBackendService.js diff --git a/scripts/server-backend.js b/scripts/server-backend.js index 66ae122..2011c83 100644 --- a/scripts/server-backend.js +++ b/scripts/server-backend.js @@ -2,6 +2,7 @@ const path = require("path"); const config = require("./config/config"); const WhiteboardServerSideInfo = require("./WhiteboardServerSideInfo"); +const ReadOnlyBackendService = require("./services/ReadOnlyBackendService"); function startBackendServer(port) { var fs = require("fs-extra"); @@ -28,10 +29,13 @@ function startBackendServer(port) { const { accessToken, enableWebdav } = config.backend; app.get("/api/loadwhiteboard", function (req, res) { - var wid = req["query"]["wid"]; - var at = req["query"]["at"]; //accesstoken + const wid = req["query"]["wid"]; + const at = req["query"]["at"]; //accesstoken if (accessToken === "" || accessToken == at) { - var ret = s_whiteboard.loadStoredData(wid); + const widForData = ReadOnlyBackendService.isReadOnly(wid) + ? ReadOnlyBackendService.getIdFromReadOnlyId(wid) + : wid; + const ret = s_whiteboard.loadStoredData(widForData); res.send(ret); res.end(); } else { @@ -192,7 +196,7 @@ function startBackendServer(port) { }, (1 / config.backend.performance.whiteboardInfoBroadcastFreq) * 1000); io.on("connection", function (socket) { - var whiteboardId = null; + let whiteboardId = null; socket.on("disconnect", function () { if (infoByWhiteboard.has(whiteboardId)) { const whiteboardServerSideInfo = infoByWhiteboard.get(whiteboardId); @@ -212,9 +216,20 @@ function startBackendServer(port) { }); socket.on("drawToWhiteboard", function (content) { + if (!whiteboardId || ReadOnlyBackendService.isReadOnly(whiteboardId)) return; + content = escapeAllContentStrings(content); if (accessToken === "" || accessToken == content["at"]) { - socket.compress(false).broadcast.to(whiteboardId).emit("drawToWhiteboard", content); //Send to all users in the room (not own socket) + const broadcastTo = (wid) => + socket.compress(false).broadcast.to(wid).emit("drawToWhiteboard", content); + // broadcast to current whiteboard + broadcastTo(whiteboardId); + const readOnlyId = ReadOnlyBackendService.getReadOnlyId(whiteboardId); + const readOnlyWhiteboardInfo = infoByWhiteboard.get(readOnlyId); + if (readOnlyWhiteboardInfo && readOnlyWhiteboardInfo.hasConnectedUser()) { + // broadcast the same content to the associated read-only whiteboard + broadcastTo(readOnlyId); + } s_whiteboard.handleEventsAndData(content); //save whiteboardchanges on the server } else { socket.emit("wrongAccessToken", true); @@ -224,9 +239,16 @@ function startBackendServer(port) { socket.on("joinWhiteboard", function (content) { content = escapeAllContentStrings(content); if (accessToken === "" || accessToken == content["at"]) { - socket.emit("whiteboardConfig", { common: config.frontend }); - whiteboardId = content["wid"]; + + socket.emit("whiteboardConfig", { + common: config.frontend, + whiteboardSpecific: { + correspondingReadOnlyId: ReadOnlyBackendService.getReadOnlyId(whiteboardId), + isReadOnly: ReadOnlyBackendService.isReadOnly(whiteboardId), + }, + }); + socket.join(whiteboardId); //Joins room name=wid if (!infoByWhiteboard.has(whiteboardId)) { infoByWhiteboard.set(whiteboardId, new WhiteboardServerSideInfo()); diff --git a/scripts/services/ReadOnlyBackendService.js b/scripts/services/ReadOnlyBackendService.js new file mode 100644 index 0000000..f501887 --- /dev/null +++ b/scripts/services/ReadOnlyBackendService.js @@ -0,0 +1,71 @@ +const { v4: uuidv4 } = require("uuid"); + +class ReadOnlyBackendService { + /** + * Mapping from an editable whiteboard id to the matching read-only whiteboard id + * @type {Map} + * @private + */ + #idToReadOnlyId = new Map(); + + /** + * Mapping from a read-only whiteboard id to the matching editable whiteboard id + * + * @type {Map} + * @private + */ + #readOnlyIdToId = new Map(); + + /** + * Make sure a whiteboardId is ignited in the service + * + * If it's not found in the service, we assume that it's an editable whiteboard + * + * @param {string} whiteboardId + */ + init(whiteboardId) { + const idToReadOnlyId = this.#idToReadOnlyId; + const readOnlyIdToId = this.#readOnlyIdToId; + + if (!idToReadOnlyId.has(whiteboardId) && !readOnlyIdToId.has(whiteboardId)) { + const readOnlyId = uuidv4(); + idToReadOnlyId.set(whiteboardId, readOnlyId); + readOnlyIdToId.set(readOnlyId, whiteboardId); + } + } + + /** + * Get the read-only id corresponding to a whiteboard id + * + * @param {string} whiteboardId + * @return {string} + */ + getReadOnlyId(whiteboardId) { + // make sure it's inited + this.init(whiteboardId); + return this.#idToReadOnlyId.get(whiteboardId); + } + + /** + * Get the id corresponding to readonly id + * + * @param {string} readOnlyId + * @return {string} + */ + getIdFromReadOnlyId(readOnlyId) { + return this.#readOnlyIdToId.get(readOnlyId); + } + + /** + * Tell is whiteboard id corresponds to a read-only whiteboard + * + * @param whiteboardId + * @return {boolean} + */ + isReadOnly(whiteboardId) { + this.init(whiteboardId); + return this.#readOnlyIdToId.has(whiteboardId); + } +} + +module.exports = new ReadOnlyBackendService();