Browse Source

e2ee: add UI elements

* Add dialog to set the E2EE key
* Use the Redux action / middleware to update the key even when set through the
  hash parameter
* Cleanup URL after processing the key so it's not recorded in browser history
master
Saúl Ibarra Corretgé 5 years ago
parent
commit
cb6fbb0f03

+ 7
- 8
conference.js View File

113
 import { getJitsiMeetGlobalNS } from './react/features/base/util';
113
 import { getJitsiMeetGlobalNS } from './react/features/base/util';
114
 import { showDesktopPicker } from './react/features/desktop-picker';
114
 import { showDesktopPicker } from './react/features/desktop-picker';
115
 import { appendSuffix } from './react/features/display-name';
115
 import { appendSuffix } from './react/features/display-name';
116
+import { setE2EEKey } from './react/features/e2ee';
116
 import {
117
 import {
117
     maybeOpenFeedbackDialog,
118
     maybeOpenFeedbackDialog,
118
     submitFeedback
119
     submitFeedback
470
      */
471
      */
471
     localVideo: null,
472
     localVideo: null,
472
 
473
 
473
-    /**
474
-     * The key used for End-To-End Encryption.
475
-     */
476
-    e2eeKey: undefined,
477
-
478
     /**
474
     /**
479
      * Creates local media tracks and connects to a room. Will show error
475
      * Creates local media tracks and connects to a room. Will show error
480
      * dialogs in case accessing the local microphone and/or camera failed. Will
476
      * dialogs in case accessing the local microphone and/or camera failed. Will
1202
             items[key] = param[1];
1198
             items[key] = param[1];
1203
         }
1199
         }
1204
 
1200
 
1205
-        this.e2eeKey = items.e2eekey;
1201
+        if (typeof items.e2eekey !== undefined) {
1202
+            APP.store.dispatch(setE2EEKey(items.e2eekey));
1206
 
1203
 
1207
-        logger.debug(`New E2EE key: ${this.e2eeKey}`);
1204
+            // Clean URL in browser history.
1205
+            const cleanUrl = window.location.href.split('#')[0];
1208
 
1206
 
1209
-        this._room.setE2EEKey(this.e2eeKey);
1207
+            history.replaceState(history.state, document.title, cleanUrl);
1208
+        }
1210
     },
1209
     },
1211
 
1210
 
1212
     /**
1211
     /**

+ 2
- 1
interface_config.js View File

51
         'fodeviceselection', 'hangup', 'profile', 'info', 'chat', 'recording',
51
         'fodeviceselection', 'hangup', 'profile', 'info', 'chat', 'recording',
52
         'livestreaming', 'etherpad', 'sharedvideo', 'settings', 'raisehand',
52
         'livestreaming', 'etherpad', 'sharedvideo', 'settings', 'raisehand',
53
         'videoquality', 'filmstrip', 'invite', 'feedback', 'stats', 'shortcuts',
53
         'videoquality', 'filmstrip', 'invite', 'feedback', 'stats', 'shortcuts',
54
-        'tileview', 'videobackgroundblur', 'download', 'help', 'mute-everyone'
54
+        'tileview', 'videobackgroundblur', 'download', 'help', 'mute-everyone',
55
+        'e2ee'
55
     ],
56
     ],
56
 
57
 
57
     SETTINGS_SECTIONS: [ 'devices', 'language', 'moderator', 'profile', 'calendar' ],
58
     SETTINGS_SECTIONS: [ 'devices', 'language', 'moderator', 'profile', 'calendar' ],

+ 5
- 0
lang/main.json View File

175
         "dismiss": "Dismiss",
175
         "dismiss": "Dismiss",
176
         "displayNameRequired": "Hi! What’s your name?",
176
         "displayNameRequired": "Hi! What’s your name?",
177
         "done": "Done",
177
         "done": "Done",
178
+        "e2eeDescription": "<p>End-to-End Encryption is currently <strong>EXPERIMENTAL</strong>. Please see <a href='https://jitsi.org/blog/e2ee/' target='_blank'>this post</a> for details.</p><br/><p>Please keep in mind that turning on end-to-end encryption will effectively disable server-side provided services such as: recording, live streaming and phone participation. Also keep in mind that the meeting will only work for people joining from browsers with support for insertable streams.</p>",
179
+        "e2eeLabel": "Key",
180
+        "e2eeTitle": "End-to-End Encryption",
178
         "enterDisplayName": "Please enter your name here",
181
         "enterDisplayName": "Please enter your name here",
179
         "error": "Error",
182
         "error": "Error",
180
         "externalInstallationMsg": "You need to install our desktop sharing extension.",
183
         "externalInstallationMsg": "You need to install our desktop sharing extension.",
595
             "chat": "Toggle chat window",
598
             "chat": "Toggle chat window",
596
             "document": "Toggle shared document",
599
             "document": "Toggle shared document",
597
             "download": "Download our apps",
600
             "download": "Download our apps",
601
+            "e2ee": "End-to-End Encryption",
598
             "feedback": "Leave feedback",
602
             "feedback": "Leave feedback",
599
             "fullScreen": "Toggle full screen",
603
             "fullScreen": "Toggle full screen",
600
             "hangup": "Leave the call",
604
             "hangup": "Leave the call",
638
         "documentClose": "Close shared document",
642
         "documentClose": "Close shared document",
639
         "documentOpen": "Open shared document",
643
         "documentOpen": "Open shared document",
640
         "download": "Download our apps",
644
         "download": "Download our apps",
645
+        "e2ee": "End-to-End Encryption",
641
         "enterFullScreen": "View full screen",
646
         "enterFullScreen": "View full screen",
642
         "enterTileView": "Enter tile view",
647
         "enterTileView": "Enter tile view",
643
         "exitFullScreen": "Exit full screen",
648
         "exitFullScreen": "Exit full screen",

+ 14
- 0
react/features/analytics/AnalyticsEvents.js View File

257
     };
257
     };
258
 }
258
 }
259
 
259
 
260
+/**
261
+ * Creates an event indicating that an action related to E2EE occurred.
262
+ *
263
+ * @param {string} action - The action which occurred.
264
+ * @returns {Object} The event in a format suitable for sending via
265
+ * sendAnalytics.
266
+ */
267
+export function createE2EEEvent(action) {
268
+    return {
269
+        action,
270
+        actionSubject: 'e2ee'
271
+    };
272
+}
273
+
260
 /**
274
 /**
261
  * Creates an event which specifies that the feedback dialog has been opened.
275
  * Creates an event which specifies that the feedback dialog has been opened.
262
  *
276
  *

+ 5
- 0
react/features/base/conference/reducer.js View File

31
 
31
 
32
 const DEFAULT_STATE = {
32
 const DEFAULT_STATE = {
33
     conference: undefined,
33
     conference: undefined,
34
+    e2eeSupported: undefined,
34
     joining: undefined,
35
     joining: undefined,
35
     leaving: undefined,
36
     leaving: undefined,
36
     locked: undefined,
37
     locked: undefined,
175
     return assign(state, {
176
     return assign(state, {
176
         authRequired,
177
         authRequired,
177
         conference: undefined,
178
         conference: undefined,
179
+        e2eeSupported: undefined,
178
         error,
180
         error,
179
         joining: undefined,
181
         joining: undefined,
180
         leaving: undefined,
182
         leaving: undefined,
226
          * @type {JitsiConference}
228
          * @type {JitsiConference}
227
          */
229
          */
228
         conference,
230
         conference,
231
+
232
+        e2eeSupported: conference.isE2EESupported(),
233
+
229
         joining: undefined,
234
         joining: undefined,
230
         leaving: undefined,
235
         leaving: undefined,
231
 
236
 

+ 8
- 0
react/features/e2ee/actionTypes.js View File

1
+/**
2
+ * The type of the action which signals the E2EE key has changed.
3
+ *
4
+ * {
5
+ *     type: SET_E2EE_KEY
6
+ * }
7
+ */
8
+export const SET_E2EE_KEY = 'SET_E2EE_KEY';

+ 16
- 0
react/features/e2ee/actions.js View File

1
+// @flow
2
+
3
+import { SET_E2EE_KEY } from './actionTypes';
4
+
5
+/**
6
+ * Dispatches an action to set the E2EE key.
7
+ *
8
+ * @param {string|undefined} key - The new key to be used for E2EE.
9
+ * @returns {Object}
10
+ */
11
+export function setE2EEKey(key: ?string) {
12
+    return {
13
+        type: SET_E2EE_KEY,
14
+        key
15
+    };
16
+}

+ 76
- 0
react/features/e2ee/components/E2EEButton.js View File

1
+// @flow
2
+
3
+import React from 'react';
4
+
5
+import { createE2EEEvent, sendAnalytics } from '../../analytics';
6
+import { openDialog } from '../../base/dialog';
7
+import { translate } from '../../base/i18n';
8
+import { IconRoomUnlock } from '../../base/icons';
9
+import { connect } from '../../base/redux';
10
+import { AbstractButton, BetaTag } from '../../base/toolbox';
11
+import type { AbstractButtonProps } from '../../base/toolbox';
12
+
13
+import E2EEDialog from './E2EEDialog';
14
+
15
+
16
+type Props = AbstractButtonProps & {
17
+
18
+    /**
19
+     * The redux {@code dispatch} function.
20
+     */
21
+    dispatch: Function
22
+
23
+};
24
+
25
+/**
26
+ * Button that open a dialog to set the E2EE key.
27
+ */
28
+class E2EEButton extends AbstractButton<Props, *> {
29
+    accessibilityLabel = 'toolbar.accessibilityLabel.e2ee';
30
+    icon = IconRoomUnlock;
31
+    label = 'toolbar.e2ee';
32
+    tooltip = 'toolbar.e2ee';
33
+
34
+    /**
35
+     * Helper function to be implemented by subclasses, which returns
36
+     * a React Element to display (a beta tag) at the end of the button.
37
+     *
38
+     * @override
39
+     * @protected
40
+     * @returns {ReactElement}
41
+     */
42
+    _getElementAfter() {
43
+        return <BetaTag />;
44
+    }
45
+
46
+    /**
47
+     * Handles clicking / pressing the button, and opens the E2EE dialog.
48
+     *
49
+     * @protected
50
+     * @returns {void}
51
+     */
52
+    _handleClick() {
53
+        sendAnalytics(createE2EEEvent('dialog.open'));
54
+        this.props.dispatch(openDialog(E2EEDialog));
55
+    }
56
+}
57
+
58
+/**
59
+ * Maps (parts of) the redux state to the associated props for this component.
60
+ *
61
+ * @param {Object} state - The Redux state.
62
+ * @param {Props} ownProps - The own props of the Component.
63
+ * @private
64
+ * @returns {Props}
65
+ */
66
+export function mapStateToProps(state: Object, ownProps: Props) {
67
+    const { e2eeSupported } = state['features/base/conference'];
68
+    const { visible = Boolean(e2eeSupported) } = ownProps;
69
+
70
+    return {
71
+        visible
72
+    };
73
+}
74
+
75
+
76
+export default translate(connect(mapStateToProps)(E2EEButton));

+ 142
- 0
react/features/e2ee/components/E2EEDialog.js View File

1
+/* @flow */
2
+
3
+import React, { Component } from 'react';
4
+import type { Dispatch } from 'redux';
5
+import { FieldTextStateless as TextField } from '@atlaskit/field-text';
6
+
7
+import { createE2EEEvent, sendAnalytics } from '../../analytics';
8
+import { Dialog } from '../../base/dialog';
9
+import { translate, translateToHTML } from '../../base/i18n';
10
+import { connect } from '../../base/redux';
11
+
12
+import { setE2EEKey } from '../actions';
13
+
14
+
15
+type Props = {
16
+
17
+    /**
18
+     * The current E2EE key.
19
+     */
20
+    _key: string,
21
+
22
+    /**
23
+     * The redux {@code dispatch} function.
24
+     */
25
+    dispatch: Dispatch<any>,
26
+
27
+    /**
28
+     * Invoked to obtain translated strings.
29
+     */
30
+    t: Function
31
+};
32
+
33
+type State = {
34
+
35
+    /**
36
+     * The current E2EE key.
37
+     */
38
+    key: string
39
+};
40
+
41
+/**
42
+ * Implements a React {@code Component} for displaying a dialog with a field
43
+ * for setting the E2EE key.
44
+ *
45
+ * @extends Component
46
+ */
47
+class E2EEDialog extends Component<Props, State> {
48
+    /**
49
+     * Initializes a new {@code E2EEDialog  } instance.
50
+     *
51
+     * @param {Object} props - The read-only properties with which the new
52
+     * instance is to be initialized.
53
+     */
54
+    constructor(props: Props) {
55
+        super(props);
56
+
57
+        this.state = {
58
+            key: this.props._key
59
+        };
60
+
61
+        // Bind event handlers so they are only bound once for every instance.
62
+        this._onKeyChange = this._onKeyChange.bind(this);
63
+        this._onSubmit = this._onSubmit.bind(this);
64
+    }
65
+
66
+    /**
67
+     * Implements React's {@link Component#render()}.
68
+     *
69
+     * @inheritdoc
70
+     * @returns {ReactElement}
71
+     */
72
+    render() {
73
+        const { t } = this.props;
74
+
75
+        return (
76
+            <Dialog
77
+                isModal = { false }
78
+                onSubmit = { this._onSubmit }
79
+                titleKey = 'dialog.e2eeTitle'
80
+                width = 'small'>
81
+                <div className = 'e2ee-destription'>
82
+                    { translateToHTML(t, 'dialog.e2eeDescription') }
83
+                </div>
84
+                <TextField
85
+                    autoFocus = { true }
86
+                    compact = { true }
87
+                    label = { t('dialog.e2eeLabel') }
88
+                    name = 'e2eeKey'
89
+                    onChange = { this._onKeyChange }
90
+                    shouldFitContainer = { true }
91
+                    type = 'password'
92
+                    value = { this.state.key } />
93
+            </Dialog>);
94
+    }
95
+
96
+    _onKeyChange: (Object) => void;
97
+
98
+    /**
99
+     * Updates the entered key.
100
+     *
101
+     * @param {Object} event - The DOM event triggered from the entered value having changed.
102
+     * @private
103
+     * @returns {void}
104
+     */
105
+    _onKeyChange(event) {
106
+        this.setState({ key: event.target.value.trim() });
107
+    }
108
+
109
+    _onSubmit: () => boolean;
110
+
111
+    /**
112
+     * Dispatches an action to update the E2EE key.
113
+     *
114
+     * @private
115
+     * @returns {boolean}
116
+     */
117
+    _onSubmit() {
118
+        const { key } = this.state;
119
+
120
+        sendAnalytics(createE2EEEvent(`key.${key ? 'set' : 'unset'}`));
121
+        this.props.dispatch(setE2EEKey(key));
122
+
123
+        return true;
124
+    }
125
+}
126
+
127
+/**
128
+ * Maps (parts of) the Redux state to the associated props for this component.
129
+ *
130
+ * @param {Object} state - The Redux state.
131
+ * @private
132
+ * @returns {Props}
133
+ */
134
+function mapStateToProps(state) {
135
+    const { e2eeKey } = state['features/e2ee'];
136
+
137
+    return {
138
+        _key: e2eeKey || ''
139
+    };
140
+}
141
+
142
+export default translate(connect(mapStateToProps)(E2EEDialog));

+ 2
- 0
react/features/e2ee/components/index.js View File

1
+export { default as E2EEButton } from './E2EEButton';
2
+export { default as E2EEDialog } from './E2EEDialog';

+ 6
- 0
react/features/e2ee/index.js View File

1
+export * from './actions';
2
+export * from './actionTypes';
3
+export * from './components';
4
+
5
+import './middleware';
6
+import './reducer';

+ 5
- 0
react/features/e2ee/logger.js View File

1
+// @flow
2
+
3
+import { getLogger } from '../base/logging/functions';
4
+
5
+export default getLogger('features/e2ee');

+ 43
- 0
react/features/e2ee/middleware.js View File

1
+// @flow
2
+
3
+import { getCurrentConference } from '../base/conference';
4
+import { MiddlewareRegistry, StateListenerRegistry } from '../base/redux';
5
+
6
+import { SET_E2EE_KEY } from './actionTypes';
7
+import { setE2EEKey } from './actions';
8
+import logger from './logger';
9
+
10
+/**
11
+ * Middleware that captures actions related to E2EE.
12
+ *
13
+ * @param {Store} store - The redux store.
14
+ * @returns {Function}
15
+ */
16
+MiddlewareRegistry.register(({ getState }) => next => action => {
17
+    switch (action.type) {
18
+    case SET_E2EE_KEY: {
19
+        const conference = getCurrentConference(getState);
20
+
21
+        if (conference) {
22
+            logger.debug(`New E2EE key: ${action.key}`);
23
+            conference.setE2EEKey(action.key);
24
+        }
25
+
26
+        break;
27
+    }
28
+    }
29
+
30
+    return next(action);
31
+});
32
+
33
+/**
34
+ * Set up state change listener to perform maintenance tasks when the conference
35
+ * is left or failed.
36
+ */
37
+StateListenerRegistry.register(
38
+    state => getCurrentConference(state),
39
+    (conference, { dispatch }, previousConference) => {
40
+        if (previousConference) {
41
+            dispatch(setE2EEKey(undefined));
42
+        }
43
+    });

+ 29
- 0
react/features/e2ee/reducer.js View File

1
+// @flow
2
+
3
+import { ReducerRegistry } from '../base/redux';
4
+
5
+import { SET_E2EE_KEY } from './actionTypes';
6
+
7
+const DEFAULT_STATE = {
8
+
9
+    /**
10
+     * E2EE key.
11
+     */
12
+    e2eeKey: undefined
13
+};
14
+
15
+/**
16
+ * Reduces the Redux actions of the feature features/e2ee.
17
+ */
18
+ReducerRegistry.register('features/e2ee', (state = DEFAULT_STATE, action) => {
19
+    switch (action.type) {
20
+    case SET_E2EE_KEY:
21
+        return {
22
+            ...state,
23
+            e2eeKey: action.key
24
+        };
25
+
26
+    default:
27
+        return state;
28
+    }
29
+});

+ 5
- 0
react/features/toolbox/components/web/Toolbox.js View File

33
 import { getLocalVideoTrack, toggleScreensharing } from '../../../base/tracks';
33
 import { getLocalVideoTrack, toggleScreensharing } from '../../../base/tracks';
34
 import { VideoBlurButton } from '../../../blur';
34
 import { VideoBlurButton } from '../../../blur';
35
 import { ChatCounter, toggleChat } from '../../../chat';
35
 import { ChatCounter, toggleChat } from '../../../chat';
36
+import { E2EEButton } from '../../../e2ee';
36
 import { SharedDocumentButton } from '../../../etherpad';
37
 import { SharedDocumentButton } from '../../../etherpad';
37
 import { openFeedbackDialog } from '../../../feedback';
38
 import { openFeedbackDialog } from '../../../feedback';
38
 import {
39
 import {
1012
                     key = 'stats'
1013
                     key = 'stats'
1013
                     onClick = { this._onToolbarOpenSpeakerStats }
1014
                     onClick = { this._onToolbarOpenSpeakerStats }
1014
                     text = { t('toolbar.speakerStats') } />,
1015
                     text = { t('toolbar.speakerStats') } />,
1016
+            this._shouldShowButton('e2ee')
1017
+                && <E2EEButton
1018
+                    key = 'e2ee'
1019
+                    showLabel = { true } />,
1015
             this._shouldShowButton('feedback')
1020
             this._shouldShowButton('feedback')
1016
                 && _feedbackConfigured
1021
                 && _feedbackConfigured
1017
                 && <OverflowMenuItem
1022
                 && <OverflowMenuItem

Loading…
Cancel
Save