Compare commits

...

10 Commits

Author SHA1 Message Date
damencho
b022ce60ac fix: Optimizes hot paths in prosody modules, string comparisons. 2020-11-06 12:55:02 -06:00
damencho
20ce38bd4c feat: Show cc button for ongoing transcribed meetings for guests 2020-11-06 08:12:59 -06:00
damencho
c4ba97e87c feat: Drop lockRoomGuestEnabled. 2020-11-06 08:12:59 -06:00
damencho
4b8aae90e0 feat: Drop HIDE_KICK_BUTTON_FOR_GUESTS setting.
The main config contains disableRemoteMute and remoteVideoMenu: { disableKick: true} options, which can be used.
2020-11-06 08:12:59 -06:00
damencho
c2539bf615 feat: Drop buttons tooltips specific to guests. 2020-11-06 08:12:59 -06:00
damencho
4fdd4b66f7 fix: Hide copy password if it is not available. Fixes #7783 2020-11-06 08:12:59 -06:00
damencho
9fa29d7353 feat: Profile tab does not depend on isGuest.
Introduced a config property to disable profile.
2020-11-06 08:12:59 -06:00
damencho
c14f639639 feat: Drops SHOW_JITSI_WATERMARK_FOR_GUESTS and SHOW_WATERMARK_FOR_GUESTS. 2020-11-06 08:12:59 -06:00
damencho
c007477ee9 fix: Show livestream button only for moderators. 2020-11-06 08:12:59 -06:00
Andrei Bora
50997ae6ac Stringify boolean values from jwt user context 2020-11-06 06:15:45 -06:00
26 changed files with 91 additions and 158 deletions

View File

@@ -359,17 +359,12 @@ var config = {
// Default language for the user interface.
// defaultLanguage: 'en',
// If true all users without a token will be considered guests and all users
// with token will be considered non-guests. Only guests will be allowed to
// edit their profile.
enableUserRolesBasedOnToken: false,
// Disables profile and the edit of all fields from the profile settings (display name and email)
// disableProfile: false,
// Whether or not some features are checked based on token.
// enableFeaturesBasedOnToken: false,
// Enable lock room for all moderators, even when userRolesBasedOnToken is enabled and participants are guests.
// lockRoomGuestEnabled: false,
// When enabled the password used for locking a room is restricted to up to the number of digits specified
// roomPasswordNumberOfDigits: 10,
// default: roomPasswordNumberOfDigits: false,

View File

@@ -186,7 +186,6 @@ var interfaceConfig = {
SHOW_JITSI_WATERMARK: true,
SHOW_POWERED_BY: false,
SHOW_PROMOTIONAL_CLOSE_PAGE: false,
SHOW_WATERMARK_FOR_GUESTS: true, // if watermark is disabled by default, it can be shown only for guests
/*
* If indicated some of the error dialogs may point to the support URL for
@@ -240,11 +239,6 @@ var interfaceConfig = {
*/
VIDEO_QUALITY_LABEL_DISABLED: false,
/**
* When enabled, the kick participant button will not be presented for users without a JWT
*/
// HIDE_KICK_BUTTON_FOR_GUESTS: false,
/**
* How many columns the tile view can expand to. The respected range is
* between 1 and 5.

View File

@@ -220,7 +220,6 @@
"kickTitle": "Ouch! {{participantDisplayName}} kicked you out of the meeting",
"liveStreaming": "Live Streaming",
"liveStreamingDisabledBecauseOfActiveRecordingTooltip": "Not possible while recording is active",
"liveStreamingDisabledForGuestTooltip": "Guests can't start live streaming.",
"liveStreamingDisabledTooltip": "Start live stream disabled.",
"lockMessage": "Failed to lock the conference.",
"lockRoom": "Add meeting $t(lockRoomPasswordUppercase)",
@@ -255,7 +254,6 @@
"readMore": "more",
"recording": "Recording",
"recordingDisabledBecauseOfActiveLiveStreamingTooltip": "Not possible while a live stream is active",
"recordingDisabledForGuestTooltip": "Guests can't start recordings.",
"recordingDisabledTooltip": "Start recording disabled.",
"rejoinNow": "Rejoin now",
"remoteControlAllowedMessage": "{{user}} accepted your remote control request!",
@@ -287,7 +285,6 @@
"shareVideoTitle": "Share a video",
"shareYourScreen": "Share your screen",
"shareYourScreenDisabled": "Screen sharing disabled.",
"shareYourScreenDisabledForGuest": "Guests can't screen share.",
"startLiveStreaming": "Start live stream",
"startRecording": "Start recording",
"startRemoteControlErrorMessage": "An error occurred while trying to start the remote control session!",

View File

@@ -104,7 +104,7 @@ export default class LocalVideo extends SmallVideo {
}
this._renderDisplayName({
allowEditing: APP.store.getState()['features/base/jwt'].isGuest,
allowEditing: !config.disableProfile,
displayNameSuffix: interfaceConfig.DEFAULT_LOCAL_DISPLAY_NAME,
elementID: 'localDisplayName',
participantID: this.id

View File

@@ -298,13 +298,13 @@ export function maybeRedirectToWelcomePage(options: Object = {}) {
return;
}
const { isGuest, jwt } = getState()['features/base/jwt'];
const { jwt } = getState()['features/base/jwt'];
let hashParam;
// save whether current user is guest or not, and pass auth token,
// before navigating to close page
window.sessionStorage.setItem('guest', isGuest);
window.sessionStorage.setItem('guest', !jwt);
window.sessionStorage.setItem('jwt', jwt);
let path = 'close.html';

View File

@@ -85,6 +85,7 @@ export default [
'disableInviteFunctions',
'disableLocalVideoFlip',
'disableNS',
'disableProfile',
'disableRemoteControl',
'disableRemoteMute',
'disableRtx',

View File

@@ -1,7 +1,5 @@
/* @flow */
import jwtDecode from 'jwt-decode';
import { parseURLParams } from '../util';
/**
@@ -24,7 +22,7 @@ export function parseJWTFromURLParams(url: URL = window.location) {
* @returns {string}
*/
export function getJwtName(state: Object) {
const jwtData = jwtDecode(state['features/base/jwt'].jwt);
const { user } = state['features/base/jwt'];
return jwtData?.context?.user?.name || '';
return user?.name || '';
}

View File

@@ -29,10 +29,7 @@ MiddlewareRegistry.register(store => next => action => {
case SET_CONFIG:
case SET_LOCATION_URL:
// XXX The JSON Web Token (JWT) is not the only piece of state that we
// have decided to store in the feature jwt, there is isGuest as well
// which depends on the states of the features base/config and jwt. So
// the JSON Web Token comes from the conference/room's URL and isGuest
// needs a recalculation upon SET_CONFIG as well.
// have decided to store in the feature jwt
return _setConfigOrLocationURL(store, next, action);
case SET_JWT:
@@ -128,12 +125,6 @@ function _setJWT(store, next, action) {
if (!Object.keys(actionPayload).length) {
if (jwt) {
const {
enableUserRolesBasedOnToken
} = store.getState()['features/base/config'];
action.isGuest = !enableUserRolesBasedOnToken;
let jwtPayload;
try {

View File

@@ -4,24 +4,6 @@ import { equals, ReducerRegistry } from '../redux';
import { SET_JWT } from './actionTypes';
/**
* The default/initial redux state of the feature jwt.
*
* @private
* @type {{
* isGuest: boolean
* }}
*/
const DEFAULT_STATE = {
/**
* The indicator which determines whether the local participant is a guest
* in the conference.
*
* @type {boolean}
*/
isGuest: true
};
/**
* Reduces redux actions which affect the JSON Web Token (JWT) stored in the
* redux store.
@@ -33,13 +15,12 @@ const DEFAULT_STATE = {
*/
ReducerRegistry.register(
'features/base/jwt',
(state = DEFAULT_STATE, action) => {
(state = {}, action) => {
switch (action.type) {
case SET_JWT: {
// eslint-disable-next-line no-unused-vars
const { type, ...payload } = action;
const nextState = {
...DEFAULT_STATE,
...payload
};

View File

@@ -298,12 +298,9 @@ export function isIconUrl(icon: ?string | ?Object) {
*
* @param {Object|Function} stateful - Object or function that can be resolved
* to the Redux state.
* @param {?boolean} ignoreToken - When true we ignore the token check.
* @returns {boolean}
*/
export function isLocalParticipantModerator(
stateful: Object | Function,
ignoreToken: ?boolean = false) {
export function isLocalParticipantModerator(stateful: Object | Function) {
const state = toState(stateful);
const localParticipant = getLocalParticipant(state);
@@ -311,11 +308,7 @@ export function isLocalParticipantModerator(
return false;
}
return (
localParticipant.role === PARTICIPANT_ROLE.MODERATOR
&& (ignoreToken
|| !state['features/base/config'].enableUserRolesBasedOnToken
|| !state['features/base/jwt'].isGuest));
return localParticipant.role === PARTICIPANT_ROLE.MODERATOR;
}
/**

View File

@@ -225,7 +225,6 @@ class Watermarks extends Component<Props, State> {
* @returns {Props}
*/
function _mapStateToProps(state, ownProps) {
const { isGuest } = state['features/base/jwt'];
const {
customizationReady,
customizationFailed,
@@ -239,12 +238,11 @@ function _mapStateToProps(state, ownProps) {
DEFAULT_LOGO_URL,
JITSI_WATERMARK_LINK,
SHOW_JITSI_WATERMARK,
SHOW_JITSI_WATERMARK_FOR_GUESTS,
filmStripOnly
} = interfaceConfig;
let _showJitsiWatermark = (!filmStripOnly
&& (customizationReady && !customizationFailed)
&& (SHOW_JITSI_WATERMARK || (isGuest && SHOW_JITSI_WATERMARK_FOR_GUESTS)))
&& SHOW_JITSI_WATERMARK)
|| !isValidRoom;
let _logoUrl = logoImageUrl;
let _logoLink = logoClickUrl;

View File

@@ -352,7 +352,7 @@ export function invitePeopleAndChatRooms( // eslint-disable-line max-params
export function isAddPeopleEnabled(state: Object): boolean {
const { peopleSearchUrl } = state['features/base/config'];
return !isGuest(state) && Boolean(peopleSearchUrl);
return state['features/base/jwt'].jwt && Boolean(peopleSearchUrl);
}
/**
@@ -368,16 +368,6 @@ export function isDialOutEnabled(state: Object): boolean {
&& conference && conference.isSIPCallingSupported();
}
/**
* Determines if the current user is guest or not.
*
* @param {Object} state - Current state.
* @returns {boolean}
*/
export function isGuest(state: Object): boolean {
return state['features/base/jwt'].isGuest;
}
/**
* Checks whether a string looks like it could be for a phone number.
*

View File

@@ -3,7 +3,10 @@
import { openDialog } from '../../../base/dialog';
import { IconLiveStreaming } from '../../../base/icons';
import { JitsiRecordingConstants } from '../../../base/lib-jitsi-meet';
import { getLocalParticipant } from '../../../base/participants';
import {
getLocalParticipant,
isLocalParticipantModerator
} from '../../../base/participants';
import { AbstractButton, type AbstractButtonProps } from '../../../base/toolbox/components';
import { getActiveSession } from '../../functions';
@@ -125,13 +128,14 @@ export function _mapStateToProps(state: Object, ownProps: Props) {
// 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 {
enableFeaturesBasedOnToken,
liveStreamingEnabled
} = state['features/base/config'];
const { features = {} } = getLocalParticipant(state);
visible = liveStreamingEnabled;
visible = isModerator && liveStreamingEnabled;
if (enableFeaturesBasedOnToken) {
visible = visible && String(features.livestreaming) === 'true';
@@ -140,13 +144,7 @@ export function _mapStateToProps(state: Object, ownProps: Props) {
if (!visible && !_disabled) {
_disabled = true;
visible = true;
// button and tooltip
if (state['features/base/jwt'].isGuest) {
_tooltip = 'dialog.liveStreamingDisabledForGuestTooltip';
} else {
_tooltip = 'dialog.liveStreamingDisabledTooltip';
}
_tooltip = 'dialog.liveStreamingDisabledTooltip';
}
}
}

View File

@@ -153,13 +153,7 @@ export function _mapStateToProps(state: Object, ownProps: Props): Object {
if (!visible && !_disabled) {
_disabled = true;
visible = true;
// button and tooltip
if (state['features/base/jwt'].isGuest) {
_tooltip = 'dialog.recordingDisabledForGuestTooltip';
} else {
_tooltip = 'dialog.recordingDisabledTooltip';
}
_tooltip = 'dialog.recordingDisabledTooltip';
}
}
}

View File

@@ -4,7 +4,6 @@ import React from 'react';
import { translate } from '../../../base/i18n';
import { IconKick } from '../../../base/icons';
import { connect } from '../../../base/redux';
import AbstractKickButton, {
type Props
} from '../AbstractKickButton';
@@ -42,11 +41,7 @@ class KickButton extends AbstractKickButton {
* @returns {ReactElement}
*/
render() {
const { participantID, t, visible } = this.props;
if (!visible) {
return null;
}
const { participantID, t } = this.props;
return (
<RemoteVideoMenuButton
@@ -61,22 +56,4 @@ class KickButton extends AbstractKickButton {
_handleClick: () => void
}
/**
* Maps (parts of) the redux state to {@link KickButton}'s React {@code Component}
* props.
*
* @param {Object} state - The redux store/state.
* @private
* @returns {Object}
*/
function _mapStateToProps(state: Object) {
const shouldHide = interfaceConfig.HIDE_KICK_BUTTON_FOR_GUESTS && state['features/base/jwt'].isGuest;
return {
visible: !shouldHide
};
}
export default translate(connect(_mapStateToProps)(KickButton));
export default translate(KickButton);

View File

@@ -154,9 +154,15 @@ function PasswordSection({
<a
className = 'remove-password'
onClick = { onPasswordRemove }>{ t('dialog.Remove') }</a>
<a
className = 'copy-password'
onClick = { onPasswordCopy }>{ t('dialog.copy') }</a>
{
// There are cases like lobby and grant moderator when password is not available
password ? <>
<a
className = 'copy-password'
onClick = { onPasswordCopy }>{ t('dialog.copy') }</a>
</> : null
}
</>
);
}

View File

@@ -125,13 +125,10 @@ function mapStateToProps(state) {
locked,
password
} = state['features/base/conference'];
const {
lockRoomGuestEnabled,
roomPasswordNumberOfDigits
} = state['features/base/config'];
const { roomPasswordNumberOfDigits } = state['features/base/config'];
return {
_canEditPassword: isLocalParticipantModerator(state, lockRoomGuestEnabled),
_canEditPassword: isLocalParticipantModerator(state),
_conference: conference,
_dialIn: state['features/invite'],
_locked: locked,

View File

@@ -126,14 +126,13 @@ class SettingsDialog extends Component<Props> {
*/
function _mapStateToProps(state) {
const configuredTabs = interfaceConfig.SETTINGS_SECTIONS || [];
const jwt = state['features/base/jwt'];
// The settings sections to display.
const showDeviceSettings = configuredTabs.includes('devices');
const moreTabProps = getMoreTabProps(state);
const { showModeratorSettings, showLanguageSettings, showPrejoinSettings } = moreTabProps;
const showProfileSettings
= configuredTabs.includes('profile') && jwt.isGuest;
= configuredTabs.includes('profile') && !state['features/base/config'].disableProfile;
const showCalendarSettings
= configuredTabs.includes('calendar') && isCalendarEnabled(state);
const tabs = [];

View File

@@ -85,7 +85,12 @@ export class AbstractClosedCaptionButton
export function _abstractMapStateToProps(state: Object, ownProps: Object) {
const { _requestingSubtitles } = state['features/subtitles'];
const { transcribingEnabled } = state['features/base/config'];
const { visible = Boolean(transcribingEnabled && isLocalParticipantModerator(state)) } = ownProps;
const { isTranscribing } = state['features/transcribing'];
// if the participant is moderator, it can enable transcriptions and if
// transcriptions are already started for the meeting, guests can just show them
const { visible = Boolean(transcribingEnabled
&& (isLocalParticipantModerator(state) || isTranscribing)) } = ownProps;
return {
_requestingSubtitles,

View File

@@ -31,14 +31,12 @@ class ClosedCaptionButton
* @returns {Props}
*/
export function mapStateToProps(state: Object, ownProps: Object) {
const { transcribingEnabled } = state['features/base/config'];
const { isGuest = true } = state['features/base/jwt'];
const enabled = getFeatureFlag(state, CLOSE_CAPTIONS_ENABLED, true) && transcribingEnabled && !isGuest;
const { visible = enabled } = ownProps;
const enabled = getFeatureFlag(state, CLOSE_CAPTIONS_ENABLED, true);
const abstractProps = _abstractMapStateToProps(state, ownProps);
return {
..._abstractMapStateToProps(state, ownProps),
visible
...abstractProps,
visible: abstractProps.visible && enabled
};
}

View File

@@ -120,7 +120,7 @@ class OverflowMenuProfileItem extends Component<Props> {
function _mapStateToProps(state) {
return {
_localParticipant: getLocalParticipant(state),
_unclickable: !state['features/base/jwt'].isGuest
_unclickable: state['features/base/config'].disableProfile
|| !interfaceConfig.SETTINGS_SECTIONS.includes('profile')
};
}

View File

@@ -128,16 +128,16 @@ type Props = {
*/
_fullScreen: boolean,
/**
* Whether or not the profile is disabled.
*/
_isProfileDisabled: boolean,
/**
* Whether or not the tile view is enabled.
*/
_tileViewEnabled: boolean,
/**
* Whether or not the current user is logged in through a JWT.
*/
_isGuest: boolean,
/**
* Whether or not the current meeting belongs to a JaaS user.
*/
@@ -993,7 +993,7 @@ class Toolbox extends Component<Props, State> {
* @returns {boolean}
*/
_isProfileVisible() {
return this.props._isGuest && this._shouldShowButton('profile');
return !this.props._isProfileDisabled && this._shouldShowButton('profile');
}
/**
@@ -1426,15 +1426,7 @@ function _mapStateToProps(state) {
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';
}
desktopSharingDisabledTooltipKey = 'dialog.shareYourScreenDisabled';
}
// NB: We compute the buttons again here because if URL parameters were used to
@@ -1448,7 +1440,7 @@ function _mapStateToProps(state) {
_desktopSharingDisabledTooltipKey: desktopSharingDisabledTooltipKey,
_dialog: Boolean(state['features/base/dialog'].component),
_feedbackConfigured: Boolean(callStatsID),
_isGuest: state['features/base/jwt'].isGuest,
_isProfileDisabled: Boolean(state['features/base/config'].disableProfile),
_isVpaasMeeting: isVpaasMeeting(state),
_fullScreen: fullScreen,
_tileViewEnabled: shouldDisplayTileView(state),

View File

@@ -1,6 +1,8 @@
local jid = require "util.jid";
local um_is_admin = require "core.usermanager".is_admin;
local is_healthcheck_room = module:require "util".is_healthcheck_room;
local util = module:require "util";
local is_healthcheck_room = util.is_healthcheck_room;
local extract_subdomain = util.extract_subdomain;
local moderated_subdomains;
local moderated_rooms;
@@ -22,11 +24,14 @@ end
-- -> true, room_name, subdomain
-- -> true, room_name, nil (if no subdomain is used for the room)
local function is_moderated(room_jid)
if #moderated_subdomains == 0 and #moderated_rooms == 0 then
return false;
end
local room_node = jid.node(room_jid);
-- parses bare room address, for multidomain expected format is:
-- [subdomain]roomName@conference.domain
local target_subdomain, target_room_name = room_node:match("^%[([^%]]+)%](.+)$");
local target_subdomain, target_room_name = extract_subdomain(room_node);
if target_subdomain then
if moderated_subdomains:contains(target_subdomain) then
return true, target_room_name, target_subdomain;

View File

@@ -1,5 +1,6 @@
local ext_events = module:require "ext_events"
local jid = require "util.jid"
local extract_subdomain = module:require "util".extract_subdomain;
-- Options and configuration
local poltergeist_component = module:get_option_string(
@@ -33,7 +34,7 @@ local function url_from_room_jid(room_jid)
local node, _, _ = jid.split(room_jid)
if not node then return nil end
local target_subdomain, target_node = node:match("^%[([^%]]+)%](.+)$")
local target_subdomain, target_node = extract_subdomain(node);
if not(target_node or target_subdomain) then
return "https://"..muc_domain_base.."/"..node

View File

@@ -9,7 +9,9 @@ local jid = require "util.jid";
local json_safe = require "cjson.safe";
local path = require "util.paths";
local sha256 = require "util.hashes".sha256;
local http_get_with_retry = module:require "util".http_get_with_retry;
local main_util = module:require "util";
local http_get_with_retry = main_util.http_get_with_retry;
local extract_subdomain = main_util.extract_subdomain;
local nr_retries = 3;
@@ -350,7 +352,7 @@ function Util:verify_room(session, room_address)
local room_node = jid.node(room_address);
-- parses bare room address, for multidomain expected format is:
-- [subdomain]roomName@conference.domain
local target_subdomain, target_room = room_node:match("^%[([^%]]+)%](.+)$");
local target_subdomain, target_room = extract_subdomain(room_node);
-- if we have '*' as room name in token, this means all rooms are allowed
-- so we will use the actual name of the room when constructing strings

View File

@@ -33,6 +33,12 @@ local roomless_iqs = {};
-- (e.g. from room1@conference.foo.example.com/res returns (room1, example.com, res, foo))
local function room_jid_split_subdomain(room_jid)
local node, host, resource = jid.split(room_jid);
-- optimization, skip matching if there is no subdomain or it is not the muc component address at all
if host == muc_domain or not starts_with(host, muc_domain_prefix) then
return node, host, resource;
end
local target_subdomain = host and host:match(target_subdomain_pattern);
return node, host, resource, target_subdomain
end
@@ -80,11 +86,13 @@ local function internal_room_jid_match_rewrite(room_jid, stanza)
return room_jid;
end
local target_subdomain, target_node = node:match("^%[([^%]]+)%](.+)$");
local target_subdomain, target_node = extract_subdomain(node);
if not (target_node and target_subdomain) then
-- module:log("debug", "Not rewriting... unexpected node format: %s", node);
return room_jid;
end
-- Ok, rewrite room_jid address to pretty format
local new_node, new_host, new_resource = target_node, muc_domain_prefix..".".. target_subdomain.."."..muc_domain_base, resource;
room_jid = jid.join(new_node, new_host, new_resource);
@@ -183,6 +191,7 @@ function update_presence_identity(
stanza:tag("identity"):tag("user");
for k, v in pairs(user) do
v = tostring(v)
stanza:tag(k):text(v):up();
end
stanza:up();
@@ -225,6 +234,17 @@ function is_feature_allowed(session, feature)
end
end
--- Extracts the subdomain and room name from internal jid node [foo]room1
-- @return subdomain(optional, if extracted or nil), the room name
function extract_subdomain(room_node)
-- optimization, skip matching if there is no subdomain, no [subdomain] part in the beginning of the node
if not starts_with(room_node, '[') then
return room_node;
end
return room_node:match("^%[([^%]]+)%](.+)$");
end
function starts_with(str, start)
return str:sub(1, #start) == start
end
@@ -305,6 +325,7 @@ function http_get_with_retry(url, retry)
end
return {
extract_subdomain = extract_subdomain;
is_feature_allowed = is_feature_allowed;
is_healthcheck_room = is_healthcheck_room;
get_room_from_jid = get_room_from_jid;