feat(e2ee) disable e2ee when large number of participants

This commit is contained in:
tmoldovan8x8 2021-09-21 14:00:23 +03:00 committed by GitHub
parent 4cd5be9d8b
commit 5ad98d193a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 188 additions and 22 deletions

View File

@ -213,7 +213,9 @@
"done": "Done",
"e2eeDescription": "End-to-End Encryption is currently EXPERIMENTAL. Please keep in mind that turning on end-to-end encryption will effectively disable server-side provided services such as: phone participation. Also keep in mind that the meeting will only work for people joining from browsers with support for insertable streams.",
"e2eeLabel": "Enable End-to-End Encryption",
"e2eeDisabledDueToMaxModeDescription": "Cannot enable End-to-End Encryption due to large number of participants in the conference.",
"e2eeWarning": "WARNING: Not all participants in this meeting seem to have support for End-to-End encryption. If you enable it they won't be able to see nor hear you.",
"e2eeWillDisableDueToMaxModeDescription": "WARNING: End-to-End Encryption will be automatically disabled if more participants join the conference.",
"enterDisplayName": "Enter your name here",
"embedMeeting": "Embed meeting",
"error": "Error",

View File

@ -5,6 +5,7 @@ import { batch } from 'react-redux';
import UIEvents from '../../../../service/UI/UIEvents';
import { approveParticipant } from '../../av-moderation/actions';
import { toggleE2EE } from '../../e2ee/actions';
import { MAX_MODE } from '../../e2ee/constants';
import { NOTIFICATION_TIMEOUT, showNotification } from '../../notifications';
import { isForceMuted } from '../../participants-pane/functions';
import { CALLING, INVITED } from '../../presence-status';
@ -327,16 +328,20 @@ StateListenerRegistry.register(
/**
* Handles a E2EE enabled status update.
*
* @param {Function} dispatch - The Redux dispatch function.
* @param {Store} store - The redux store.
* @param {Object} conference - The conference for which we got an update.
* @param {string} participantId - The ID of the participant from which we got an update.
* @param {boolean} newValue - The new value of the E2EE enabled status.
* @returns {void}
*/
function _e2eeUpdated({ dispatch }, conference, participantId, newValue) {
function _e2eeUpdated({ getState, dispatch }, conference, participantId, newValue) {
const e2eeEnabled = newValue === 'true';
const { maxMode } = getState()['features/e2ee'];
if (maxMode !== MAX_MODE.THRESHOLD_EXCEEDED || !e2eeEnabled) {
dispatch(toggleE2EE(e2eeEnabled));
}
dispatch(participantUpdated({
conference,

View File

@ -25,3 +25,12 @@ export const SET_EVERYONE_ENABLED_E2EE = 'SET_EVERYONE_ENABLED_E2EE';
* }
*/
export const SET_EVERYONE_SUPPORT_E2EE = 'SET_EVERYONE_SUPPORT_E2EE';
/**
* The type of the action which signals to set new value E2EE maxMode.
*
* {
* type: SET_MAX_MODE
* }
*/
export const SET_MAX_MODE = 'SET_MAX_MODE';

View File

@ -1,6 +1,6 @@
// @flow
import { SET_EVERYONE_ENABLED_E2EE, SET_EVERYONE_SUPPORT_E2EE, TOGGLE_E2EE } from './actionTypes';
import { SET_EVERYONE_ENABLED_E2EE, SET_EVERYONE_SUPPORT_E2EE, SET_MAX_MODE, TOGGLE_E2EE } from './actionTypes';
/**
* Dispatches an action to enable / disable E2EE.
@ -46,3 +46,16 @@ export function setEveryoneSupportE2EE(everyoneSupportE2EE: boolean) {
everyoneSupportE2EE
};
}
/**
* Dispatches an action to set E2EE maxMode.
*
* @param {string} maxMode - The new value.
* @returns {Object}
*/
export function setE2EEMaxMode(maxMode: string) {
return {
type: SET_MAX_MODE,
maxMode
};
}

View File

@ -8,17 +8,23 @@ import { translate } from '../../base/i18n';
import { Switch } from '../../base/react';
import { connect } from '../../base/redux';
import { toggleE2EE } from '../actions';
import { MAX_MODE } from '../constants';
import { doesEveryoneSupportE2EE } from '../functions';
type Props = {
/**
* The resource for the description, computed based on the maxMode and whether the switch is toggled or not.
*/
_descriptionResource: string,
/**
* Custom e2ee labels.
*/
_e2eeLabels: Object,
/**
* Whether E2EE is currently enabled or not.
* Whether the switch is currently enabled or not.
*/
_enabled: boolean,
@ -27,6 +33,11 @@ type Props = {
*/
_everyoneSupportE2EE: boolean,
/**
* Whether E2EE is currently enabled or not.
*/
_toggled: boolean,
/**
* The redux {@code dispatch} function.
*/
@ -43,7 +54,7 @@ type State = {
/**
* True if the switch is toggled on.
*/
enabled: boolean
toggled: boolean
};
/**
@ -59,10 +70,10 @@ class E2EESection extends Component<Props, State> {
* @inheritdoc
*/
static getDerivedStateFromProps(props: Props, state: Object) {
if (props._enabled !== state.enabled) {
if (props._toggled !== state.toggled) {
return {
enabled: props._enabled
toggled: props._toggled
};
}
@ -78,7 +89,7 @@ class E2EESection extends Component<Props, State> {
super(props);
this.state = {
enabled: false
toggled: false
};
// Bind event handlers so they are only bound once for every instance.
@ -92,9 +103,9 @@ class E2EESection extends Component<Props, State> {
* @returns {ReactElement}
*/
render() {
const { _e2eeLabels, _everyoneSupportE2EE, t } = this.props;
const { enabled } = this.state;
const description = _e2eeLabels?.description || t('dialog.e2eeDescription');
const { _descriptionResource, _enabled, _e2eeLabels, _everyoneSupportE2EE, t } = this.props;
const { toggled } = this.state;
const description = _e2eeLabels?.description || t(_descriptionResource);
const label = _e2eeLabels?.label || t('dialog.e2eeLabel');
const warning = _e2eeLabels?.warning || t('dialog.e2eeWarning');
@ -113,9 +124,10 @@ class E2EESection extends Component<Props, State> {
{ label }
</label>
<Switch
disabled = { !_enabled }
id = 'e2ee-section-switch'
onValueChange = { this._onToggle }
value = { enabled } />
value = { toggled } />
</div>
</div>
);
@ -130,10 +142,10 @@ class E2EESection extends Component<Props, State> {
* @returns {void}
*/
_onToggle() {
const newValue = !this.state.enabled;
const newValue = !this.state.toggled;
this.setState({
enabled: newValue
toggled: newValue
});
sendAnalytics(createE2EEEvent(`enabled.${String(newValue)}`));
@ -149,12 +161,28 @@ class E2EESection extends Component<Props, State> {
* @returns {Props}
*/
function mapStateToProps(state) {
const { enabled } = state['features/e2ee'];
const { enabled: e2eeEnabled, maxMode } = state['features/e2ee'];
const { e2eeLabels } = state['features/base/config'];
let descriptionResource = '';
if (e2eeLabels) {
// When e2eeLabels are present, the descriptionResouse is ignored.
descriptionResource = undefined;
} else if (maxMode === MAX_MODE.THRESHOLD_EXCEEDED) {
descriptionResource = 'dialog.e2eeDisabledDueToMaxModeDescription';
} else if (maxMode === MAX_MODE.ENABLED) {
descriptionResource = e2eeEnabled
? 'dialog.e2eeWillDisableDueToMaxModeDescription' : 'dialog.e2eeDisabledDueToMaxModeDescription';
} else {
descriptionResource = 'dialog.e2eeDescription';
}
return {
_descriptionResource: descriptionResource,
_e2eeLabels: e2eeLabels,
_enabled: enabled,
_enabled: maxMode === MAX_MODE.DISABLED || e2eeEnabled,
_toggled: e2eeEnabled,
_everyoneSupportE2EE: doesEveryoneSupportE2EE(state)
};
}

View File

@ -13,3 +13,43 @@ export const E2EE_OFF_SOUND_ID = 'E2EE_OFF_SOUND';
* @type {string}
*/
export const E2EE_ON_SOUND_ID = 'E2EE_ON_SOUND';
/**
* The number of participants after which e2ee maxMode is set to MAX_MODE.ENABLED.
*
* @type {integer}
*/
export const MAX_MODE_LIMIT = 20;
/**
* If the number of participants is greater then MAX_MODE_LIMIT + MAX_MODE_THRESHOLD
* e2ee maxMode is set to MAX_MODE.THRESHOLD_EXCEEDED.
*
* @type {integer}
*/
export const MAX_MODE_THRESHOLD = 5;
export const MAX_MODE = {
/**
* Mode for which the e2ee can be enabled or disabled.
* If e2ee is enabled, e2ee section is enabled with a warning text.
* If e2ee is disabled, e2ee section is disabled with a warning text.
*
* @type {string}
*/
ENABLED: 'max-mode-enabled',
/**
* Mode for which the e2ee and the e2ee section are automatically disabled.
*
* @type {string}
*/
THRESHOLD_EXCEEDED: 'max-mode-threshold-exceeded',
/**
* The default e2ee maxMode, e2ee can be enabled/disabled, e2ee section is enabled.
*
* @type {string}
*/
DISABLED: 'max-mode-disabled'
};

View File

@ -2,6 +2,8 @@
import { getParticipantCount } from '../base/participants/functions';
import { toState } from '../base/redux';
import { MAX_MODE_LIMIT, MAX_MODE_THRESHOLD } from './constants';
/**
* Gets the value of a specific React {@code Component} prop of the currently
* mounted {@link App}.
@ -27,3 +29,29 @@ export function doesEveryoneSupportE2EE(stateful) {
return everyoneSupportE2EE;
}
/**
* Returns true is the number of participants is larger than {@code MAX_MODE_LIMIT}.
*
* @param {Function|Object} stateful - The redux store or {@code getState}
* function.
* @returns {boolean}.
*/
export function isMaxModeReached(stateful) {
const participantCount = getParticipantCount(toState(stateful));
return participantCount >= MAX_MODE_LIMIT;
}
/**
* Returns true is the number of participants is larger than {@code MAX_MODE_LIMIT + MAX_MODE_THREHOLD}.
*
* @param {Function|Object} stateful - The redux store or {@code getState}
* function.
* @returns {boolean}.
*/
export function isMaxModeThresholdReached(stateful) {
const participantCount = getParticipantCount(toState(stateful));
return participantCount >= MAX_MODE_LIMIT + MAX_MODE_THRESHOLD;
}

View File

@ -3,7 +3,7 @@
import { batch } from 'react-redux';
import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../base/app';
import { getCurrentConference } from '../base/conference';
import { CONFERENCE_JOINED, getCurrentConference } from '../base/conference';
import {
getLocalParticipant,
getParticipantById,
@ -18,8 +18,9 @@ import { MiddlewareRegistry, StateListenerRegistry } from '../base/redux';
import { playSound, registerSound, unregisterSound } from '../base/sounds';
import { TOGGLE_E2EE } from './actionTypes';
import { setEveryoneEnabledE2EE, setEveryoneSupportE2EE, toggleE2EE } from './actions';
import { E2EE_OFF_SOUND_ID, E2EE_ON_SOUND_ID } from './constants';
import { setE2EEMaxMode, setEveryoneEnabledE2EE, setEveryoneSupportE2EE, toggleE2EE } from './actions';
import { E2EE_OFF_SOUND_ID, E2EE_ON_SOUND_ID, MAX_MODE } from './constants';
import { isMaxModeReached, isMaxModeThresholdReached } from './functions';
import logger from './logger';
import { E2EE_OFF_SOUND_FILE, E2EE_ON_SOUND_FILE } from './sounds';
@ -46,6 +47,11 @@ MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
dispatch(unregisterSound(E2EE_ON_SOUND_ID));
break;
case CONFERENCE_JOINED:
_updateMaxMode(dispatch, getState);
break;
case PARTICIPANT_UPDATED: {
const { id, e2eeEnabled, e2eeSupported } = action.participant;
const oldParticipant = getParticipantById(getState(), id);
@ -88,7 +94,7 @@ MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
const result = next(action);
const { e2eeEnabled, e2eeSupported, local } = action.participant;
const { everyoneEnabledE2EE } = getState()['features/e2ee'];
const participantCount = getParticipantCount(getState());
const participantCount = getParticipantCount(getState);
// the initial values
if (participantCount === 1) {
@ -116,6 +122,8 @@ MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
dispatch(setEveryoneSupportE2EE(false));
}
_updateMaxMode(dispatch, getState);
return result;
}
@ -165,6 +173,8 @@ MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
});
}
_updateMaxMode(dispatch, getState);
return result;
}
@ -207,3 +217,24 @@ StateListenerRegistry.register(
dispatch(toggleE2EE(false));
}
});
/**
* Sets the maxMode based on the number of participants in the conference.
*
* @param { Dispatch<any>} dispatch - The redux dispatch function.
* @param {Function|Object} getState - The {@code getState} function.
* @private
* @returns {void}
*/
function _updateMaxMode(dispatch, getState) {
const state = getState();
if (isMaxModeThresholdReached(state)) {
dispatch(setE2EEMaxMode(MAX_MODE.THRESHOLD_EXCEEDED));
dispatch(toggleE2EE(false));
} else if (isMaxModeReached(state)) {
dispatch(setE2EEMaxMode(MAX_MODE.ENABLED));
} else {
dispatch(setE2EEMaxMode(MAX_MODE.DISABLED));
}
}

View File

@ -5,11 +5,14 @@ import { ReducerRegistry } from '../base/redux';
import {
SET_EVERYONE_ENABLED_E2EE,
SET_EVERYONE_SUPPORT_E2EE,
SET_MAX_MODE,
TOGGLE_E2EE
} from './actionTypes';
import { MAX_MODE } from './constants';
const DEFAULT_STATE = {
enabled: false
enabled: false,
maxMode: MAX_MODE.DISABLED
};
/**
@ -33,6 +36,13 @@ ReducerRegistry.register('features/e2ee', (state = DEFAULT_STATE, action) => {
everyoneSupportE2EE: action.everyoneSupportE2EE
};
case SET_MAX_MODE: {
return {
...state,
maxMode: action.maxMode
};
}
default:
return state;
}