Token based features (#3075)

* Adds an option to disable features based on token data.

Reverts changes from b84e910086, removes disableDesktopSharing option and an interface_config option.

* Disable recording button based on token features data.

Hide recording if local participant isGuest and roles based on token.
When enableUserRolesBasedOnToken is enabled we were not hiding the record button for guests.

* Adds filtering of jibri iqs and rayo based on features.

Moves feature checking in separate utility function.
Renames utility method.

* Adds a footer text when outbound-call is not feature enabled.

* Fixes comments.
This commit is contained in:
Дамян Минков 2018-06-15 13:10:22 -05:00 committed by GitHub
parent 0cf585860b
commit ac834326e7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 402 additions and 108 deletions

View File

@ -484,8 +484,7 @@ export default {
/**
* Indicates if the desktop sharing functionality has been enabled.
* It takes into consideration {@link isDesktopSharingDisabledByConfig}
* as well as the status returned by
* It takes into consideration the status returned by
* {@link JitsiMeetJS.isDesktopSharingEnabled()}. The latter can be false
* either if the desktop sharing is not supported by the current browser
* or if it was disabled through lib-jitsi-meet specific options (check
@ -493,19 +492,6 @@ export default {
*/
isDesktopSharingEnabled: false,
/**
* Set to <tt>true</tt> if the desktop sharing functionality has been
* explicitly disabled in the config.
*/
isDesktopSharingDisabledByConfig: false,
/**
* The text displayed when the desktop sharing button is disabled through
* the config. The value is set through
* {@link interfaceConfig.DESKTOP_SHARING_BUTTON_DISABLED_TOOLTIP}.
*/
desktopSharingDisabledTooltip: null,
/**
* The local audio track (if any).
* FIXME tracks from redux store should be the single source of truth
@ -720,13 +706,8 @@ export default {
APP.connection = connection = con;
// Desktop sharing related stuff:
this.isDesktopSharingDisabledByConfig
= config.disableDesktopSharing;
this.isDesktopSharingEnabled
= !this.isDesktopSharingDisabledByConfig
&& JitsiMeetJS.isDesktopSharingEnabled();
this.desktopSharingDisabledTooltip
= interfaceConfig.DESKTOP_SHARING_BUTTON_DISABLED_TOOLTIP;
= JitsiMeetJS.isDesktopSharingEnabled();
eventEmitter.emit(
JitsiMeetConferenceEvents.DESKTOP_SHARING_ENABLED_CHANGED,
this.isDesktopSharingEnabled);
@ -1909,6 +1890,14 @@ export default {
JitsiConferenceEvents.PARTICIPANT_PROPERTY_CHANGED,
(participant, name, oldValue, newValue) => {
switch (name) {
case 'features_screen-sharing': {
APP.store.dispatch(participantUpdated({
conference: room,
id: participant.getId(),
features: { 'screen-sharing': true }
}));
break;
}
case 'raisedHand':
APP.store.dispatch(participantUpdated({
conference: room,

View File

@ -142,9 +142,6 @@ var config = {
// Desktop sharing
// Enable / disable desktop sharing
// disableDesktopSharing: false,
// The ID of the jidesha extension for Chrome.
desktopSharingChromeExtId: null,
@ -248,6 +245,9 @@ var config = {
// edit their profile.
enableUserRolesBasedOnToken: false,
// Whether or not some features are checked based on token.
// enableFeaturesBasedOnToken: false,
// Message to show the users. Example: 'The service will be down for
// maintenance at 01:00 AM GMT,
// noticeMessage: '',

View File

@ -95,7 +95,13 @@
}
i.disabled {
cursor: initial
cursor: initial;
color: #3b475c;
}
.disabled i {
cursor: initial;
color: #3b475c;
}
i.disabled:hover {
@ -135,6 +141,10 @@
&.unclickable:hover {
background: inherit;
}
&.disabled {
cursor: initial;
color: #3b475c;
}
}
.beta-tag {

View File

@ -23,6 +23,21 @@
margin: auto;
}
}
.footer-text-wrap {
display: flex;
}
.footer-telephone-icon {
display: flex;
transform: scaleX(-1);
padding-left: 10px;
i {
line-height: 20px;
margin: auto;
}
}
}
}

View File

@ -5,12 +5,6 @@ var interfaceConfig = {
// methods allowing to use variables both in css and js.
DEFAULT_BACKGROUND: '#474747',
/**
* In case the desktop sharing is disabled through the config the button
* will not be hidden, but displayed as disabled with this text us as
* a tooltip.
*/
DESKTOP_SHARING_BUTTON_DISABLED_TOOLTIP: null,
INITIAL_TOOLBAR_TIMEOUT: 20000,
TOOLBAR_TIMEOUT: 4000,
TOOLBAR_ALWAYS_VISIBLE: false,

View File

@ -361,6 +361,10 @@
"muteParticipantTitle": "Mute this member?",
"muteParticipantBody": "You won't be able to unmute them, but they can unmute themselves at any time.",
"muteParticipantButton": "Mute",
"liveStreamingDisabledTooltip": "Start live stream disabled.",
"liveStreamingDisabledForGuestTooltip": "Guests can't start live streaming.",
"recordingDisabledTooltip": "Start recording disabled.",
"recordingDisabledForGuestTooltip": "Guests can't start recordings.",
"remoteControlTitle": "Remote desktop control",
"remoteControlRequestMessage": "Will you allow __user__ to remotely control your desktop?",
"remoteControlShareScreenWarning": "Note that if you press \"Allow\" you will share your screen!",
@ -371,6 +375,8 @@
"remoteControlStopMessage": "The remote control session ended!",
"close": "Close",
"shareYourScreen": "Share your screen",
"shareYourScreenDisabled": "Screen sharing disabled.",
"shareYourScreenDisabledForGuest": "Guests can't screen share.",
"yourEntireScreen": "Your entire screen",
"applicationWindow": "Application window"
},
@ -521,6 +527,7 @@
"countryNotSupported": "We do not support this destination yet.",
"countryReminder": "Calling outside the US? Please make sure you start with the country code!",
"disabled": "You can't invite people.",
"footerText": "Dialing out is disabled.",
"invite": "Invite",
"loading": "Searching for people and phone numbers",
"loadingNumber": "Validating phone number",

View File

@ -188,8 +188,17 @@ function _reportError(msg, err) {
*/
export function sendLocalParticipant(
stateful: Function | Object,
conference: { sendCommand: Function, setDisplayName: Function }) {
const { avatarID, avatarURL, email, name } = getLocalParticipant(stateful);
conference: {
sendCommand: Function,
setDisplayName: Function,
setLocalParticipantProperty: Function }) {
const {
avatarID,
avatarURL,
email,
features,
name
} = getLocalParticipant(stateful);
avatarID && conference.sendCommand(AVATAR_ID_COMMAND, {
value: avatarID
@ -200,5 +209,10 @@ export function sendLocalParticipant(
email && conference.sendCommand(EMAIL_COMMAND, {
value: email
});
if (features && features['screen-sharing'] === 'true') {
conference.setLocalParticipantProperty('features_screen-sharing', true);
}
conference.setDisplayName(name);
}

View File

@ -89,8 +89,6 @@ const WHITELISTED_KEYS = [
'disableAGC',
'disableAP',
'disableAudioLevels',
'disableDesktopSharing',
'disableDesktopSharing',
'disableH264',
'disableHPF',
'disableNS',
@ -106,7 +104,6 @@ const WHITELISTED_KEYS = [
'enableStatsID',
'enableTalkWhileMuted',
'enableTcc',
'enableUserRolesBasedOnToken',
'etherpad_base',
'failICE',
'fileRecordingsEnabled',

View File

@ -138,7 +138,7 @@ function _maybeSetCalleeInfoVisible({ dispatch, getState }, next, action) {
*/
function _overwriteLocalParticipant(
{ dispatch, getState },
{ avatarURL, email, name }) {
{ avatarURL, email, name, features }) {
let localParticipant;
if ((avatarURL || email || name)
@ -157,6 +157,9 @@ function _overwriteLocalParticipant(
if (name) {
newProperties.name = name;
}
if (features) {
newProperties.features = features;
}
dispatch(participantUpdated(newProperties));
}
}
@ -229,7 +232,9 @@ function _setJWT(store, next, action) {
action.server = context.server;
action.user = user;
user && _overwriteLocalParticipant(store, user);
user && _overwriteLocalParticipant(
store, { ...user,
features: context.features });
}
}
} else if (typeof APP === 'undefined') {
@ -281,6 +286,8 @@ function _undoOverwriteLocalParticipant(
if (name === localParticipant.name) {
newProperties.name = undefined;
}
newProperties.features = undefined;
dispatch(participantUpdated(newProperties));
}
}

View File

@ -24,6 +24,12 @@ class MultiSelectAutocomplete extends Component {
*/
defaultValue: PropTypes.array,
/**
* Optional footer to show as a last element in the results.
* Should be of type {content: <some content>}
*/
footer: PropTypes.object,
/**
* Indicates if the component is disabled.
*/
@ -151,6 +157,7 @@ class MultiSelectAutocomplete extends Component {
<div>
<MultiSelectStateless
filterValue = { this.state.filterValue }
footer = { this.props.footer }
icon = { null }
isDisabled = { isDisabled }
isLoading = { this.state.loading }

View File

@ -8,7 +8,8 @@ import { connect } from 'react-redux';
import { createInviteDialogEvent, sendAnalytics } from '../../analytics';
import { Dialog, hideDialog } from '../../base/dialog';
import { translate } from '../../base/i18n';
import { translate, translateToHTML } from '../../base/i18n';
import { getLocalParticipant } from '../../base/participants';
import { MultiSelectAutocomplete } from '../../base/react';
import { invite } from '../actions';
@ -39,6 +40,12 @@ class AddPeopleDialog extends Component<*, *> {
*/
_dialOutAuthUrl: PropTypes.string,
/**
* Whether to show a footer text after the search results
* as a last element.
*/
_footerTextEnabled: PropTypes.bool,
/**
* The JWT token.
*/
@ -168,11 +175,15 @@ class AddPeopleDialog extends Component<*, *> {
* @returns {ReactElement}
*/
render() {
const { addPeopleEnabled, dialOutEnabled, t } = this.props;
const { _footerTextEnabled,
addPeopleEnabled,
dialOutEnabled,
t } = this.props;
let isMultiSelectDisabled = this.state.addToCallInProgress || false;
let placeholder;
let loadingMessage;
let noMatches;
let footerText;
if (addPeopleEnabled && dialOutEnabled) {
loadingMessage = 'addPeople.loading';
@ -192,6 +203,19 @@ class AddPeopleDialog extends Component<*, *> {
placeholder = 'addPeople.disabled';
}
if (_footerTextEnabled) {
footerText = {
content: <div className = 'footer-text-wrap'>
<div>
<span className = 'footer-telephone-icon'>
<i className = 'icon-telephone' />
</span>
</div>
{ translateToHTML(t, 'addPeople.footerText') }
</div>
};
}
return (
<Dialog
okDisabled = { this._isAddDisabled() }
@ -202,6 +226,7 @@ class AddPeopleDialog extends Component<*, *> {
<div className = 'add-people-form-wrap'>
{ this._renderErrorMessage() }
<MultiSelectAutocomplete
footer = { footerText }
isDisabled = { isMultiSelectDisabled }
loadingMessage = { t(loadingMessage) }
noMatchesFound = { t(noMatches) }
@ -525,12 +550,23 @@ class AddPeopleDialog extends Component<*, *> {
function _mapStateToProps(state) {
const {
dialOutAuthUrl,
enableFeaturesBasedOnToken,
peopleSearchQueryTypes,
peopleSearchUrl
} = state['features/base/config'];
let footerTextEnabled = false;
if (enableFeaturesBasedOnToken) {
const { features = {} } = getLocalParticipant(state);
if (String(features['outbound-call']) !== 'true') {
footerTextEnabled = true;
}
}
return {
_dialOutAuthUrl: dialOutAuthUrl,
_footerTextEnabled: footerTextEnabled,
_jwt: state['features/base/jwt'].jwt,
_peopleSearchQueryTypes: peopleSearchQueryTypes,
_peopleSearchUrl: peopleSearchUrl

View File

@ -1,3 +1,4 @@
import Tooltip from '@atlaskit/tooltip';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
@ -8,6 +9,16 @@ import React, { Component } from 'react';
* @extends Component
*/
class OverflowMenuItem extends Component {
/**
* Default values for {@code OverflowMenuItem} component's properties.
*
* @static
*/
static defaultProps = {
tooltipPosition: 'left',
disabled: false
};
/**
* {@code OverflowMenuItem} component's property types.
*
@ -20,6 +31,11 @@ class OverflowMenuItem extends Component {
*/
accessibilityLabel: PropTypes.string,
/**
* Whether menu item is disabled or not.
*/
disabled: PropTypes.bool,
/**
* The icon class to use for displaying an icon before the link text.
*/
@ -33,7 +49,18 @@ class OverflowMenuItem extends Component {
/**
* The text to display in the {@code OverflowMenuItem}.
*/
text: PropTypes.string
text: PropTypes.string,
/**
* The text to display in the tooltip.
*/
tooltip: PropTypes.string,
/**
* From which direction the tooltip should appear, relative to the
* button.
*/
tooltipPosition: PropTypes.string
};
/**
@ -43,15 +70,25 @@ class OverflowMenuItem extends Component {
* @returns {ReactElement}
*/
render() {
let className = 'overflow-menu-item';
className += this.props.disabled ? ' disabled' : '';
return (
<li
aria-label = { this.props.accessibilityLabel }
className = 'overflow-menu-item'
onClick = { this.props.onClick }>
className = { className }
onClick = { this.props.disabled ? null : this.props.onClick }>
<span className = 'overflow-menu-item-icon'>
<i className = { this.props.icon } />
</span>
{ this.props.text }
{ this.props.tooltip
? <Tooltip
content = { this.props.tooltip }
position = { this.props.tooltipPosition }>
<span>{ this.props.text }</span>
</Tooltip>
: this.props.text }
</li>
);
}

View File

@ -1,39 +1,66 @@
// @flow
import Tooltip from '@atlaskit/tooltip';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { translate } from '../../../base/i18n';
/**
* The type of the React {@code Component} props of
* {@link OverflowMenuLiveStreamingItem}.
*/
type Props = {
/**
* The callback to invoke when {@code OverflowMenuLiveStreamingItem} is
* clicked.
*/
onClick: Function,
/**
* The current live streaming session, if any.
*/
session: ?Object,
/**
* Invoked to obtain translated strings.
*/
t: Function
};
/**
* React {@code Component} for starting or stopping a live streaming of the
* current conference and displaying the current state of live streaming.
*
* @extends Component
*/
class OverflowMenuLiveStreamingItem extends Component<Props> {
class OverflowMenuLiveStreamingItem extends Component {
/**
* Default values for {@code OverflowMenuLiveStreamingItem}
* component's properties.
*
* @static
*/
static defaultProps = {
tooltipPosition: 'left',
disabled: false
};
/**
* The type of the React {@code Component} props of
* {@link OverflowMenuLiveStreamingItem}.
*/
static propTypes = {
/**
* Whether menu item is disabled or not.
*/
disabled: PropTypes.bool,
/**
* The callback to invoke when {@code OverflowMenuLiveStreamingItem} is
* clicked.
*/
onClick: Function,
/**
* The current live streaming session, if any.
*/
session: PropTypes.object,
/**
* Invoked to obtain translated strings.
*/
t: Function,
/**
* The text to display in the tooltip.
*/
tooltip: PropTypes.string,
/**
* From which direction the tooltip should appear, relative to the
* button.
*/
tooltipPosition: PropTypes.string
};
/**
* Implements React's {@link Component#render()}.
*
@ -41,26 +68,43 @@ class OverflowMenuLiveStreamingItem extends Component<Props> {
* @returns {ReactElement}
*/
render() {
const { onClick, session, t } = this.props;
const { disabled, onClick, session, t, tooltip, tooltipPosition }
= this.props;
const translationKey = session
? 'dialog.stopLiveStreaming'
: 'dialog.startLiveStreaming';
return (
<li
aria-label = { t('dialog.accessibilityLabel.liveStreaming') }
className = 'overflow-menu-item'
onClick = { onClick }>
<span className = 'overflow-menu-item-icon'>
<i className = 'icon-public' />
</span>
let className = 'overflow-menu-item';
className += disabled ? ' disabled' : '';
const button = (// eslint-disable-line no-extra-parens
<span>
<span className = 'profile-text'>
{ t(translationKey) }
</span>
<span className = 'beta-tag'>
{ t('recording.beta') }
</span>
</span>
);
return (
<li
aria-label = { t('dialog.accessibilityLabel.liveStreaming') }
className = { className }
onClick = { disabled ? null : onClick }>
<span className = 'overflow-menu-item-icon'>
<i className = 'icon-public' />
</span>
{ tooltip
? <Tooltip
content = { tooltip }
position = { tooltipPosition }>
{ button }
</Tooltip>
: button }
</li>
);
}

View File

@ -13,8 +13,9 @@ import { openDialog } from '../../../base/dialog';
import { translate } from '../../../base/i18n';
import { JitsiRecordingConstants } from '../../../base/lib-jitsi-meet';
import {
PARTICIPANT_ROLE,
getLocalParticipant,
getParticipants,
isLocalParticipantModerator,
participantUpdated
} from '../../../base/participants';
import { getLocalVideoTrack, toggleScreensharing } from '../../../base/tracks';
@ -74,9 +75,10 @@ type Props = {
_conference: Object,
/**
* Whether or not desktopsharing was explicitly configured to be disabled.
* The tooltip key to use when screensharing is disabled. Or undefined
* if non to be shown and the button to be hidden.
*/
_desktopSharingDisabledByConfig: boolean,
_desktopSharingDisabledTooltipKey: boolean,
/**
* Whether or not screensharing is initialized.
@ -103,6 +105,12 @@ type Props = {
*/
_feedbackConfigured: boolean,
/**
* The tooltip key to use when file recording is disabled. Or undefined
* if non to be shown and the button to be hidden.
*/
_fileRecordingsDisabledTooltipKey: boolean,
/**
* Whether or not the file recording feature is enabled for use.
*/
@ -129,6 +137,12 @@ type Props = {
*/
_isGuest: boolean,
/**
* The tooltip key to use when live streaming is disabled. Or undefined
* if non to be shown and the button to be hidden.
*/
_liveStreamingDisabledTooltipKey: boolean,
/**
* Whether or not the live streaming feature is enabled for use.
*/
@ -925,17 +939,14 @@ class Toolbox extends Component<Props> {
*/
_renderDesktopSharingButton() {
const {
_desktopSharingDisabledByConfig,
_desktopSharingEnabled,
_desktopSharingDisabledTooltipKey,
_screensharing,
t
} = this.props;
const disabledTooltipText
= interfaceConfig.DESKTOP_SHARING_BUTTON_DISABLED_TOOLTIP;
const showDisabledTooltip
= disabledTooltipText && _desktopSharingDisabledByConfig;
const visible = _desktopSharingEnabled || showDisabledTooltip;
const visible
= _desktopSharingEnabled || _desktopSharingDisabledTooltipKey;
if (!visible) {
return null;
@ -944,9 +955,9 @@ class Toolbox extends Component<Props> {
const classNames = `icon-share-desktop ${
_screensharing ? 'toggled' : ''} ${
_desktopSharingEnabled ? '' : 'disabled'}`;
const tooltip = showDisabledTooltip
? disabledTooltipText
: t('dialog.shareYourScreen');
const tooltip = t(
_desktopSharingEnabled
? 'dialog.shareYourScreen' : _desktopSharingDisabledTooltipKey);
return (
<ToolbarButton
@ -969,9 +980,11 @@ class Toolbox extends Component<Props> {
_editingDocument,
_etherpadInitialized,
_feedbackConfigured,
_fileRecordingsDisabledTooltipKey,
_fileRecordingsEnabled,
_fullScreen,
_isGuest,
_liveStreamingDisabledTooltipKey,
_liveStreamingEnabled,
_liveStreamingSession,
_sharingVideo,
@ -1000,13 +1013,15 @@ class Toolbox extends Component<Props> {
text = { _fullScreen
? t('toolbar.exitFullScreen')
: t('toolbar.enterFullScreen') } />,
_liveStreamingEnabled
(_liveStreamingEnabled || _liveStreamingDisabledTooltipKey)
&& this._shouldShowButton('livestreaming')
&& <OverflowMenuLiveStreamingItem
disabled = { !_liveStreamingEnabled }
key = 'livestreaming'
onClick = { this._onToolbarToggleLiveStreaming }
session = { _liveStreamingSession } />,
_fileRecordingsEnabled
session = { _liveStreamingSession }
tooltip = { t(_liveStreamingDisabledTooltipKey) } />,
(_fileRecordingsEnabled || _fileRecordingsDisabledTooltipKey)
&& this._shouldShowButton('recording')
&& this._renderRecordingButton(),
this._shouldShowButton('sharedvideo')
@ -1070,7 +1085,11 @@ class Toolbox extends Component<Props> {
* @returns {ReactElement|null}
*/
_renderRecordingButton() {
const { _fileRecordingSession, t } = this.props;
const {
_fileRecordingSession,
_fileRecordingsDisabledTooltipKey,
_fileRecordingsEnabled,
t } = this.props;
const translationKey = _fileRecordingSession
? 'dialog.stopRecording'
@ -1080,10 +1099,12 @@ class Toolbox extends Component<Props> {
<OverflowMenuItem
accessibilityLabel =
{ t('toolbar.accessibilityLabel.recording') }
disabled = { !_fileRecordingsEnabled }
icon = 'icon-camera-take-picture'
key = 'recording'
onClick = { this._onToolbarToggleRecording }
text = { t(translationKey) } />
text = { t(translationKey) }
tooltip = { t(_fileRecordingsDisabledTooltipKey) } />
);
}
@ -1111,15 +1132,14 @@ class Toolbox extends Component<Props> {
* @returns {{}}
*/
function _mapStateToProps(state) {
const {
conference,
desktopSharingEnabled
} = state['features/base/conference'];
const { conference } = state['features/base/conference'];
let { desktopSharingEnabled } = state['features/base/conference'];
const {
callStatsID,
disableDesktopSharing,
iAmRecorder
} = state['features/base/config'];
let {
fileRecordingsEnabled,
iAmRecorder,
liveStreamingEnabled
} = state['features/base/config'];
const sharedVideoStatus = state['features/shared-video'].status;
@ -1133,15 +1153,77 @@ function _mapStateToProps(state) {
} = state['features/toolbox'];
const localParticipant = getLocalParticipant(state);
const localVideo = getLocalVideoTrack(state['features/base/tracks']);
const isModerator = localParticipant.role === PARTICIPANT_ROLE.MODERATOR;
const addPeopleEnabled = isAddPeopleEnabled(state);
const dialOutEnabled = isDialOutEnabled(state);
let desktopSharingDisabledTooltipKey;
let fileRecordingsDisabledTooltipKey;
let liveStreamingDisabledTooltipKey;
fileRecordingsEnabled
= isLocalParticipantModerator(state) && fileRecordingsEnabled;
liveStreamingEnabled
= isLocalParticipantModerator(state) && liveStreamingEnabled;
if (state['features/base/config'].enableFeaturesBasedOnToken) {
// we enable desktop sharing if any participant already have this
// feature enabled
desktopSharingEnabled = getParticipants(state)
.find(({ features = {} }) =>
String(features['screen-sharing']) === 'true') !== undefined;
// we want to show button and tooltip
if (state['features/base/jwt'].isGuest) {
desktopSharingDisabledTooltipKey
= 'dialog.shareYourScreenDisabledForGuest';
} else {
desktopSharingDisabledTooltipKey
= 'dialog.shareYourScreenDisabled';
}
// we enable recording if the local participant have this
// feature enabled
const { features = {} } = localParticipant;
const { isGuest } = state['features/base/jwt'];
fileRecordingsEnabled
= fileRecordingsEnabled && String(features.recording) === 'true';
// if the feature is disabled on purpose, do no show it, no tooltip
if (!fileRecordingsEnabled
&& String(features.recording) !== 'disabled') {
// button and tooltip
if (isGuest) {
fileRecordingsDisabledTooltipKey
= 'dialog.recordingDisabledForGuestTooltip';
} else {
fileRecordingsDisabledTooltipKey
= 'dialog.recordingDisabledTooltip';
}
}
liveStreamingEnabled
= liveStreamingEnabled && String(features.livestreaming) === 'true';
// if the feature is disabled on purpose, do no show it, no tooltip
if (!liveStreamingEnabled
&& String(features.livestreaming) !== 'disabled') {
// button and tooltip
if (isGuest) {
liveStreamingDisabledTooltipKey
= 'dialog.liveStreamingDisabledForGuestTooltip';
} else {
liveStreamingDisabledTooltipKey
= 'dialog.liveStreamingDisabledTooltip';
}
}
}
return {
_chatOpen: current === 'chat_container',
_conference: conference,
_desktopSharingEnabled: desktopSharingEnabled,
_desktopSharingDisabledByConfig: disableDesktopSharing,
_desktopSharingDisabledTooltipKey: desktopSharingDisabledTooltipKey,
_dialog: Boolean(state['features/base/dialog'].component),
_editingDocument: Boolean(state['features/etherpad'].editing),
_etherpadInitialized: Boolean(state['features/etherpad'].initialized),
@ -1149,11 +1231,13 @@ function _mapStateToProps(state) {
_hideInviteButton:
iAmRecorder || (!addPeopleEnabled && !dialOutEnabled),
_isGuest: state['features/base/jwt'].isGuest,
_fileRecordingsEnabled: isModerator && fileRecordingsEnabled,
_fileRecordingsDisabledTooltipKey: fileRecordingsDisabledTooltipKey,
_fileRecordingsEnabled: fileRecordingsEnabled,
_fileRecordingSession:
getActiveSession(state, JitsiRecordingConstants.mode.FILE),
_fullScreen: fullScreen,
_liveStreamingEnabled: isModerator && liveStreamingEnabled,
_liveStreamingDisabledTooltipKey: liveStreamingDisabledTooltipKey,
_liveStreamingEnabled: liveStreamingEnabled,
_liveStreamingSession:
getActiveSession(state, JitsiRecordingConstants.mode.STREAM),
_localParticipantID: localParticipant.id,

View File

@ -0,0 +1,26 @@
local st = require "util.stanza";
local is_feature_allowed = module:require "util".is_feature_allowed;
-- filters jibri iq in case of requested from jwt authenticated session that
-- has features in the user context, but without feature for recording
module:hook("pre-iq/full", function(event)
local stanza = event.stanza;
if stanza.name == "iq" then
local jibri = stanza:get_child('jibri', 'http://jitsi.org/protocol/jibri');
if jibri then
local session = event.origin;
local token = session.auth_token;
if jibri.attr.action == 'start'
and (token == nil
or not is_feature_allowed(session,
(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
end
end
end);

View File

@ -2,6 +2,7 @@ local st = require "util.stanza";
local token_util = module:require "token/util".new(module);
local room_jid_match_rewrite = module:require "util".room_jid_match_rewrite;
local is_feature_allowed = module:require "util".is_feature_allowed;
-- no token configuration but required
if token_util == nil then
@ -10,6 +11,8 @@ if token_util == nil then
end
-- filters rayo iq in case of requested from not jwt authenticated sessions
-- or if the session has features in user context and it doesn't mention
-- feature "outbound-call" to be enabled
module:hook("pre-iq/full", function(event)
local stanza = event.stanza;
if stanza.name == "iq" then
@ -31,8 +34,11 @@ module:hook("pre-iq/full", function(event)
if token == nil
or roomName == nil
or not token_util:verify_room(
session, room_jid_match_rewrite(roomName)) then
or not token_util:verify_room(session, room_jid_match_rewrite(roomName))
or not is_feature_allowed(session,
(dial.attr.to == 'jitsi_meet_transcribe' and 'transcription'
or 'outbound-call'))
then
module:log("info",
"Filtering stanza dial, stanza:%s", tostring(stanza));
session.send(st.error_reply(stanza, "auth", "forbidden"));

View File

@ -235,6 +235,7 @@ end
-- session.jitsi_meet_domain - the domain name value from the token
-- session.jitsi_meet_context_user - the user details from the token
-- session.jitsi_meet_context_group - the group value from the token
-- session.jitsi_meet_context_features - the features value from the token
-- @param session the current session
-- @return false and error
function Util:process_and_verify_token(session)
@ -285,6 +286,11 @@ function Util:process_and_verify_token(session)
-- Binds any group details to the session
session.jitsi_meet_context_group = claims["context"]["group"];
end
if claims["context"]["features"] ~= nil then
-- Binds any features details to the session
session.jitsi_meet_context_features = claims["context"]["features"];
end
end
return true;
else

View File

@ -133,7 +133,22 @@ function update_presence_identity(
"Presence with identity inserted %s", tostring(stanza))
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(session, feature)
if (session.jitsi_meet_context_features == nil
or session.jitsi_meet_context_features[feature] == "true") then
return true;
else
return false;
end
end
return {
is_feature_allowed = is_feature_allowed;
get_room_from_jid = get_room_from_jid;
wrap_async_run = wrap_async_run;
room_jid_match_rewrite = room_jid_match_rewrite;