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. (#15074)

* fix(transcriptions): Uses dial command to invite transcriber.

* fix(transcriptions,recording): Allows non moderators with features to dial, record or transcribe.

* sqaush: Make sure filtering works when only is a moderator.

It works now and without a token and no features, but being moderator.

* squash: Rename constant.

* squash: Checks features first before defaulting to moderator when filtering metadata service.

* squash: Checks features first before defaulting to moderator in UI.

* squash: Fixes lint and one other check.

* squash: Moves more logic to is_feature_allowed.

* squash: Drops unnecessary check.

* squash: Uses constant coming from ljm.

* squash: Toggles back captions button on error.

* squash: Fix comment.

* squash: Reverting back isLiveStreamingButtonVisible.

* squash: Fix imports.
  • Loading branch information
damencho authored Sep 13, 2024
1 parent 262cb04 commit b3742a3
Show file tree
Hide file tree
Showing 16 changed files with 154 additions and 118 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 the local recording too.
*/
export const FEATURES_TO_BUTTONS_MAPPING = {
'livestreaming': 'livestreaming',
'recording': 'recording',
'transcription': 'closedcaptions'
};

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

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

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

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,9 +133,8 @@ export function _mapStateToProps(state: IReduxState, ownProps: IProps) {
const liveStreaming = getLiveStreaming(state);

visible = isLiveStreamingButtonVisible({
localParticipantIsModerator: isModerator,
liveStreamingAllowed: isJwtFeatureEnabled(state, 'livestreaming', isModerator, isModerator),
liveStreamingEnabled: liveStreaming?.enabled,
liveStreamingEnabledInJwt: isJwtFeatureEnabled(state, 'livestreaming', true),
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 Down Expand Up @@ -34,11 +35,6 @@ export interface IProps extends WithTranslation {
*/
_hideStorageWarning: boolean;

/**
* Whether local participant is moderator.
*/
_isModerator: boolean;

/**
* Whether local recording is available or not.
*/
Expand All @@ -59,6 +55,11 @@ export interface IProps extends WithTranslation {
*/
_localRecordingSelfEnabled: boolean;

/**
* Whether to render recording.
*/
_renderRecording: boolean;

/**
* The color-schemed stylesheet of this component.
*/
Expand Down Expand Up @@ -412,15 +413,15 @@ 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();
const isModerator = isLocalParticipantModerator(state);

return {
..._abstractMapStateToProps(state),
isVpaas: isVpaasMeeting(state),
_canStartTranscribing: canAddTranscriber(state),
_hideStorageWarning: Boolean(recordingService?.hideStorageWarning),
_isModerator: isLocalParticipantModerator(state),
_renderRecording: isJwtFeatureEnabled(state, 'recording', isModerator, isModerator),
_localRecordingAvailable,
_localRecordingEnabled: !localRecording?.disable,
_localRecordingSelfEnabled: !localRecording?.disableSelfRecording,
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._renderRecording;

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
27 changes: 10 additions & 17 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 (isJwtFeatureEnabled(state, 'recording', isModerator, isModerator)) {
visible = recordingEnabled;
}

// disable the button if the livestreaming is running.
Expand Down Expand Up @@ -416,29 +416,22 @@ export function registerRecordingAudioFiles(dispatch: IStore['dispatch'], should
}

/**
* Returns true if the live streaming button should be visible.
* Returns true if the live-streaming button should be visible.
*
* @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} liveStreamingEnabled - True if the live-streaming is enabled.
* @param {boolean} liveStreamingAllowed - True if the live-streaming feature is enabled in JWT
* or is a moderator if JWT is missing or features are missing in JWT.
* @param {boolean} isInBreakoutRoom - True if in breakout room.
* @returns {boolean}
*/
export function isLiveStreamingButtonVisible({
localParticipantIsModerator,
liveStreamingAllowed,
liveStreamingEnabled,
liveStreamingEnabledInJwt,
isInBreakoutRoom
}: {
isInBreakoutRoom: boolean;
liveStreamingAllowed: boolean;
liveStreamingEnabled: boolean;
liveStreamingEnabledInJwt: boolean;
localParticipantIsModerator: boolean;
}) {
let visible = false;

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

return visible;
return !isInBreakoutRoom && liveStreamingEnabled && liveStreamingAllowed;
}
7 changes: 3 additions & 4 deletions react/features/recording/hooks.web.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,15 +47,14 @@ export function useLiveStreamingButton() {
const toolbarButtons = useSelector((state: IReduxState) => state['features/toolbox'].toolbarButtons);
const localParticipantIsModerator = useSelector(isLocalParticipantModerator);
const liveStreaming = useSelector(getLiveStreaming);
const liveStreamingEnabledInJwt
= useSelector((state: IReduxState) => isJwtFeatureEnabled(state, 'livestreaming', true));
const liveStreamingAllowed = useSelector((state: IReduxState) =>
isJwtFeatureEnabled(state, 'livestreaming', localParticipantIsModerator, localParticipantIsModerator));
const _isInBreakoutRoom = useSelector(isInBreakoutRoom);

if (toolbarButtons?.includes('recording')
&& isLiveStreamingButtonVisible({
localParticipantIsModerator,
liveStreamingAllowed,
liveStreamingEnabled: liveStreaming?.enabled,
liveStreamingEnabledInJwt,
isInBreakoutRoom: _isInBreakoutRoom
})) {
return livestreaming;
Expand Down
27 changes: 26 additions & 1 deletion react/features/subtitles/middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ import { AnyAction } from 'redux';

import { IStore } from '../app/types';
import { ENDPOINT_MESSAGE_RECEIVED } from '../base/conference/actionTypes';
import { isJwtFeatureEnabled } from '../base/jwt/functions';
import JitsiMeetJS from '../base/lib-jitsi-meet';
import { isLocalParticipantModerator } from '../base/participants/functions';
import MiddlewareRegistry from '../base/redux/MiddlewareRegistry';

import {
Expand All @@ -10,9 +13,11 @@ import {
} from './actionTypes';
import {
removeTranscriptMessage,
setRequestingSubtitles,
updateTranscriptMessage
} from './actions.any';
import { notifyTranscriptionChunkReceived } from './functions';
import logger from './logger';
import { ITranscriptMessage } from './types';


Expand Down Expand Up @@ -40,6 +45,11 @@ const P_NAME_REQUESTING_TRANSCRIPTION = 'requestingTranscription';
*/
const P_NAME_TRANSLATION_LANGUAGE = 'translation_language';

/**
* The dial command to use for starting a transcriber.
*/
const TRANSCRIBER_DIAL_NUMBER = 'jitsi_meet_transcribe';

/**
* Time after which the rendered subtitles will be removed.
*/
Expand Down Expand Up @@ -229,7 +239,7 @@ function _endpointMessageReceived(store: IStore, next: Function, action: AnyActi
* @returns {void}
*/
function _requestingSubtitlesChange(
{ getState }: IStore,
{ dispatch, getState }: IStore,
enabled: boolean,
language?: string | null) {
const state = getState();
Expand All @@ -239,6 +249,21 @@ function _requestingSubtitlesChange(
P_NAME_REQUESTING_TRANSCRIPTION,
enabled);

if (enabled && conference?.getTranscriptionStatus() === JitsiMeetJS.constants.transcriptionStatus.OFF) {
const isModerator = isLocalParticipantModerator(state);
const featureAllowed = isJwtFeatureEnabled(getState(), 'transcription', isModerator, isModerator);

if (featureAllowed) {
conference?.dial(TRANSCRIBER_DIAL_NUMBER)
.catch((e: any) => {
logger.error('Error dialing', e);

// let's back to the correct state
dispatch(setRequestingSubtitles(false, false));
});
}
}

if (enabled && language) {
conference?.setLocalParticipantProperty(
P_NAME_TRANSLATION_LANGUAGE,
Expand Down
13 changes: 3 additions & 10 deletions react/features/transcribing/functions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,15 +77,8 @@ export function isRecorderTranscriptionsRunning(state: IReduxState) {
*/
export function canAddTranscriber(state: IReduxState) {
const { transcription } = state['features/base/config'];
const isJwtTranscribingEnabled = isJwtFeatureEnabled(state, 'transcription', isLocalParticipantModerator(state));
const isModerator = isLocalParticipantModerator(state);
const isTranscribingAllowed = isJwtFeatureEnabled(state, 'transcription', isModerator, isModerator);

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

if (isJwtTranscribingEnabled) {
return true;
}

return false;
return Boolean(transcription?.enabled) && isTranscribingAllowed;
}
29 changes: 18 additions & 11 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,17 +15,19 @@ module:hook("pre-iq/full", function(event)
if jibri then
local session = event.origin;
local token = session.auth_token;
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);
local feature = jibri.attr.recording_mode == 'file' and 'recording' or 'livestreaming';
local is_allowed = is_feature_allowed(
feature,
session.jitsi_meet_context_features,
session.granted_jitsi_meet_context_features,
occupant.role == 'moderator');

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
module:log("info",
"Filtering jibri start recording, stanza:%s", tostring(stanza));
session.send(st.error_reply(stanza, "auth", "forbidden"));
return true;
end
if jibri.attr.action == 'start' and not is_allowed then
module:log('info', 'Filtering jibri start recording, stanza:%s', tostring(stanza));
session.send(st.error_reply(stanza, 'auth', 'forbidden'));
return true;
end
end
end
Expand Down
Loading

0 comments on commit b3742a3

Please sign in to comment.