diff --git a/bigbluebutton-html5/imports/api/captions/server/helpers.js b/bigbluebutton-html5/imports/api/captions/server/helpers.js index 549ea8b5d770d5cebc9385269b32fd496b82b63c..e144c6b1eb33595b96456abd253ce543947c4333 100644 --- a/bigbluebutton-html5/imports/api/captions/server/helpers.js +++ b/bigbluebutton-html5/imports/api/captions/server/helpers.js @@ -1,22 +1,20 @@ import { Meteor } from 'meteor/meteor'; -import { hashFNV32a } from '/imports/api/common/server/helpers'; +import { hashSHA1 } from '/imports/api/common/server/helpers'; import { check } from 'meteor/check'; +const ETHERPAD = Meteor.settings.private.etherpad; const CAPTIONS_CONFIG = Meteor.settings.public.captions; const BASENAME = Meteor.settings.public.app.basename; const APP = Meteor.settings.private.app; const LOCALES_URL = `http://${APP.host}:${APP.port}${BASENAME}${APP.localesUrl}`; -const CAPTIONS = '_captions_'; +const CAPTIONS_TOKEN = '_cc_'; const TOKEN = '$'; -// Captions padId should look like: {padId}_captions_{locale} -const generatePadId = (meetingId, locale) => { - const padId = `${hashFNV32a(meetingId, true)}${CAPTIONS}${locale}`; - return padId; -}; +// Captions padId should look like: {prefix}_cc_{locale} +const generatePadId = (meetingId, locale) => `${hashSHA1(meetingId+locale+ETHERPAD.apikey)}${CAPTIONS_TOKEN}${locale}`; const isCaptionsPad = (padId) => { - const splitPadId = padId.split(CAPTIONS); + const splitPadId = padId.split(CAPTIONS_TOKEN); return splitPadId.length === 2; }; @@ -45,6 +43,7 @@ const processForCaptionsPadOnly = fn => (message, ...args) => { }; export { + CAPTIONS_TOKEN, generatePadId, processForCaptionsPadOnly, isEnabled, diff --git a/bigbluebutton-html5/imports/api/captions/server/methods/appendText.js b/bigbluebutton-html5/imports/api/captions/server/methods/appendText.js index 672c47787bef508a013e67777283e9bd6e367c42..f15099c072309e4e3d882649a3e0cfbd4aa1473d 100644 --- a/bigbluebutton-html5/imports/api/captions/server/methods/appendText.js +++ b/bigbluebutton-html5/imports/api/captions/server/methods/appendText.js @@ -23,8 +23,9 @@ export default function appendText(text, locale) { responseType: 'json', }).then((response) => { const { status } = response; - if (status === 200) { - Logger.verbose('Captions: appended text', { padId }); + if (status !== 200) { + Logger.error(`Could not append captions for padId=${padId}`); + return; } }).catch(error => Logger.error(`Could not append captions for padId=${padId}: ${error}`)); } diff --git a/bigbluebutton-html5/imports/api/captions/server/methods/createCaptions.js b/bigbluebutton-html5/imports/api/captions/server/methods/createCaptions.js index 888f340bca5f2bf55b7ea176d28cca9739a0dac2..24215b91c52876b47814e62b33fd52687df92340 100644 --- a/bigbluebutton-html5/imports/api/captions/server/methods/createCaptions.js +++ b/bigbluebutton-html5/imports/api/captions/server/methods/createCaptions.js @@ -25,6 +25,7 @@ export default function createCaptions(meetingId) { const { status } = response; if (status !== 200) { Logger.error(`Could not get locales info for ${meetingId} ${status}`); + return; } const locales = response.data; locales.forEach((locale) => { diff --git a/bigbluebutton-html5/imports/api/captions/server/methods/editCaptions.js b/bigbluebutton-html5/imports/api/captions/server/methods/editCaptions.js index 9cd569810209f992f924385c630d53327d62917c..f613b0827047b256848457869e579b835990d495 100644 --- a/bigbluebutton-html5/imports/api/captions/server/methods/editCaptions.js +++ b/bigbluebutton-html5/imports/api/captions/server/methods/editCaptions.js @@ -21,7 +21,6 @@ export default function editCaptions(padId, data) { return; } - const { meetingId, ownerId, diff --git a/bigbluebutton-html5/imports/api/captions/server/methods/fetchReadOnlyPadId.js b/bigbluebutton-html5/imports/api/captions/server/methods/fetchReadOnlyPadId.js index fc34f216f59ae08b617681d95db4c52016f49b12..fb487fa0a8ac517a2f1a07acb421a8b0f03947e0 100644 --- a/bigbluebutton-html5/imports/api/captions/server/methods/fetchReadOnlyPadId.js +++ b/bigbluebutton-html5/imports/api/captions/server/methods/fetchReadOnlyPadId.js @@ -11,11 +11,17 @@ export default function fetchReadOnlyPadId(padId) { check(padId, String); const readOnlyURL = getReadOnlyIdURL(padId); + axios({ method: 'get', url: readOnlyURL, responseType: 'json', }).then((response) => { + const { status } = response; + if (status !== 200) { + Logger.error(`Could not get closed captions readOnlyID for ${padId} ${status}`); + return; + } const readOnlyPadId = getDataFromResponse(response.data, 'readOnlyID'); if (readOnlyPadId) { updateReadOnlyPadId(padId, readOnlyPadId); diff --git a/bigbluebutton-html5/imports/api/captions/server/methods/takeOwnership.js b/bigbluebutton-html5/imports/api/captions/server/methods/takeOwnership.js index 51b176a2a61ebeb5c2a9136281535e169653b009..af649a8a89d501468713668e68f4cc05007a2821 100644 --- a/bigbluebutton-html5/imports/api/captions/server/methods/takeOwnership.js +++ b/bigbluebutton-html5/imports/api/captions/server/methods/takeOwnership.js @@ -2,13 +2,14 @@ import { check } from 'meteor/check'; import Captions from '/imports/api/captions'; import updateOwnerId from '/imports/api/captions/server/modifiers/updateOwnerId'; import { extractCredentials } from '/imports/api/common/server/helpers'; +import { CAPTIONS_TOKEN } from '/imports/api/captions/server/helpers'; export default function takeOwnership(locale) { const { meetingId, requesterUserId } = extractCredentials(this.userId); check(locale, String); - const pad = Captions.findOne({ meetingId, padId: { $regex: `_captions_${locale}$` } }); + const pad = Captions.findOne({ meetingId, padId: { $regex: `${CAPTIONS_TOKEN}${locale}$` } }); if (pad) { updateOwnerId(meetingId, requesterUserId, pad.padId); diff --git a/bigbluebutton-html5/imports/api/common/server/helpers.js b/bigbluebutton-html5/imports/api/common/server/helpers.js index 99720cbdb8594cda151eee7b2cda1893e847e65f..c0fb424ce95065b152d303e72bd0cf76e0fa5ed7 100755 --- a/bigbluebutton-html5/imports/api/common/server/helpers.js +++ b/bigbluebutton-html5/imports/api/common/server/helpers.js @@ -1,3 +1,4 @@ +import sha1 from 'crypto-js/sha1'; import Users from '/imports/api/users'; const MSG_DIRECT_TYPE = 'DIRECT'; @@ -38,31 +39,7 @@ export const processForHTML5ServerOnly = fn => (message, ...args) => { return fn(message, ...args); }; -/** - * Calculate a 32 bit FNV-1a hash - * Found here: https://gist.github.com/vaiorabbit/5657561 - * Ref.: http://isthe.com/chongo/tech/comp/fnv/ - * - * @param {string} str the input value - * @param {boolean} [asString=false] set to true to return the hash value as - * 8-digit hex string instead of an integer - * @param {integer} [seed] optionally pass the hash of the previous chunk - * @returns {integer | string} - */ -/* eslint-disable */ -export const hashFNV32a = (str, asString, seed) => { - let hval = (seed === undefined) ? 0x811c9dc5 : seed; - - for (let i = 0, l = str.length; i < l; i++) { - hval ^= str.charCodeAt(i); - hval += (hval << 1) + (hval << 4) + (hval << 7) + (hval << 8) + (hval << 24); - } - if (asString) { - return (`0000000${(hval >>> 0).toString(16)}`).substr(-8); - } - return hval >>> 0; -}; -/* eslint-enable */ +export const hashSHA1 = (str) => sha1(str).toString(); export const extractCredentials = (credentials) => { if (!credentials) return {}; diff --git a/bigbluebutton-html5/imports/api/note/server/helpers.js b/bigbluebutton-html5/imports/api/note/server/helpers.js index b4c5214fc492fe0a9fa3e65f8bb015a462affa49..cfac36af96480cbbca40f4b0310ad7a8aad07877 100644 --- a/bigbluebutton-html5/imports/api/note/server/helpers.js +++ b/bigbluebutton-html5/imports/api/note/server/helpers.js @@ -1,5 +1,5 @@ import { Meteor } from 'meteor/meteor'; -import { hashFNV32a } from '/imports/api/common/server/helpers'; +import { hashSHA1 } from '/imports/api/common/server/helpers'; const ETHERPAD = Meteor.settings.private.etherpad; const NOTE_CONFIG = Meteor.settings.public.note; @@ -12,10 +12,7 @@ const getReadOnlyIdURL = padId => `${BASE_URL}/getReadOnlyID?apikey=${ETHERPAD.a const appendTextURL = (padId, text) => `${BASE_URL}/appendText?apikey=${ETHERPAD.apikey}&padID=${padId}&text=${encodeURIComponent(text)}`; -const generateNoteId = (meetingId) => { - const noteId = hashFNV32a(meetingId, true); - return noteId; -}; +const generateNoteId = (meetingId) => hashSHA1(meetingId+ETHERPAD.apikey); const isEnabled = () => NOTE_CONFIG.enabled; diff --git a/bigbluebutton-html5/imports/api/note/server/methods/createNote.js b/bigbluebutton-html5/imports/api/note/server/methods/createNote.js index 8ebdbac20ae70f37c93b43e5894ded92f870c65b..656d461c0601489ebd6ba11836bee8c80895d961 100644 --- a/bigbluebutton-html5/imports/api/note/server/methods/createNote.js +++ b/bigbluebutton-html5/imports/api/note/server/methods/createNote.js @@ -22,6 +22,7 @@ export default function createNote(meetingId) { const noteId = generateNoteId(meetingId); const createURL = createPadURL(noteId); + axios({ method: 'get', url: createURL, @@ -30,6 +31,7 @@ export default function createNote(meetingId) { const { status } = responseOuter; if (status !== 200) { Logger.error(`Could not get note info for ${meetingId} ${status}`); + return; } const readOnlyURL = getReadOnlyIdURL(noteId); axios({ diff --git a/bigbluebutton-html5/imports/ui/components/captions/pad/service.js b/bigbluebutton-html5/imports/ui/components/captions/pad/service.js index 47af07b506c0dd7f3fdba9c8e96a7f1419a3ad8b..592362c672a0a61ed11c72c9d8ef3eba49ae9d1a 100644 --- a/bigbluebutton-html5/imports/ui/components/captions/pad/service.js +++ b/bigbluebutton-html5/imports/ui/components/captions/pad/service.js @@ -10,11 +10,9 @@ const getLang = () => { }; const getPadParams = () => { - const { config } = NOTE_CONFIG; - const User = Users.findOne({ userId: Auth.userID }, { fields: { name: 1, color: 1 } }); - config.userName = User.name; - config.userColor = User.color; + let config = {}; config.lang = getLang(); + config.rtl = document.documentElement.getAttribute('dir') === 'rtl'; const params = []; Object.keys(config).forEach((k) => { @@ -26,12 +24,12 @@ const getPadParams = () => { const getPadURL = (padId, readOnlyPadId, ownerId) => { const userId = Auth.userID; + const params = getPadParams(); let url; if (!ownerId || (ownerId && userId === ownerId)) { - const params = getPadParams(); url = Auth.authenticateURL(`${NOTE_CONFIG.url}/p/${padId}?${params}`); } else { - url = Auth.authenticateURL(`${NOTE_CONFIG.url}/p/${readOnlyPadId}`); + url = Auth.authenticateURL(`${NOTE_CONFIG.url}/p/${readOnlyPadId}?${params}`); } return url; }; diff --git a/bigbluebutton-html5/imports/ui/components/captions/service.js b/bigbluebutton-html5/imports/ui/components/captions/service.js index a7fe093c3556e63867e3a7f321dba3dba851997b..c757d0e41bf8c15156904c0e66c59788e42af2f6 100644 --- a/bigbluebutton-html5/imports/ui/components/captions/service.js +++ b/bigbluebutton-html5/imports/ui/components/captions/service.js @@ -7,7 +7,7 @@ import { Meteor } from 'meteor/meteor'; import { Session } from 'meteor/session'; const CAPTIONS_CONFIG = Meteor.settings.public.captions; -const CAPTIONS = '_captions_'; +const CAPTIONS_TOKEN = '_cc_'; const LINE_BREAK = '\n'; const ROLE_MODERATOR = Meteor.settings.public.user.role_moderator; @@ -19,7 +19,7 @@ const getActiveCaptions = () => { const getCaptions = locale => Captions.findOne({ meetingId: Auth.meetingID, - padId: { $regex: `${CAPTIONS}${locale}$` }, + padId: { $regex: `${CAPTIONS_TOKEN}${locale}$` }, }); const getCaptionsData = () => { @@ -170,6 +170,7 @@ const initSpeechRecognition = (locale) => { }; export default { + CAPTIONS_TOKEN, getCaptionsData, getAvailableLocales, getOwnedLocales, diff --git a/bigbluebutton-html5/imports/ui/components/note/service.js b/bigbluebutton-html5/imports/ui/components/note/service.js index 303bece5e19fc6eed230a0234cdac640244234e9..1a6ee84ea198bfda5b2388c1f9c1aa5fe496f1a3 100644 --- a/bigbluebutton-html5/imports/ui/components/note/service.js +++ b/bigbluebutton-html5/imports/ui/components/note/service.js @@ -24,18 +24,15 @@ const getLang = () => { }; const getNoteParams = () => { - const { config } = NOTE_CONFIG; - const User = Users.findOne({ userId: Auth.userID }, { fields: { name: 1, color: 1 } }); - config.userName = User.name; - config.userColor = User.color; + let config = {}; config.lang = getLang(); + config.rtl = document.documentElement.getAttribute('dir') === 'rtl'; const params = []; - for (const key in config) { - if (config.hasOwnProperty(key)) { - params.push(`${key}=${encodeURIComponent(config[key])}`); - } - } + Object.keys(config).forEach((k) => { + params.push(`${k}=${encodeURIComponent(config[k])}`); + }); + return params.join('&'); }; @@ -51,7 +48,8 @@ const isLocked = () => { const getReadOnlyURL = () => { const readOnlyNoteId = getReadOnlyNoteId(); - const url = Auth.authenticateURL(`${NOTE_CONFIG.url}/p/${readOnlyNoteId}`); + const params = getNoteParams(); + const url = Auth.authenticateURL(`${NOTE_CONFIG.url}/p/${readOnlyNoteId}?${params}`); return url; }; diff --git a/bigbluebutton-html5/package-lock.json b/bigbluebutton-html5/package-lock.json index 221ca9289fe2a2f410ae2ef66f95300845eb8b50..693d59c2fc2d9876a146535305ddfd91be44796b 100644 --- a/bigbluebutton-html5/package-lock.json +++ b/bigbluebutton-html5/package-lock.json @@ -1127,6 +1127,11 @@ "integrity": "sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs=", "dev": true }, + "crypto-js": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.0.0.tgz", + "integrity": "sha512-bzHZN8Pn+gS7DQA6n+iUmBfl0hO5DJq++QP3U6uTucDtk/0iGpXd/Gg7CGR0p8tJhofJyaKoWBuJI4eAO00BBg==" + }, "css-selector-tokenizer": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/css-selector-tokenizer/-/css-selector-tokenizer-0.7.0.tgz", diff --git a/bigbluebutton-html5/package.json b/bigbluebutton-html5/package.json index 0fba447a480a92f3142925f5347cd755b6ee313e..04f35967e9dbe2e3c1404de8198d86de03d763ea 100755 --- a/bigbluebutton-html5/package.json +++ b/bigbluebutton-html5/package.json @@ -41,6 +41,7 @@ "browser-detect": "^0.2.28", "classnames": "^2.2.6", "clipboard": "^2.0.4", + "crypto-js": "^4.0.0", "eventemitter2": "~5.0.1", "fastdom": "^1.0.9", "fibers": "^3.1.1", diff --git a/bigbluebutton-html5/private/config/settings.yml b/bigbluebutton-html5/private/config/settings.yml index 3149cd1087702d5729ab7046fe82c6ab4f5db3a7..d3ee7bd7bcefe07a941b447d1b3730c357749bac 100755 --- a/bigbluebutton-html5/private/config/settings.yml +++ b/bigbluebutton-html5/private/config/settings.yml @@ -288,12 +288,6 @@ public: note: enabled: false url: ETHERPAD_HOST - config: - showLineNumbers: false - showChat: false - noColors: true - showControls: true - rtl: false layout: autoSwapLayout: false hidePresentation: false diff --git a/record-and-playback/core/Gemfile b/record-and-playback/core/Gemfile index 375fc602601ec1320e211cd15e94792a75b502d3..f59575913f66709e322ef88b65022a2954d3ee11 100644 --- a/record-and-playback/core/Gemfile +++ b/record-and-playback/core/Gemfile @@ -22,7 +22,6 @@ source "https://rubygems.org" gem "absolute_time" gem "builder" gem "fastimage" -gem "fnv" gem "java_properties" gem "journald-logger" gem "jwt" diff --git a/record-and-playback/core/Gemfile.lock b/record-and-playback/core/Gemfile.lock index e4ca02b41ab35794edac20556e8541ac7601e51e..4bba32d81d4bb5c9f8eeb202c7988ce238c1c28d 100644 --- a/record-and-playback/core/Gemfile.lock +++ b/record-and-playback/core/Gemfile.lock @@ -7,7 +7,6 @@ GEM crass (1.0.5) fastimage (2.1.5) ffi (1.11.1) - fnv (0.2.0) jaro_winkler (1.5.2) java_properties (0.0.4) journald-logger (2.0.4) @@ -48,7 +47,6 @@ DEPENDENCIES absolute_time builder fastimage - fnv java_properties journald-logger jwt diff --git a/record-and-playback/core/lib/recordandplayback.rb b/record-and-playback/core/lib/recordandplayback.rb index 59eb0b6346d697cb5d530873f290e898bf9c0a33..db4661644f95bd8090cea086bf7a6d6c82f84021 100755 --- a/record-and-playback/core/lib/recordandplayback.rb +++ b/record-and-playback/core/lib/recordandplayback.rb @@ -36,7 +36,7 @@ require 'logger' require 'find' require 'rubygems' require 'net/http' -require 'fnv' +require 'digest' require 'shellwords' require 'English' @@ -226,9 +226,10 @@ module BigBlueButton r.split("-")[1].to_i / 1000 end - # Notes id will be an 8-sized hash string based on the meeting id - def self.get_notes_id(meeting_id) - FNV.new.fnv1a_32(meeting_id).to_s(16).rjust(8, '0') + # Notes id will be a SHA1 hash string based on the meeting id and etherpad's apikey + def self.get_notes_id(meeting_id, notes_apikey) + value = meeting_id + notes_apikey + Digest::SHA1.hexdigest value end def self.done_to_timestamp(r) diff --git a/record-and-playback/core/scripts/archive/archive.rb b/record-and-playback/core/scripts/archive/archive.rb index 824e883788a49fe544906dc94e1af87d72f62e29..3a4167ecfb30cd35bfe41c3dcecb80205ec075ce 100755 --- a/record-and-playback/core/scripts/archive/archive.rb +++ b/record-and-playback/core/scripts/archive/archive.rb @@ -45,11 +45,11 @@ def archive_events(meeting_id, redis_host, redis_port, redis_password, raw_archi end end -def archive_notes(meeting_id, notes_endpoint, notes_formats, raw_archive_dir) +def archive_notes(meeting_id, notes_endpoint, notes_formats, notes_apikey, raw_archive_dir) BigBlueButton.logger.info("Archiving notes for #{meeting_id}") notes_dir = "#{raw_archive_dir}/#{meeting_id}/notes" FileUtils.mkdir_p(notes_dir) - notes_id = BigBlueButton.get_notes_id(meeting_id) + notes_id = BigBlueButton.get_notes_id(meeting_id, notes_apikey) tmp_note = "#{notes_dir}/tmp_note.txt" BigBlueButton.try_download("#{notes_endpoint}/#{notes_id}/export/txt", tmp_note) @@ -180,6 +180,7 @@ kurento_screenshare_dir = props['kurento_screenshare_src'] log_dir = props['log_dir'] notes_endpoint = props['notes_endpoint'] notes_formats = props['notes_formats'] +notes_apikey = props['notes_apikey'] # Determine the filenames for the done and fail files if !break_timestamp.nil? @@ -198,7 +199,7 @@ archive_events(meeting_id, redis_host, redis_port, redis_password, raw_archive_d # FreeSWITCH Audio files archive_audio(meeting_id, audio_dir, raw_archive_dir) # Etherpad notes -archive_notes(meeting_id, notes_endpoint, notes_formats, raw_archive_dir) +archive_notes(meeting_id, notes_endpoint, notes_formats, notes_apikey, raw_archive_dir) # Presentation files archive_directory("#{presentation_dir}/#{meeting_id}/#{meeting_id}", "#{target_dir}/presentation") # Red5 media diff --git a/record-and-playback/core/scripts/bigbluebutton.yml b/record-and-playback/core/scripts/bigbluebutton.yml index c5dd4006a34f92f76d7feb35199b9b25652e959d..d491cf8aa9a7bd2cff29c42f82617c613f50c558 100755 --- a/record-and-playback/core/scripts/bigbluebutton.yml +++ b/record-and-playback/core/scripts/bigbluebutton.yml @@ -8,6 +8,7 @@ raw_webrtc_deskshare_src: /usr/share/red5/webapps/video-broadcast/streams raw_deskshare_src: /var/bigbluebutton/deskshare raw_presentation_src: /var/bigbluebutton notes_endpoint: http://localhost:9001/p +notes_apikey: ETHERPAD_APIKEY # Specify the notes formats we archive # txt, doc and odt are also supported notes_formats: diff --git a/record-and-playback/core/scripts/events/events.rb b/record-and-playback/core/scripts/events/events.rb index 22d9035dc5dcad6f6fc540cd368bb430f907c2c0..85200edcac64e3f952dbda6a316a219eabf0c460 100755 --- a/record-and-playback/core/scripts/events/events.rb +++ b/record-and-playback/core/scripts/events/events.rb @@ -24,9 +24,9 @@ require 'logger' require 'trollop' require 'yaml' -def keep_etherpad_events(meeting_id, events_etherpad, notes_endpoint) +def keep_etherpad_events(meeting_id, events_etherpad, notes_endpoint, notes_apikey) BigBlueButton.logger.info("Keeping etherpad events for #{meeting_id}") - notes_id = BigBlueButton.get_notes_id(meeting_id) + notes_id = BigBlueButton.get_notes_id(meeting_id, notes_apikey) # Always fetch for the audit format BigBlueButton.try_download("#{notes_endpoint}/#{notes_id}/export/etherpad", events_etherpad) @@ -61,6 +61,7 @@ redis_port = props['redis_port'] redis_password = props['redis_password'] log_dir = props['log_dir'] notes_endpoint = props['notes_endpoint'] +notes_apikey = props['notes_apikey'] raw_events_xml = "#{raw_archive_dir}/#{meeting_id}/events.xml" ended_done_file = "#{recording_dir}/status/ended/#{meeting_id}.done" @@ -83,7 +84,7 @@ if not FileTest.directory?(target_dir) FileUtils.mkdir_p target_dir events_etherpad = "#{target_dir}/events.etherpad" - keep_etherpad_events(meeting_id, events_etherpad, notes_endpoint) + keep_etherpad_events(meeting_id, events_etherpad, notes_endpoint, notes_apikey) events_xml = "#{target_dir}/events.xml" if File.exist? raw_events_xml