feat: Backend reports default permissions.

When any of the backend is used 'anonymous', 'jitsi-anonymous', 'internal_hashed', 'internal_plain', 'cyrus' and a participant becomes a moderator, because of external module or because set from jicofo we send to client with the self-presence about becoming moderator a default set of permissions which can be controlled via prosody config.
If using 'token' authentication the above applies only if there is a token and the token does not contain context.features.
This commit is contained in:
damencho
2025-03-26 13:18:30 -05:00
committed by Дамян Минков
parent b97798e1ca
commit 92df4bfbbb
19 changed files with 219 additions and 102 deletions

View File

@@ -45,14 +45,12 @@ export function getJwtName(state: IReduxState) {
*
* @param {IReduxState} state - The app state.
* @param {string} feature - The feature we want to check.
* @param {boolean} ifNoToken - Default value if there is no token.
* @param {boolean} ifNotInFeatures - Default value if features prop exists but does not have the {@code feature}.
* @returns {boolean}
*/
export function isJwtFeatureEnabled(
state: IReduxState,
feature: ParticipantFeaturesKey,
ifNoToken: boolean,
ifNotInFeatures: boolean
) {
const { jwt } = state['features/base/jwt'];
@@ -67,14 +65,12 @@ export function isJwtFeatureEnabled(
jwt,
localParticipantFeatures: features,
feature,
ifNoToken,
ifNotInFeatures
});
}
interface IIsJwtFeatureEnabledStatelessParams {
feature: ParticipantFeaturesKey;
ifNoToken: boolean;
ifNotInFeatures: boolean;
jwt?: string;
localParticipantFeatures?: IParticipantFeatures;
@@ -86,7 +82,6 @@ interface IIsJwtFeatureEnabledStatelessParams {
* @param {string | undefined} jwt - The jwt token.
* @param {ILocalParticipant} localParticipantFeatures - The features of the local participant.
* @param {string} feature - The feature we want to check.
* @param {boolean} ifNoToken - Default value if there is no token.
* @param {boolean} ifNotInFeatures - Default value if features is missing
* or prop exists but does not have the {@code feature}.
* @returns {boolean}
@@ -95,18 +90,9 @@ export function isJwtFeatureEnabledStateless({
jwt,
localParticipantFeatures: features,
feature,
ifNoToken,
ifNotInFeatures
}: IIsJwtFeatureEnabledStatelessParams) {
if (!jwt) {
return ifNoToken;
}
if (typeof features === 'undefined') {
return ifNoToken;
}
if (typeof features[feature] === 'undefined') {
if (!jwt || typeof features?.[feature] === 'undefined') {
return ifNotInFeatures;
}

View File

@@ -7,6 +7,7 @@ import emojiAsciiAliases from 'react-emoji-render/data/asciiAliases';
import { IReduxState } from '../app/types';
import { getLocalizedDateFormatter } from '../base/i18n/dateUtil';
import i18next from '../base/i18n/i18next';
import { MEET_FEATURES } from '../base/jwt/constants';
import { isJwtFeatureEnabled } from '../base/jwt/functions';
import { getParticipantById } from '../base/participants/functions';
import { escapeRegexp } from '../base/util/helpers';
@@ -206,5 +207,5 @@ export function isSendGroupChatDisabled(state: IReduxState) {
return false;
}
return !isJwtFeatureEnabled(state, 'send-groupchat', false, false);
return !isJwtFeatureEnabled(state, MEET_FEATURES.SEND_GROUPCHAT, false);
}

View File

@@ -256,6 +256,7 @@ MiddlewareRegistry.register(store => next => action => {
lobbyChat: false
}, false, true);
}
break;
}
}

View File

@@ -7,9 +7,10 @@ import { getRoomName } from '../base/conference/functions';
import { getInviteURL } from '../base/connection/functions';
import { isIosMobileBrowser } from '../base/environment/utils';
import i18next from '../base/i18n/i18next';
import { MEET_FEATURES } from '../base/jwt/constants';
import { isJwtFeatureEnabled } from '../base/jwt/functions';
import { JitsiRecordingConstants } from '../base/lib-jitsi-meet';
import { getLocalParticipant, isLocalParticipantModerator } from '../base/participants/functions';
import { getLocalParticipant } from '../base/participants/functions';
import { toState } from '../base/redux/functions';
import { doGetJSON } from '../base/util/httpUtils';
import { parseURLParams } from '../base/util/parseURLParams';
@@ -491,10 +492,8 @@ export function isAddPeopleEnabled(state: IReduxState): boolean {
*/
export function isDialOutEnabled(state: IReduxState): boolean {
const { conference } = state['features/base/conference'];
const isModerator = isLocalParticipantModerator(state);
return isJwtFeatureEnabled(state, 'outbound-call', isModerator, false)
&& conference?.isSIPCallingSupported();
return isJwtFeatureEnabled(state, MEET_FEATURES.OUTBOUND_CALL, false) && conference?.isSIPCallingSupported();
}
/**
@@ -505,10 +504,8 @@ export function isDialOutEnabled(state: IReduxState): boolean {
*/
export function isSipInviteEnabled(state: IReduxState): boolean {
const { sipInviteUrl } = state['features/base/config'];
const isModerator = isLocalParticipantModerator(state);
return isJwtFeatureEnabled(state, 'sip-outbound-call', isModerator, false)
&& Boolean(sipInviteUrl);
return isJwtFeatureEnabled(state, MEET_FEATURES.SIP_OUTBOUND_CALL, false) && Boolean(sipInviteUrl);
}
/**

View File

@@ -1,4 +1,5 @@
import { IReduxState } from '../app/types';
import { MEET_FEATURES } from '../base/jwt/constants';
import { isJwtFeatureEnabled } from '../base/jwt/functions';
import { IAnswerData } from './types';
@@ -77,5 +78,5 @@ export function isCreatePollDisabled(state: IReduxState) {
return false;
}
return !isJwtFeatureEnabled(state, 'create-polls', false, false);
return !isJwtFeatureEnabled(state, MEET_FEATURES.CREATE_POLLS, false);
}

View File

@@ -1,12 +1,9 @@
import { IStore } from '../app/types';
import { getMeetingRegion, getRecordingSharingUrl } from '../base/config/functions';
import { MEET_FEATURES } from '../base/jwt/constants';
import { isJwtFeatureEnabled } from '../base/jwt/functions';
import JitsiMeetJS, { JitsiRecordingConstants } from '../base/lib-jitsi-meet';
import {
getLocalParticipant,
getParticipantDisplayName,
isLocalParticipantModerator
} from '../base/participants/functions';
import { getLocalParticipant, getParticipantDisplayName } from '../base/participants/functions';
import { BUTTON_TYPES } from '../base/ui/constants.any';
import { copyText } from '../base/util/copyText';
import { getVpaasTenant, isVpaasMeeting } from '../jaas/functions';
@@ -435,10 +432,9 @@ export function showStartRecordingNotificationWithCallback(openRecordingDialog:
customActionNameKey: [ 'notify.suggestRecordingAction' ],
customActionHandler: [ () => {
state = getState();
const isModerator = isLocalParticipantModerator(state);
const { recordingService } = state['features/base/config'];
const canBypassDialog = recordingService?.enabled
&& isJwtFeatureEnabled(state, 'recording', isModerator, false);
&& isJwtFeatureEnabled(state, MEET_FEATURES.RECORDING, false);
if (canBypassDialog) {
const options = {

View File

@@ -2,7 +2,6 @@ import { IReduxState } from '../../../app/types';
import { IconSites } from '../../../base/icons/svg';
import { MEET_FEATURES } from '../../../base/jwt/constants';
import { isJwtFeatureEnabled } from '../../../base/jwt/functions';
import { isLocalParticipantModerator } from '../../../base/participants/functions';
import AbstractButton, { IProps as AbstractButtonProps } from '../../../base/toolbox/components/AbstractButton';
import { isInBreakoutRoom } from '../../../breakout-rooms/functions';
import { maybeShowPremiumFeatureDialog } from '../../../jaas/actions';
@@ -129,11 +128,10 @@ export function _mapStateToProps(state: IReduxState, ownProps: IProps) {
// If the containing component provides the visible prop, that is one
// above all, but if not, the button should be autonomous and decide on
// its own to be visible or not.
const isModerator = isLocalParticipantModerator(state);
const liveStreaming = getLiveStreaming(state);
visible = isLiveStreamingButtonVisible({
liveStreamingAllowed: isJwtFeatureEnabled(state, 'livestreaming', isModerator, false),
liveStreamingAllowed: isJwtFeatureEnabled(state, MEET_FEATURES.LIVESTREAMING, false),
liveStreamingEnabled: liveStreaming?.enabled,
isInBreakoutRoom: isInBreakoutRoom(state)
});

View File

@@ -6,8 +6,8 @@ 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 { MEET_FEATURES } from '../../../base/jwt/constants';
import { isJwtFeatureEnabled } from '../../../base/jwt/functions';
import { isLocalParticipantModerator } from '../../../base/participants/functions';
import { authorizeDropbox, updateDropboxToken } from '../../../dropbox/actions';
import { isVpaasMeeting } from '../../../jaas/functions';
import { canAddTranscriber } from '../../../transcribing/functions';
@@ -414,14 +414,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 isModerator = isLocalParticipantModerator(state);
return {
..._abstractMapStateToProps(state),
isVpaas: isVpaasMeeting(state),
_canStartTranscribing: canAddTranscriber(state),
_hideStorageWarning: Boolean(recordingService?.hideStorageWarning),
_renderRecording: isJwtFeatureEnabled(state, 'recording', isModerator, false),
_renderRecording: isJwtFeatureEnabled(state, MEET_FEATURES.RECORDING, false),
_localRecordingAvailable,
_localRecordingEnabled: !localRecording?.disable,
_localRecordingSelfEnabled: !localRecording?.disableSelfRecording,

View File

@@ -2,14 +2,11 @@ import i18next from 'i18next';
import { IReduxState, IStore } from '../app/types';
import { isMobileBrowser } from '../base/environment/utils';
import { MEET_FEATURES } from '../base/jwt/constants';
import { isJwtFeatureEnabled } from '../base/jwt/functions';
import { JitsiRecordingConstants, browser } from '../base/lib-jitsi-meet';
import { getSoundFileSrc } from '../base/media/functions';
import {
getLocalParticipant,
getRemoteParticipants,
isLocalParticipantModerator
} from '../base/participants/functions';
import { getLocalParticipant, getRemoteParticipants } from '../base/participants/functions';
import { registerSound, unregisterSound } from '../base/sounds/actions';
import { isInBreakoutRoom as isInBreakoutRoomF } from '../breakout-rooms/functions';
import { isEnabled as isDropboxEnabled } from '../dropbox/functions';
@@ -203,9 +200,7 @@ export function canStopRecording(state: IReduxState) {
}
if (isCloudRecordingRunning(state) || isRecorderTranscriptionsRunning(state)) {
const isModerator = isLocalParticipantModerator(state);
return isJwtFeatureEnabled(state, 'recording', isModerator, false);
return isJwtFeatureEnabled(state, MEET_FEATURES.RECORDING, false);
}
return false;
@@ -257,7 +252,6 @@ export function getRecordButtonProps(state: IReduxState) {
// If the containing component provides the visible prop, that is one
// above all, but if not, the button should be autonomus and decide on
// its own to be visible or not.
const isModerator = isLocalParticipantModerator(state);
const {
recordingService,
localRecording
@@ -269,7 +263,7 @@ export function getRecordButtonProps(state: IReduxState) {
if (localRecordingEnabled) {
visible = true;
} else if (isJwtFeatureEnabled(state, 'recording', isModerator, false)) {
} else if (isJwtFeatureEnabled(state, MEET_FEATURES.RECORDING, false)) {
visible = recordingEnabled;
}

View File

@@ -1,8 +1,8 @@
import { useSelector } from 'react-redux';
import { IReduxState } from '../app/types';
import { MEET_FEATURES } from '../base/jwt/constants';
import { isJwtFeatureEnabled } from '../base/jwt/functions';
import { isLocalParticipantModerator } from '../base/participants/functions';
import { isInBreakoutRoom } from '../breakout-rooms/functions';
import { getLiveStreaming } from './components/LiveStream/functions';
@@ -45,10 +45,9 @@ export function useRecordingButton() {
*/
export function useLiveStreamingButton() {
const toolbarButtons = useSelector((state: IReduxState) => state['features/toolbox'].toolbarButtons);
const localParticipantIsModerator = useSelector(isLocalParticipantModerator);
const liveStreaming = useSelector(getLiveStreaming);
const liveStreamingAllowed = useSelector((state: IReduxState) =>
isJwtFeatureEnabled(state, 'livestreaming', localParticipantIsModerator, false));
isJwtFeatureEnabled(state, MEET_FEATURES.LIVESTREAMING, false));
const _isInBreakoutRoom = useSelector(isInBreakoutRoom);
if (toolbarButtons?.includes('recording')

View File

@@ -2,9 +2,9 @@ import { AnyAction } from 'redux';
import { IStore } from '../app/types';
import { ENDPOINT_MESSAGE_RECEIVED } from '../base/conference/actionTypes';
import { MEET_FEATURES } from '../base/jwt/constants';
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 { TRANSCRIBER_JOINED } from '../transcribing/actionTypes';
@@ -293,8 +293,7 @@ function _requestingSubtitlesChange(
enabled);
if (enabled && conference?.getTranscriptionStatus() === JitsiMeetJS.constants.transcriptionStatus.OFF) {
const isModerator = isLocalParticipantModerator(state);
const featureAllowed = isJwtFeatureEnabled(getState(), 'transcription', isModerator, false);
const featureAllowed = isJwtFeatureEnabled(getState(), MEET_FEATURES.TRANSCRIPTION, false);
if (featureAllowed) {
conference?.dial(TRANSCRIBER_DIAL_NUMBER)

View File

@@ -94,7 +94,7 @@ export default function Toolbox({
const transcribing = useSelector(isTranscribing);
// Do not convert to selector, it returns new array and will cause re-rendering of toolbox on every action.
const jwtDisabledButtons = getJwtDisabledButtons(transcribing, isModerator, jwt, localParticipant?.features);
const jwtDisabledButtons = getJwtDisabledButtons(transcribing, jwt, localParticipant?.features);
const reactionsButtonEnabled = useSelector(isReactionsButtonEnabled);
const _shouldDisplayReactionsButtons = useSelector(shouldDisplayReactionsButtons);

View File

@@ -27,14 +27,12 @@ export function isAudioMuteButtonDisabled(state: IReduxState) {
* This function is stateless as it returns a new array and may cause re-rendering.
*
* @param {boolean} isTranscribing - Whether there is currently a transcriber in the meeting.
* @param {boolean} isModerator - Whether local participant is moderator.
* @param {string | undefined} jwt - The jwt token.
* @param {ILocalParticipant} localParticipantFeatures - The features of the local participant.
* @returns {string[]} - The disabled by jwt buttons array.
*/
export function getJwtDisabledButtons(
isTranscribing: boolean,
isModerator: boolean,
jwt: string | undefined,
localParticipantFeatures?: IParticipantFeatures) {
const acc = [];
@@ -43,7 +41,6 @@ export function getJwtDisabledButtons(
jwt,
localParticipantFeatures,
feature: 'livestreaming',
ifNoToken: isModerator,
ifNotInFeatures: false
})) {
acc.push('livestreaming');
@@ -53,7 +50,6 @@ export function getJwtDisabledButtons(
jwt,
localParticipantFeatures,
feature: 'transcription',
ifNoToken: isModerator,
ifNotInFeatures: false
})) {
acc.push('closedcaptions');

View File

@@ -73,7 +73,7 @@ export function isAudioSettingsButtonDisabled(state: IReduxState) {
export function isDesktopShareButtonDisabled(state: IReduxState) {
const { muted, unmuteBlocked } = state['features/base/media'].video;
const videoOrShareInProgress = !muted || isScreenMediaShared(state);
const enabledInJwt = isJwtFeatureEnabled(state, MEET_FEATURES.SCREEN_SHARING, true, true);
const enabledInJwt = isJwtFeatureEnabled(state, MEET_FEATURES.SCREEN_SHARING, true);
return !enabledInJwt || (unmuteBlocked && !videoOrShareInProgress);
}

View File

@@ -2,8 +2,8 @@ import i18next from 'i18next';
import { IReduxState } from '../app/types';
import { IConfig } from '../base/config/configType';
import { MEET_FEATURES } from '../base/jwt/constants';
import { isJwtFeatureEnabled } from '../base/jwt/functions';
import { isLocalParticipantModerator } from '../base/participants/functions';
import JITSI_TO_BCP47_MAP from './jitsi-bcp47-map.json';
import logger from './logger';
@@ -77,8 +77,7 @@ export function isRecorderTranscriptionsRunning(state: IReduxState) {
*/
export function canAddTranscriber(state: IReduxState) {
const { transcription } = state['features/base/config'];
const isModerator = isLocalParticipantModerator(state);
const isTranscribingAllowed = isJwtFeatureEnabled(state, 'transcription', isModerator, false);
const isTranscribingAllowed = isJwtFeatureEnabled(state, MEET_FEATURES.TRANSCRIPTION, false);
return Boolean(transcription?.enabled) && isTranscribingAllowed;
}