Преглед на файлове

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.
master
Дамян Минков преди 7 години
родител
ревизия
ac834326e7
No account linked to committer's email address

+ 10
- 21
conference.js Целия файл

@@ -484,8 +484,7 @@ export default {
484 484
 
485 485
     /**
486 486
      * Indicates if the desktop sharing functionality has been enabled.
487
-     * It takes into consideration {@link isDesktopSharingDisabledByConfig}
488
-     * as well as the status returned by
487
+     * It takes into consideration the status returned by
489 488
      * {@link JitsiMeetJS.isDesktopSharingEnabled()}. The latter can be false
490 489
      * either if the desktop sharing is not supported by the current browser
491 490
      * or if it was disabled through lib-jitsi-meet specific options (check
@@ -493,19 +492,6 @@ export default {
493 492
      */
494 493
     isDesktopSharingEnabled: false,
495 494
 
496
-    /**
497
-     * Set to <tt>true</tt> if the desktop sharing functionality has been
498
-     * explicitly disabled in the config.
499
-     */
500
-    isDesktopSharingDisabledByConfig: false,
501
-
502
-    /**
503
-     * The text displayed when the desktop sharing button is disabled through
504
-     * the config. The value is set through
505
-     * {@link interfaceConfig.DESKTOP_SHARING_BUTTON_DISABLED_TOOLTIP}.
506
-     */
507
-    desktopSharingDisabledTooltip: null,
508
-
509 495
     /**
510 496
      * The local audio track (if any).
511 497
      * FIXME tracks from redux store should be the single source of truth
@@ -720,13 +706,8 @@ export default {
720 706
                 APP.connection = connection = con;
721 707
 
722 708
                 // Desktop sharing related stuff:
723
-                this.isDesktopSharingDisabledByConfig
724
-                    = config.disableDesktopSharing;
725 709
                 this.isDesktopSharingEnabled
726
-                    = !this.isDesktopSharingDisabledByConfig
727
-                        && JitsiMeetJS.isDesktopSharingEnabled();
728
-                this.desktopSharingDisabledTooltip
729
-                    = interfaceConfig.DESKTOP_SHARING_BUTTON_DISABLED_TOOLTIP;
710
+                    = JitsiMeetJS.isDesktopSharingEnabled();
730 711
                 eventEmitter.emit(
731 712
                     JitsiMeetConferenceEvents.DESKTOP_SHARING_ENABLED_CHANGED,
732 713
                     this.isDesktopSharingEnabled);
@@ -1909,6 +1890,14 @@ export default {
1909 1890
             JitsiConferenceEvents.PARTICIPANT_PROPERTY_CHANGED,
1910 1891
             (participant, name, oldValue, newValue) => {
1911 1892
                 switch (name) {
1893
+                case 'features_screen-sharing': {
1894
+                    APP.store.dispatch(participantUpdated({
1895
+                        conference: room,
1896
+                        id: participant.getId(),
1897
+                        features: { 'screen-sharing': true }
1898
+                    }));
1899
+                    break;
1900
+                }
1912 1901
                 case 'raisedHand':
1913 1902
                     APP.store.dispatch(participantUpdated({
1914 1903
                         conference: room,

+ 3
- 3
config.js Целия файл

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

+ 11
- 1
css/_toolbars.scss Целия файл

@@ -95,7 +95,13 @@
95 95
     }
96 96
 
97 97
     i.disabled {
98
-        cursor: initial
98
+        cursor: initial;
99
+        color: #3b475c;
100
+    }
101
+
102
+    .disabled i {
103
+        cursor: initial;
104
+        color: #3b475c;
99 105
     }
100 106
 
101 107
     i.disabled:hover {
@@ -135,6 +141,10 @@
135 141
             &.unclickable:hover {
136 142
                 background: inherit;
137 143
             }
144
+            &.disabled {
145
+                cursor: initial;
146
+                color: #3b475c;
147
+            }
138 148
         }
139 149
 
140 150
         .beta-tag {

+ 15
- 0
css/modals/invite/_add-people.scss Целия файл

@@ -23,6 +23,21 @@
23 23
                 margin: auto;
24 24
             }
25 25
         }
26
+
27
+        .footer-text-wrap {
28
+            display: flex;
29
+        }
30
+
31
+        .footer-telephone-icon {
32
+            display: flex;
33
+            transform: scaleX(-1);
34
+            padding-left: 10px;
35
+
36
+            i {
37
+                line-height: 20px;
38
+                margin: auto;
39
+            }
40
+        }
26 41
     }
27 42
 }
28 43
 

+ 0
- 6
interface_config.js Целия файл

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

+ 7
- 0
lang/main.json Целия файл

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

+ 16
- 2
react/features/base/conference/functions.js Целия файл

@@ -188,8 +188,17 @@ function _reportError(msg, err) {
188 188
  */
189 189
 export function sendLocalParticipant(
190 190
         stateful: Function | Object,
191
-        conference: { sendCommand: Function, setDisplayName: Function }) {
192
-    const { avatarID, avatarURL, email, name } = getLocalParticipant(stateful);
191
+        conference: {
192
+            sendCommand: Function,
193
+            setDisplayName: Function,
194
+            setLocalParticipantProperty: Function }) {
195
+    const {
196
+        avatarID,
197
+        avatarURL,
198
+        email,
199
+        features,
200
+        name
201
+    } = getLocalParticipant(stateful);
193 202
 
194 203
     avatarID && conference.sendCommand(AVATAR_ID_COMMAND, {
195 204
         value: avatarID
@@ -200,5 +209,10 @@ export function sendLocalParticipant(
200 209
     email && conference.sendCommand(EMAIL_COMMAND, {
201 210
         value: email
202 211
     });
212
+
213
+    if (features && features['screen-sharing'] === 'true') {
214
+        conference.setLocalParticipantProperty('features_screen-sharing', true);
215
+    }
216
+
203 217
     conference.setDisplayName(name);
204 218
 }

+ 0
- 3
react/features/base/config/functions.js Целия файл

@@ -89,8 +89,6 @@ const WHITELISTED_KEYS = [
89 89
     'disableAGC',
90 90
     'disableAP',
91 91
     'disableAudioLevels',
92
-    'disableDesktopSharing',
93
-    'disableDesktopSharing',
94 92
     'disableH264',
95 93
     'disableHPF',
96 94
     'disableNS',
@@ -106,7 +104,6 @@ const WHITELISTED_KEYS = [
106 104
     'enableStatsID',
107 105
     'enableTalkWhileMuted',
108 106
     'enableTcc',
109
-    'enableUserRolesBasedOnToken',
110 107
     'etherpad_base',
111 108
     'failICE',
112 109
     'fileRecordingsEnabled',

+ 9
- 2
react/features/base/jwt/middleware.js Целия файл

@@ -138,7 +138,7 @@ function _maybeSetCalleeInfoVisible({ dispatch, getState }, next, action) {
138 138
  */
139 139
 function _overwriteLocalParticipant(
140 140
         { dispatch, getState },
141
-        { avatarURL, email, name }) {
141
+        { avatarURL, email, name, features }) {
142 142
     let localParticipant;
143 143
 
144 144
     if ((avatarURL || email || name)
@@ -157,6 +157,9 @@ function _overwriteLocalParticipant(
157 157
         if (name) {
158 158
             newProperties.name = name;
159 159
         }
160
+        if (features) {
161
+            newProperties.features = features;
162
+        }
160 163
         dispatch(participantUpdated(newProperties));
161 164
     }
162 165
 }
@@ -229,7 +232,9 @@ function _setJWT(store, next, action) {
229 232
                     action.server = context.server;
230 233
                     action.user = user;
231 234
 
232
-                    user && _overwriteLocalParticipant(store, user);
235
+                    user && _overwriteLocalParticipant(
236
+                        store, { ...user,
237
+                            features: context.features });
233 238
                 }
234 239
             }
235 240
         } else if (typeof APP === 'undefined') {
@@ -281,6 +286,8 @@ function _undoOverwriteLocalParticipant(
281 286
         if (name === localParticipant.name) {
282 287
             newProperties.name = undefined;
283 288
         }
289
+        newProperties.features = undefined;
290
+
284 291
         dispatch(participantUpdated(newProperties));
285 292
     }
286 293
 }

+ 7
- 0
react/features/base/react/components/web/MultiSelectAutocomplete.js Целия файл

@@ -24,6 +24,12 @@ class MultiSelectAutocomplete extends Component {
24 24
          */
25 25
         defaultValue: PropTypes.array,
26 26
 
27
+        /**
28
+         * Optional footer to show as a last element in the results.
29
+         * Should be of type {content: <some content>}
30
+         */
31
+        footer: PropTypes.object,
32
+
27 33
         /**
28 34
          * Indicates if the component is disabled.
29 35
          */
@@ -151,6 +157,7 @@ class MultiSelectAutocomplete extends Component {
151 157
             <div>
152 158
                 <MultiSelectStateless
153 159
                     filterValue = { this.state.filterValue }
160
+                    footer = { this.props.footer }
154 161
                     icon = { null }
155 162
                     isDisabled = { isDisabled }
156 163
                     isLoading = { this.state.loading }

+ 38
- 2
react/features/invite/components/AddPeopleDialog.web.js Целия файл

@@ -8,7 +8,8 @@ import { connect } from 'react-redux';
8 8
 
9 9
 import { createInviteDialogEvent, sendAnalytics } from '../../analytics';
10 10
 import { Dialog, hideDialog } from '../../base/dialog';
11
-import { translate } from '../../base/i18n';
11
+import { translate, translateToHTML } from '../../base/i18n';
12
+import { getLocalParticipant } from '../../base/participants';
12 13
 import { MultiSelectAutocomplete } from '../../base/react';
13 14
 
14 15
 import { invite } from '../actions';
@@ -39,6 +40,12 @@ class AddPeopleDialog extends Component<*, *> {
39 40
          */
40 41
         _dialOutAuthUrl: PropTypes.string,
41 42
 
43
+        /**
44
+         * Whether to show a footer text after the search results
45
+         * as a last element.
46
+         */
47
+        _footerTextEnabled: PropTypes.bool,
48
+
42 49
         /**
43 50
          * The JWT token.
44 51
          */
@@ -168,11 +175,15 @@ class AddPeopleDialog extends Component<*, *> {
168 175
      * @returns {ReactElement}
169 176
      */
170 177
     render() {
171
-        const { addPeopleEnabled, dialOutEnabled, t } = this.props;
178
+        const { _footerTextEnabled,
179
+            addPeopleEnabled,
180
+            dialOutEnabled,
181
+            t } = this.props;
172 182
         let isMultiSelectDisabled = this.state.addToCallInProgress || false;
173 183
         let placeholder;
174 184
         let loadingMessage;
175 185
         let noMatches;
186
+        let footerText;
176 187
 
177 188
         if (addPeopleEnabled && dialOutEnabled) {
178 189
             loadingMessage = 'addPeople.loading';
@@ -192,6 +203,19 @@ class AddPeopleDialog extends Component<*, *> {
192 203
             placeholder = 'addPeople.disabled';
193 204
         }
194 205
 
206
+        if (_footerTextEnabled) {
207
+            footerText = {
208
+                content: <div className = 'footer-text-wrap'>
209
+                    <div>
210
+                        <span className = 'footer-telephone-icon'>
211
+                            <i className = 'icon-telephone' />
212
+                        </span>
213
+                    </div>
214
+                    { translateToHTML(t, 'addPeople.footerText') }
215
+                </div>
216
+            };
217
+        }
218
+
195 219
         return (
196 220
             <Dialog
197 221
                 okDisabled = { this._isAddDisabled() }
@@ -202,6 +226,7 @@ class AddPeopleDialog extends Component<*, *> {
202 226
                 <div className = 'add-people-form-wrap'>
203 227
                     { this._renderErrorMessage() }
204 228
                     <MultiSelectAutocomplete
229
+                        footer = { footerText }
205 230
                         isDisabled = { isMultiSelectDisabled }
206 231
                         loadingMessage = { t(loadingMessage) }
207 232
                         noMatchesFound = { t(noMatches) }
@@ -525,12 +550,23 @@ class AddPeopleDialog extends Component<*, *> {
525 550
 function _mapStateToProps(state) {
526 551
     const {
527 552
         dialOutAuthUrl,
553
+        enableFeaturesBasedOnToken,
528 554
         peopleSearchQueryTypes,
529 555
         peopleSearchUrl
530 556
     } = state['features/base/config'];
557
+    let footerTextEnabled = false;
558
+
559
+    if (enableFeaturesBasedOnToken) {
560
+        const { features = {} } = getLocalParticipant(state);
561
+
562
+        if (String(features['outbound-call']) !== 'true') {
563
+            footerTextEnabled = true;
564
+        }
565
+    }
531 566
 
532 567
     return {
533 568
         _dialOutAuthUrl: dialOutAuthUrl,
569
+        _footerTextEnabled: footerTextEnabled,
534 570
         _jwt: state['features/base/jwt'].jwt,
535 571
         _peopleSearchQueryTypes: peopleSearchQueryTypes,
536 572
         _peopleSearchUrl: peopleSearchUrl

+ 41
- 4
react/features/toolbox/components/web/OverflowMenuItem.js Целия файл

@@ -1,3 +1,4 @@
1
+import Tooltip from '@atlaskit/tooltip';
1 2
 import PropTypes from 'prop-types';
2 3
 import React, { Component } from 'react';
3 4
 
@@ -8,6 +9,16 @@ import React, { Component } from 'react';
8 9
  * @extends Component
9 10
  */
10 11
 class OverflowMenuItem extends Component {
12
+    /**
13
+     * Default values for {@code OverflowMenuItem} component's properties.
14
+     *
15
+     * @static
16
+     */
17
+    static defaultProps = {
18
+        tooltipPosition: 'left',
19
+        disabled: false
20
+    };
21
+
11 22
     /**
12 23
      * {@code OverflowMenuItem} component's property types.
13 24
      *
@@ -20,6 +31,11 @@ class OverflowMenuItem extends Component {
20 31
          */
21 32
         accessibilityLabel: PropTypes.string,
22 33
 
34
+        /**
35
+         * Whether menu item is disabled or not.
36
+         */
37
+        disabled: PropTypes.bool,
38
+
23 39
         /**
24 40
          * The icon class to use for displaying an icon before the link text.
25 41
          */
@@ -33,7 +49,18 @@ class OverflowMenuItem extends Component {
33 49
         /**
34 50
          * The text to display in the {@code OverflowMenuItem}.
35 51
          */
36
-        text: PropTypes.string
52
+        text: PropTypes.string,
53
+
54
+        /**
55
+         * The text to display in the tooltip.
56
+         */
57
+        tooltip: PropTypes.string,
58
+
59
+        /**
60
+         * From which direction the tooltip should appear, relative to the
61
+         * button.
62
+         */
63
+        tooltipPosition: PropTypes.string
37 64
     };
38 65
 
39 66
     /**
@@ -43,15 +70,25 @@ class OverflowMenuItem extends Component {
43 70
      * @returns {ReactElement}
44 71
      */
45 72
     render() {
73
+        let className = 'overflow-menu-item';
74
+
75
+        className += this.props.disabled ? ' disabled' : '';
76
+
46 77
         return (
47 78
             <li
48 79
                 aria-label = { this.props.accessibilityLabel }
49
-                className = 'overflow-menu-item'
50
-                onClick = { this.props.onClick }>
80
+                className = { className }
81
+                onClick = { this.props.disabled ? null : this.props.onClick }>
51 82
                 <span className = 'overflow-menu-item-icon'>
52 83
                     <i className = { this.props.icon } />
53 84
                 </span>
54
-                { this.props.text }
85
+                { this.props.tooltip
86
+                    ? <Tooltip
87
+                        content = { this.props.tooltip }
88
+                        position = { this.props.tooltipPosition }>
89
+                        <span>{ this.props.text }</span>
90
+                    </Tooltip>
91
+                    : this.props.text }
55 92
             </li>
56 93
         );
57 94
     }

+ 76
- 32
react/features/toolbox/components/web/OverflowMenuLiveStreamingItem.js Целия файл

@@ -1,39 +1,66 @@
1
-// @flow
2
-
1
+import Tooltip from '@atlaskit/tooltip';
2
+import PropTypes from 'prop-types';
3 3
 import React, { Component } from 'react';
4 4
 
5 5
 import { translate } from '../../../base/i18n';
6 6
 
7 7
 /**
8
- * The type of the React {@code Component} props of
9
- * {@link OverflowMenuLiveStreamingItem}.
8
+ * React {@code Component} for starting or stopping a live streaming of the
9
+ * current conference and displaying the current state of live streaming.
10
+ *
11
+ * @extends Component
10 12
  */
11
-type Props = {
12
-
13
+class OverflowMenuLiveStreamingItem extends Component {
13 14
     /**
14
-     * The callback to invoke when {@code OverflowMenuLiveStreamingItem} is
15
-     * clicked.
15
+     * Default values for {@code OverflowMenuLiveStreamingItem}
16
+     * component's properties.
17
+     *
18
+     * @static
16 19
      */
17
-    onClick: Function,
20
+    static defaultProps = {
21
+        tooltipPosition: 'left',
22
+        disabled: false
23
+    };
18 24
 
19 25
     /**
20
-     * The current live streaming session, if any.
26
+     * The type of the React {@code Component} props of
27
+     * {@link OverflowMenuLiveStreamingItem}.
21 28
      */
22
-    session: ?Object,
29
+    static propTypes = {
23 30
 
24
-    /**
25
-     * Invoked to obtain translated strings.
26
-     */
27
-    t: Function
28
-};
31
+        /**
32
+         * Whether menu item is disabled or not.
33
+         */
34
+        disabled: PropTypes.bool,
35
+
36
+        /**
37
+         * The callback to invoke when {@code OverflowMenuLiveStreamingItem} is
38
+         * clicked.
39
+         */
40
+        onClick: Function,
41
+
42
+        /**
43
+         * The current live streaming session, if any.
44
+         */
45
+        session: PropTypes.object,
46
+
47
+        /**
48
+         * Invoked to obtain translated strings.
49
+         */
50
+        t: Function,
51
+
52
+        /**
53
+         * The text to display in the tooltip.
54
+         */
55
+        tooltip: PropTypes.string,
56
+
57
+        /**
58
+         * From which direction the tooltip should appear, relative to the
59
+         * button.
60
+         */
61
+        tooltipPosition: PropTypes.string
62
+    };
29 63
 
30
-/**
31
- * React {@code Component} for starting or stopping a live streaming of the
32
- * current conference and displaying the current state of live streaming.
33
- *
34
- * @extends Component
35
- */
36
-class OverflowMenuLiveStreamingItem extends Component<Props> {
37 64
     /**
38 65
      * Implements React's {@link Component#render()}.
39 66
      *
@@ -41,26 +68,43 @@ class OverflowMenuLiveStreamingItem extends Component<Props> {
41 68
      * @returns {ReactElement}
42 69
      */
43 70
     render() {
44
-        const { onClick, session, t } = this.props;
71
+        const { disabled, onClick, session, t, tooltip, tooltipPosition }
72
+            = this.props;
45 73
 
46 74
         const translationKey = session
47 75
             ? 'dialog.stopLiveStreaming'
48 76
             : 'dialog.startLiveStreaming';
49 77
 
50
-        return (
51
-            <li
52
-                aria-label = { t('dialog.accessibilityLabel.liveStreaming') }
53
-                className = 'overflow-menu-item'
54
-                onClick = { onClick }>
55
-                <span className = 'overflow-menu-item-icon'>
56
-                    <i className = 'icon-public' />
57
-                </span>
78
+        let className = 'overflow-menu-item';
79
+
80
+        className += disabled ? ' disabled' : '';
81
+
82
+        const button = (// eslint-disable-line no-extra-parens
83
+            <span>
58 84
                 <span className = 'profile-text'>
59 85
                     { t(translationKey) }
60 86
                 </span>
61 87
                 <span className = 'beta-tag'>
62 88
                     { t('recording.beta') }
63 89
                 </span>
90
+            </span>
91
+        );
92
+
93
+        return (
94
+            <li
95
+                aria-label = { t('dialog.accessibilityLabel.liveStreaming') }
96
+                className = { className }
97
+                onClick = { disabled ? null : onClick }>
98
+                <span className = 'overflow-menu-item-icon'>
99
+                    <i className = 'icon-public' />
100
+                </span>
101
+                { tooltip
102
+                    ? <Tooltip
103
+                        content = { tooltip }
104
+                        position = { tooltipPosition }>
105
+                        { button }
106
+                    </Tooltip>
107
+                    : button }
64 108
             </li>
65 109
         );
66 110
     }

+ 111
- 27
react/features/toolbox/components/web/Toolbox.js Целия файл

@@ -13,8 +13,9 @@ import { openDialog } from '../../../base/dialog';
13 13
 import { translate } from '../../../base/i18n';
14 14
 import { JitsiRecordingConstants } from '../../../base/lib-jitsi-meet';
15 15
 import {
16
-    PARTICIPANT_ROLE,
17 16
     getLocalParticipant,
17
+    getParticipants,
18
+    isLocalParticipantModerator,
18 19
     participantUpdated
19 20
 } from '../../../base/participants';
20 21
 import { getLocalVideoTrack, toggleScreensharing } from '../../../base/tracks';
@@ -74,9 +75,10 @@ type Props = {
74 75
     _conference: Object,
75 76
 
76 77
     /**
77
-     * Whether or not desktopsharing was explicitly configured to be disabled.
78
+     * The tooltip key to use when screensharing is disabled. Or undefined
79
+     * if non to be shown and the button to be hidden.
78 80
      */
79
-    _desktopSharingDisabledByConfig: boolean,
81
+    _desktopSharingDisabledTooltipKey: boolean,
80 82
 
81 83
     /**
82 84
      * Whether or not screensharing is initialized.
@@ -103,6 +105,12 @@ type Props = {
103 105
      */
104 106
     _feedbackConfigured: boolean,
105 107
 
108
+    /**
109
+     * The tooltip key to use when file recording is disabled. Or undefined
110
+     * if non to be shown and the button to be hidden.
111
+     */
112
+    _fileRecordingsDisabledTooltipKey: boolean,
113
+
106 114
     /**
107 115
      * Whether or not the file recording feature is enabled for use.
108 116
      */
@@ -129,6 +137,12 @@ type Props = {
129 137
      */
130 138
     _isGuest: boolean,
131 139
 
140
+    /**
141
+     * The tooltip key to use when live streaming is disabled. Or undefined
142
+     * if non to be shown and the button to be hidden.
143
+     */
144
+    _liveStreamingDisabledTooltipKey: boolean,
145
+
132 146
     /**
133 147
      * Whether or not the live streaming feature is enabled for use.
134 148
      */
@@ -925,17 +939,14 @@ class Toolbox extends Component<Props> {
925 939
      */
926 940
     _renderDesktopSharingButton() {
927 941
         const {
928
-            _desktopSharingDisabledByConfig,
929 942
             _desktopSharingEnabled,
943
+            _desktopSharingDisabledTooltipKey,
930 944
             _screensharing,
931 945
             t
932 946
         } = this.props;
933 947
 
934
-        const disabledTooltipText
935
-            = interfaceConfig.DESKTOP_SHARING_BUTTON_DISABLED_TOOLTIP;
936
-        const showDisabledTooltip
937
-            = disabledTooltipText && _desktopSharingDisabledByConfig;
938
-        const visible = _desktopSharingEnabled || showDisabledTooltip;
948
+        const visible
949
+            = _desktopSharingEnabled || _desktopSharingDisabledTooltipKey;
939 950
 
940 951
         if (!visible) {
941 952
             return null;
@@ -944,9 +955,9 @@ class Toolbox extends Component<Props> {
944 955
         const classNames = `icon-share-desktop ${
945 956
             _screensharing ? 'toggled' : ''} ${
946 957
             _desktopSharingEnabled ? '' : 'disabled'}`;
947
-        const tooltip = showDisabledTooltip
948
-            ? disabledTooltipText
949
-            : t('dialog.shareYourScreen');
958
+        const tooltip = t(
959
+            _desktopSharingEnabled
960
+                ? 'dialog.shareYourScreen' : _desktopSharingDisabledTooltipKey);
950 961
 
951 962
         return (
952 963
             <ToolbarButton
@@ -969,9 +980,11 @@ class Toolbox extends Component<Props> {
969 980
             _editingDocument,
970 981
             _etherpadInitialized,
971 982
             _feedbackConfigured,
983
+            _fileRecordingsDisabledTooltipKey,
972 984
             _fileRecordingsEnabled,
973 985
             _fullScreen,
974 986
             _isGuest,
987
+            _liveStreamingDisabledTooltipKey,
975 988
             _liveStreamingEnabled,
976 989
             _liveStreamingSession,
977 990
             _sharingVideo,
@@ -1000,13 +1013,15 @@ class Toolbox extends Component<Props> {
1000 1013
                     text = { _fullScreen
1001 1014
                         ? t('toolbar.exitFullScreen')
1002 1015
                         : t('toolbar.enterFullScreen') } />,
1003
-            _liveStreamingEnabled
1016
+            (_liveStreamingEnabled || _liveStreamingDisabledTooltipKey)
1004 1017
                 && this._shouldShowButton('livestreaming')
1005 1018
                 && <OverflowMenuLiveStreamingItem
1019
+                    disabled = { !_liveStreamingEnabled }
1006 1020
                     key = 'livestreaming'
1007 1021
                     onClick = { this._onToolbarToggleLiveStreaming }
1008
-                    session = { _liveStreamingSession } />,
1009
-            _fileRecordingsEnabled
1022
+                    session = { _liveStreamingSession }
1023
+                    tooltip = { t(_liveStreamingDisabledTooltipKey) } />,
1024
+            (_fileRecordingsEnabled || _fileRecordingsDisabledTooltipKey)
1010 1025
                 && this._shouldShowButton('recording')
1011 1026
                 && this._renderRecordingButton(),
1012 1027
             this._shouldShowButton('sharedvideo')
@@ -1070,7 +1085,11 @@ class Toolbox extends Component<Props> {
1070 1085
      * @returns {ReactElement|null}
1071 1086
      */
1072 1087
     _renderRecordingButton() {
1073
-        const { _fileRecordingSession, t } = this.props;
1088
+        const {
1089
+            _fileRecordingSession,
1090
+            _fileRecordingsDisabledTooltipKey,
1091
+            _fileRecordingsEnabled,
1092
+            t } = this.props;
1074 1093
 
1075 1094
         const translationKey = _fileRecordingSession
1076 1095
             ? 'dialog.stopRecording'
@@ -1080,10 +1099,12 @@ class Toolbox extends Component<Props> {
1080 1099
             <OverflowMenuItem
1081 1100
                 accessibilityLabel =
1082 1101
                     { t('toolbar.accessibilityLabel.recording') }
1102
+                disabled = { !_fileRecordingsEnabled }
1083 1103
                 icon = 'icon-camera-take-picture'
1084 1104
                 key = 'recording'
1085 1105
                 onClick = { this._onToolbarToggleRecording }
1086
-                text = { t(translationKey) } />
1106
+                text = { t(translationKey) }
1107
+                tooltip = { t(_fileRecordingsDisabledTooltipKey) } />
1087 1108
         );
1088 1109
     }
1089 1110
 
@@ -1111,15 +1132,14 @@ class Toolbox extends Component<Props> {
1111 1132
  * @returns {{}}
1112 1133
  */
1113 1134
 function _mapStateToProps(state) {
1114
-    const {
1115
-        conference,
1116
-        desktopSharingEnabled
1117
-    } = state['features/base/conference'];
1135
+    const { conference } = state['features/base/conference'];
1136
+    let { desktopSharingEnabled } = state['features/base/conference'];
1118 1137
     const {
1119 1138
         callStatsID,
1120
-        disableDesktopSharing,
1139
+        iAmRecorder
1140
+    } = state['features/base/config'];
1141
+    let {
1121 1142
         fileRecordingsEnabled,
1122
-        iAmRecorder,
1123 1143
         liveStreamingEnabled
1124 1144
     } = state['features/base/config'];
1125 1145
     const sharedVideoStatus = state['features/shared-video'].status;
@@ -1133,15 +1153,77 @@ function _mapStateToProps(state) {
1133 1153
     } = state['features/toolbox'];
1134 1154
     const localParticipant = getLocalParticipant(state);
1135 1155
     const localVideo = getLocalVideoTrack(state['features/base/tracks']);
1136
-    const isModerator = localParticipant.role === PARTICIPANT_ROLE.MODERATOR;
1137 1156
     const addPeopleEnabled = isAddPeopleEnabled(state);
1138 1157
     const dialOutEnabled = isDialOutEnabled(state);
1139 1158
 
1159
+    let desktopSharingDisabledTooltipKey;
1160
+    let fileRecordingsDisabledTooltipKey;
1161
+    let liveStreamingDisabledTooltipKey;
1162
+
1163
+    fileRecordingsEnabled
1164
+        = isLocalParticipantModerator(state) && fileRecordingsEnabled;
1165
+    liveStreamingEnabled
1166
+        = isLocalParticipantModerator(state) && liveStreamingEnabled;
1167
+
1168
+    if (state['features/base/config'].enableFeaturesBasedOnToken) {
1169
+        // we enable desktop sharing if any participant already have this
1170
+        // feature enabled
1171
+        desktopSharingEnabled = getParticipants(state)
1172
+            .find(({ features = {} }) =>
1173
+                String(features['screen-sharing']) === 'true') !== undefined;
1174
+
1175
+        // we want to show button and tooltip
1176
+        if (state['features/base/jwt'].isGuest) {
1177
+            desktopSharingDisabledTooltipKey
1178
+                = 'dialog.shareYourScreenDisabledForGuest';
1179
+        } else {
1180
+            desktopSharingDisabledTooltipKey
1181
+                = 'dialog.shareYourScreenDisabled';
1182
+        }
1183
+
1184
+        // we enable recording if the local participant have this
1185
+        // feature enabled
1186
+        const { features = {} } = localParticipant;
1187
+        const { isGuest } = state['features/base/jwt'];
1188
+
1189
+        fileRecordingsEnabled
1190
+            = fileRecordingsEnabled && String(features.recording) === 'true';
1191
+
1192
+        // if the feature is disabled on purpose, do no show it, no tooltip
1193
+        if (!fileRecordingsEnabled
1194
+            && String(features.recording) !== 'disabled') {
1195
+            // button and tooltip
1196
+            if (isGuest) {
1197
+                fileRecordingsDisabledTooltipKey
1198
+                    = 'dialog.recordingDisabledForGuestTooltip';
1199
+            } else {
1200
+                fileRecordingsDisabledTooltipKey
1201
+                    = 'dialog.recordingDisabledTooltip';
1202
+            }
1203
+        }
1204
+
1205
+        liveStreamingEnabled
1206
+            = liveStreamingEnabled && String(features.livestreaming) === 'true';
1207
+
1208
+        // if the feature is disabled on purpose, do no show it, no tooltip
1209
+        if (!liveStreamingEnabled
1210
+            && String(features.livestreaming) !== 'disabled') {
1211
+            // button and tooltip
1212
+            if (isGuest) {
1213
+                liveStreamingDisabledTooltipKey
1214
+                    = 'dialog.liveStreamingDisabledForGuestTooltip';
1215
+            } else {
1216
+                liveStreamingDisabledTooltipKey
1217
+                    = 'dialog.liveStreamingDisabledTooltip';
1218
+            }
1219
+        }
1220
+    }
1221
+
1140 1222
     return {
1141 1223
         _chatOpen: current === 'chat_container',
1142 1224
         _conference: conference,
1143 1225
         _desktopSharingEnabled: desktopSharingEnabled,
1144
-        _desktopSharingDisabledByConfig: disableDesktopSharing,
1226
+        _desktopSharingDisabledTooltipKey: desktopSharingDisabledTooltipKey,
1145 1227
         _dialog: Boolean(state['features/base/dialog'].component),
1146 1228
         _editingDocument: Boolean(state['features/etherpad'].editing),
1147 1229
         _etherpadInitialized: Boolean(state['features/etherpad'].initialized),
@@ -1149,11 +1231,13 @@ function _mapStateToProps(state) {
1149 1231
         _hideInviteButton:
1150 1232
             iAmRecorder || (!addPeopleEnabled && !dialOutEnabled),
1151 1233
         _isGuest: state['features/base/jwt'].isGuest,
1152
-        _fileRecordingsEnabled: isModerator && fileRecordingsEnabled,
1234
+        _fileRecordingsDisabledTooltipKey: fileRecordingsDisabledTooltipKey,
1235
+        _fileRecordingsEnabled: fileRecordingsEnabled,
1153 1236
         _fileRecordingSession:
1154 1237
             getActiveSession(state, JitsiRecordingConstants.mode.FILE),
1155 1238
         _fullScreen: fullScreen,
1156
-        _liveStreamingEnabled: isModerator && liveStreamingEnabled,
1239
+        _liveStreamingDisabledTooltipKey: liveStreamingDisabledTooltipKey,
1240
+        _liveStreamingEnabled: liveStreamingEnabled,
1157 1241
         _liveStreamingSession:
1158 1242
              getActiveSession(state, JitsiRecordingConstants.mode.STREAM),
1159 1243
         _localParticipantID: localParticipant.id,

+ 26
- 0
resources/prosody-plugins/mod_filter_iq_jibri.lua Целия файл

@@ -0,0 +1,26 @@
1
+local st = require "util.stanza";
2
+local is_feature_allowed = module:require "util".is_feature_allowed;
3
+
4
+-- filters jibri iq in case of requested from jwt authenticated session that
5
+-- has features in the user context, but without feature for recording
6
+module:hook("pre-iq/full", function(event)
7
+    local stanza = event.stanza;
8
+    if stanza.name == "iq" then
9
+        local jibri = stanza:get_child('jibri', 'http://jitsi.org/protocol/jibri');
10
+        if jibri then
11
+            local session = event.origin;
12
+            local token = session.auth_token;
13
+
14
+            if jibri.attr.action == 'start'
15
+                and (token == nil
16
+                    or not is_feature_allowed(session,
17
+                        (jibri.attr.recording_mode == 'file' and 'recording' or 'livestreaming'))
18
+                ) then
19
+                module:log("info",
20
+                    "Filtering jibri start recording, stanza:%s", tostring(stanza));
21
+                session.send(st.error_reply(stanza, "auth", "forbidden"));
22
+                return true;
23
+            end
24
+        end
25
+    end
26
+end);

+ 8
- 2
resources/prosody-plugins/mod_filter_iq_rayo.lua Целия файл

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

+ 6
- 0
resources/prosody-plugins/token/util.lib.lua Целия файл

@@ -235,6 +235,7 @@ end
235 235
 -- session.jitsi_meet_domain - the domain name value from the token
236 236
 -- session.jitsi_meet_context_user - the user details from the token
237 237
 -- session.jitsi_meet_context_group - the group value from the token
238
+-- session.jitsi_meet_context_features - the features value from the token
238 239
 -- @param session the current session
239 240
 -- @return false and error
240 241
 function Util:process_and_verify_token(session)
@@ -285,6 +286,11 @@ function Util:process_and_verify_token(session)
285 286
             -- Binds any group details to the session
286 287
             session.jitsi_meet_context_group = claims["context"]["group"];
287 288
           end
289
+
290
+          if claims["context"]["features"] ~= nil then
291
+            -- Binds any features details to the session
292
+            session.jitsi_meet_context_features = claims["context"]["features"];
293
+          end
288 294
         end
289 295
         return true;
290 296
     else

+ 15
- 0
resources/prosody-plugins/util.lib.lua Целия файл

@@ -133,7 +133,22 @@ function update_presence_identity(
133 133
         "Presence with identity inserted %s", tostring(stanza))
134 134
 end
135 135
 
136
+-- Utility function to check whether feature is present and enabled. Allow
137
+-- a feature if there are features present in the session(coming from
138
+-- the token) and the value of the feature is true.
139
+-- If features is not present in the token we skip feature detection and allow
140
+-- everything.
141
+function is_feature_allowed(session, feature)
142
+    if (session.jitsi_meet_context_features == nil
143
+        or session.jitsi_meet_context_features[feature] == "true") then
144
+        return true;
145
+    else
146
+        return false;
147
+    end
148
+end
149
+
136 150
 return {
151
+    is_feature_allowed = is_feature_allowed;
137 152
     get_room_from_jid = get_room_from_jid;
138 153
     wrap_async_run = wrap_async_run;
139 154
     room_jid_match_rewrite = room_jid_match_rewrite;

Loading…
Отказ
Запис