Skip to content

Commit

Permalink
fix(transcriptions,recording): Allows non moderators with features to…
Browse files Browse the repository at this point in the history
… dial, record or transcribe.
  • Loading branch information
damencho committed Sep 3, 2024
1 parent abde8d1 commit 3abd747
Show file tree
Hide file tree
Showing 14 changed files with 111 additions and 66 deletions.
1 change: 1 addition & 0 deletions react/features/base/conference/reducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ export interface IJitsiConference {
getRole: Function;
getSpeakerStats: () => ISpeakerStats;
getSsrcByTrack: Function;
getTranscriptionStatus: Function;
grantOwner: Function;
isAVModerationSupported: Function;
isE2EEEnabled: Function;
Expand Down
2 changes: 1 addition & 1 deletion react/features/base/jwt/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,10 @@ export const MEET_FEATURES = {

/**
* A mapping between jwt features and toolbar buttons keys.
* We don't need recording in here, as it will disable and local recording.
*/
export const FEATURES_TO_BUTTONS_MAPPING = {
'livestreaming': 'livestreaming',
'recording': 'recording',
'transcription': 'closedcaptions'
};

Expand Down
5 changes: 2 additions & 3 deletions react/features/invite/functions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -492,7 +492,7 @@ export function isAddPeopleEnabled(state: IReduxState): boolean {
export function isDialOutEnabled(state: IReduxState): boolean {
const { conference } = state['features/base/conference'];

return isLocalParticipantModerator(state)
return (isLocalParticipantModerator(state) || isJwtFeatureEnabled(state, 'outbound-call', false, false))
&& conference && conference.isSIPCallingSupported();
}

Expand All @@ -505,8 +505,7 @@ export function isDialOutEnabled(state: IReduxState): boolean {
export function isSipInviteEnabled(state: IReduxState): boolean {
const { sipInviteUrl } = state['features/base/config'];

return isLocalParticipantModerator(state)
&& isJwtFeatureEnabled(state, 'sip-outbound-call')
return (isLocalParticipantModerator(state) || isJwtFeatureEnabled(state, 'sip-outbound-call', false, false))
&& Boolean(sipInviteUrl);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ export function _mapStateToProps(state: IReduxState, ownProps: IProps) {
visible = isLiveStreamingButtonVisible({
localParticipantIsModerator: isModerator,
liveStreamingEnabled: liveStreaming?.enabled,
liveStreamingEnabledInJwt: isJwtFeatureEnabled(state, 'livestreaming', true),
liveStreamingEnabledInJwt: isJwtFeatureEnabled(state, 'livestreaming', false, false),
isInBreakoutRoom: isInBreakoutRoom(state)
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { sendAnalytics } from '../../../analytics/functions';
import { IReduxState, IStore } from '../../../app/types';
import ColorSchemeRegistry from '../../../base/color-scheme/ColorSchemeRegistry';
import { _abstractMapStateToProps } from '../../../base/dialog/functions';
import { isJwtFeatureEnabled } from '../../../base/jwt/functions';
import { isLocalParticipantModerator } from '../../../base/participants/functions';
import { authorizeDropbox, updateDropboxToken } from '../../../dropbox/actions';
import { isVpaasMeeting } from '../../../jaas/functions';
Expand All @@ -29,6 +30,11 @@ export interface IProps extends WithTranslation {
*/
_dialogStyles: any;

/**
* Whether recording feature is enabled.
*/
_hasRecordingFeature: boolean;

/**
* Whether to hide the storage warning or not.
*/
Expand Down Expand Up @@ -412,13 +418,13 @@ class AbstractStartRecordingDialogContent extends Component<IProps, IState> {
*/
export function mapStateToProps(state: IReduxState) {
const { localRecording, recordingService } = state['features/base/config'];
const _localRecordingAvailable
= !localRecording?.disable && supportsLocalRecording();
const _localRecordingAvailable = !localRecording?.disable && supportsLocalRecording();

return {
..._abstractMapStateToProps(state),
isVpaas: isVpaasMeeting(state),
_canStartTranscribing: canAddTranscriber(state),
_hasRecordingFeature: isJwtFeatureEnabled(state, 'recording', false, false),
_hideStorageWarning: Boolean(recordingService?.hideStorageWarning),
_isModerator: isLocalParticipantModerator(state),
_localRecordingAvailable,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,11 @@ class StartRecordingDialogContent extends AbstractStartRecordingDialogContent {
* @returns {React$Component}
*/
render() {
const _renderRecording = this.props._isModerator || this.props._hasRecordingFeature;

return (
<Container className = 'recording-dialog'>
{ this.props._isModerator && (
{ _renderRecording && (
<>
{ this._renderNoIntegrationsContent() }
{ this._renderFileSharingContent() }
Expand All @@ -48,7 +50,7 @@ class StartRecordingDialogContent extends AbstractStartRecordingDialogContent {
</>
)}
{ this._renderLocalRecordingContent() }
{ this._renderAdvancedOptions() }
{ _renderRecording && <> { this._renderAdvancedOptions() } </> }
</Container>
);
}
Expand Down
11 changes: 6 additions & 5 deletions react/features/recording/functions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -267,8 +267,8 @@ export function getRecordButtonProps(state: IReduxState) {

if (localRecordingEnabled) {
visible = true;
} else if (isModerator) {
visible = recordingEnabled ? isJwtFeatureEnabled(state, 'recording', true) : false;
} else if (isModerator || isJwtFeatureEnabled(state, 'recording', false, false)) {
visible = recordingEnabled;
}

// disable the button if the livestreaming is running.
Expand Down Expand Up @@ -420,7 +420,8 @@ export function registerRecordingAudioFiles(dispatch: IStore['dispatch'], should
*
* @param {boolean} localParticipantIsModerator - True if the local participant is moderator.
* @param {boolean} liveStreamingEnabled - True if the live streaming is enabled.
* @param {boolean} liveStreamingEnabledInJwt - True if the lives treaming feature is enabled in JWT.
* @param {boolean} liveStreamingEnabledInJwt - True if the lives streaming feature is enabled in JWT.
* @param {boolean} isInBreakoutRoom - True if currently in breakout room.
* @returns {boolean}
*/
export function isLiveStreamingButtonVisible({
Expand All @@ -436,8 +437,8 @@ export function isLiveStreamingButtonVisible({
}) {
let visible = false;

if (localParticipantIsModerator && !isInBreakoutRoom) {
visible = liveStreamingEnabled ? liveStreamingEnabledInJwt : false;
if (!isInBreakoutRoom) {
visible = liveStreamingEnabled ? localParticipantIsModerator || liveStreamingEnabledInJwt : false;
}

return visible;
Expand Down
4 changes: 2 additions & 2 deletions react/features/transcribing/functions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,13 +77,13 @@ export function isRecorderTranscriptionsRunning(state: IReduxState) {
*/
export function canAddTranscriber(state: IReduxState) {
const { transcription } = state['features/base/config'];
const isJwtTranscribingEnabled = isJwtFeatureEnabled(state, 'transcription', isLocalParticipantModerator(state));
const isJwtTranscribingEnabled = isJwtFeatureEnabled(state, 'transcription', false, false);

if (!transcription?.enabled) {
return false;
}

if (isJwtTranscribingEnabled) {
if (isLocalParticipantModerator(state) || isJwtTranscribingEnabled) {
return true;
}

Expand Down
33 changes: 28 additions & 5 deletions resources/prosody-plugins/mod_filter_iq_jibri.lua
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
-- This module is enabled under the main virtual host
local st = require "util.stanza";
local is_feature_allowed = module:require "util".is_feature_allowed;
local jid_bare = require "util.jid".bare;
local util = module:require 'util';
local is_feature_allowed = util.is_feature_allowed;
local get_room_from_jid = util.get_room_from_jid;
local room_jid_match_rewrite = util.room_jid_match_rewrite;

-- filters jibri iq in case of requested from jwt authenticated session that
-- has features in the user context, but without feature for recording
Expand All @@ -10,12 +15,30 @@ module:hook("pre-iq/full", function(event)
if jibri then
local session = event.origin;
local token = session.auth_token;
local feature = jibri.attr.recording_mode == 'file' and 'recording' or 'livestreaming';
local is_allowed = is_feature_allowed(session.jitsi_meet_context_features, feature);

-- if current user is not allowed, but was granted moderation by a user
-- that is allowed by its features we want to allow it
local is_granting_allowed = false;
if session.granted_jitsi_meet_context_features then
is_granting_allowed = is_feature_allowed(session.granted_jitsi_meet_context_features, feature);
end

if jibri.attr.action == 'start' then
if token == nil
or not is_feature_allowed(session.jitsi_meet_context_features,
(jibri.attr.recording_mode == 'file' and 'recording' or 'livestreaming')
) then
if token == nil or not (is_allowed or is_granting_allowed)
then
if not session.jitsi_meet_context_features and not session.granted_jitsi_meet_context_features then
-- we need to check for moderator rights
-- when there are no features and the occupant is moderator we allow recording
local room = get_room_from_jid(room_jid_match_rewrite(jid_bare(stanza.attr.to)));
local occupant = room:get_occupant_by_real_jid(stanza.attr.from);

if occupant.role == 'moderator' then
return;
end
end

module:log("info",
"Filtering jibri start recording, stanza:%s", tostring(stanza));
session.send(st.error_reply(stanza, "auth", "forbidden"));
Expand Down
41 changes: 37 additions & 4 deletions resources/prosody-plugins/mod_filter_iq_rayo.lua
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
-- This module is enabled under the main virtual host
local new_throttle = require "util.throttle".create;
local st = require "util.stanza";

Expand Down Expand Up @@ -75,13 +76,21 @@ module:hook("pre-iq/full", function(event)
-- if current user is not allowed, but was granted moderation by a user
-- that is allowed by its features we want to allow it
local is_granting_session_allowed = false;
if (session.granted_jitsi_meet_context_features) then
if session.granted_jitsi_meet_context_features then
is_granting_session_allowed = is_feature_allowed(session.granted_jitsi_meet_context_features, feature);
end

local room_real_jid = room_jid_match_rewrite(roomName);

if not session.jitsi_meet_context_features and not session.granted_jitsi_meet_context_features then
-- if there is no features in the token we need to check whether the participants is moderator
local room = main_muc_service.get_room_from_jid(room_real_jid);
is_session_allowed = room:get_affiliation(stanza.attr.from) == 'owner';
end

if (token == nil
or roomName == nil
or not token_util:verify_room(session, room_jid_match_rewrite(roomName))
or not token_util:verify_room(session, room_real_jid)
or not (is_session_allowed or is_granting_session_allowed))
then
module:log("warn", "Filtering stanza dial, stanza:%s", tostring(stanza));
Expand All @@ -99,8 +108,8 @@ module:hook("pre-iq/full", function(event)
group_id = session.granted_jitsi_meet_context_group_id;
end

-- now lets check any limits if configured
if limit_outgoing_calls > 0 then
-- now lets check any limits for outgoing calls if configured
if feature == 'outbound-call' and limit_outgoing_calls > 0 then
if not session.dial_out_throttle then
-- module:log("debug", "Enabling dial-out throttle session=%s.", session);
session.dial_out_throttle = new_throttle(limit_outgoing_calls, OUTGOING_CALLS_THROTTLE_INTERVAL);
Expand Down Expand Up @@ -259,3 +268,27 @@ process_host_module(main_muc_component_host, function(host_module, host)
end);
end
end);

-- when recording participants may enable and backend transcriptions
-- it is possible that participant is not moderator, but has the features enabled for
-- transcribing, we need to allow that operation
module:hook('jitsi-metadata-allow-moderation', function (event)
local data, key, occupant, session = event.data, event.key, event.actor, event.session;

if occupant.role == 'moderator' then
return data;
end

if key == 'recording' and data and data.isTranscribingEnabled ~= nil
and occupant.role ~= 'moderator'
and is_feature_allowed(session.jitsi_meet_context_features, 'transcription')
and is_feature_allowed(session.jitsi_meet_context_features, 'recording') then

local res = {};
res.isTranscribingEnabled = data.isTranscribingEnabled;
return res;
end

return nil;
end);

31 changes: 0 additions & 31 deletions resources/prosody-plugins/mod_muc_transcription_filter.lua

This file was deleted.

26 changes: 20 additions & 6 deletions resources/prosody-plugins/mod_room_metadata_component.lua
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,13 @@ local FORM_KEY = 'muc#roominfo_jitsimetadata';
local muc_component_host = module:get_option_string('muc_component');

if muc_component_host == nil then
module:log("error", "No muc_component specified. No muc to operate on!");
module:log('error', 'No muc_component specified. No muc to operate on!');
return;
end

local muc_domain_base = module:get_option_string('muc_mapper_domain_base');
if not muc_domain_base then
module:log('warn', 'No muc_domain_base option set.');
return;
end

Expand Down Expand Up @@ -129,11 +135,6 @@ function on_message(event)
return false;
end

if occupant.role ~= 'moderator' then
module:log('warn', 'Occupant %s is not moderator and not allowed this operation for %s', from, room.jid);
return false;
end

local jsonData, error = json.decode(messageText);
if jsonData == nil then -- invalid JSON
module:log("error", "Invalid JSON message: %s error:%s", messageText, error);
Expand All @@ -145,6 +146,19 @@ function on_message(event)
return false;
end

if occupant.role ~= 'moderator' then
-- will return a non nil filtered data to use, if it is nil, it is not allowed
local res = module:context(muc_domain_base):fire_event('jitsi-metadata-allow-moderation',
{ room = room; actor = occupant; key = jsonData.key ; data = jsonData.data; session = session; });

if not res then
module:log('warn', 'Occupant %s is not moderator and not allowed this operation for %s', from, room.jid);
return false;
end

jsonData.data = res;
end

room.jitsiMetadata[jsonData.key] = jsonData.data;

broadcastMetadata(room);
Expand Down
1 change: 0 additions & 1 deletion resources/prosody-plugins/mod_system_chat_message.lua
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ local get_room_from_jid = util.get_room_from_jid;
local st = require "util.stanza";
local json = require "cjson.safe";

local muc_domain_base = module:get_option_string("muc_mapper_domain_base");
local asapKeyServer = module:get_option_string("prosody_password_public_key_repo_url", "");

if asapKeyServer then
Expand Down
4 changes: 1 addition & 3 deletions resources/prosody-plugins/util.lib.lua
Original file line number Diff line number Diff line change
Expand Up @@ -250,10 +250,8 @@ end
-- Utility function to check whether feature is present and enabled. Allow
-- a feature if there are features present in the session(coming from
-- the token) and the value of the feature is true.
-- If features is not present in the token we skip feature detection and allow
-- everything.
function is_feature_allowed(features, ft)
if (features == nil or features[ft] == "true" or features[ft] == true) then
if features ~= nil and (features[ft] == "true" or features[ft] == true) then
return true;
else
return false;
Expand Down

0 comments on commit 3abd747

Please sign in to comment.