瀏覽代碼

feat(shared-video): Get allowed URL domains from config and dynamic branding.

factor2
Hristo Terezov 1 年之前
父節點
當前提交
5b4383d835

+ 6
- 2
config.js 查看文件

@@ -1443,8 +1443,12 @@ var config = {
1443 1443
     */
1444 1444
     // dynamicBrandingUrl: '',
1445 1445
 
1446
-    // Own url domains list added to the white listed domains for shared video
1447
-    // ownVideoURLDomains: [ '' ],
1446
+    // A list of allowed URL domains for shared video.
1447
+    //
1448
+    // NOTE:
1449
+    // '*' is allowed value and it will allow any URL to be used for shared video. We do not recommend using '*',
1450
+    // use it at your own risk!
1451
+    // sharedVideoAllowedURLDomains: [ ],
1448 1452
 
1449 1453
     // Options related to the participants pane.
1450 1454
     // participantsPane: {

+ 1
- 1
modules/API/API.js 查看文件

@@ -547,7 +547,7 @@ function initCommands() {
547 547
         },
548 548
         'start-share-video': url => {
549 549
             sendAnalytics(createApiEvent('share.video.start'));
550
-            const id = extractYoutubeIdOrURL(url);
550
+            const id = extractYoutubeIdOrURL(url, APP.store.getState()['features/shared-video'].allowedUrlDomains);
551 551
 
552 552
             if (id) {
553 553
                 APP.store.dispatch(playSharedVideo(id));

+ 1
- 1
react/features/base/config/configType.ts 查看文件

@@ -456,7 +456,6 @@ export interface IConfig {
456 456
     notifications?: Array<string>;
457 457
     openSharedDocumentOnJoin?: boolean;
458 458
     opusMaxAverageBitrate?: number;
459
-    ownVideoURLDomains?: Array<string>;
460 459
     p2p?: {
461 460
         backToP2PDelay?: number;
462 461
         codecPreferenceOrder?: Array<string>;
@@ -532,6 +531,7 @@ export interface IConfig {
532 531
         hideLobbyButton?: boolean;
533 532
     };
534 533
     serviceUrl?: string;
534
+    sharedVideoAllowedURLDomains?: Array<string>;
535 535
     sipInviteUrl?: string;
536 536
     speakerStats?: {
537 537
         disableSearch?: boolean;

+ 3
- 3
react/features/dynamic-branding/reducer.ts 查看文件

@@ -157,8 +157,8 @@ export interface IDynamicBrandingState {
157 157
     logoImageUrl: string;
158 158
     muiBrandedTheme?: boolean;
159 159
     premeetingBackground: string;
160
+    sharedVideoAllowedURLDomains?: Array<string>;
160 161
     showGiphyIntegration?: boolean;
161
-    urlWhitelist?: Array<string>;
162 162
     useDynamicBrandingData: boolean;
163 163
     virtualBackgrounds: Array<Image>;
164 164
 }
@@ -182,8 +182,8 @@ ReducerRegistry.register<IDynamicBrandingState>(STORE_NAME, (state = DEFAULT_STA
182 182
             logoImageUrl,
183 183
             muiBrandedTheme,
184 184
             premeetingBackground,
185
+            sharedVideoAllowedURLDomains,
185 186
             showGiphyIntegration,
186
-            urlWhitelist,
187 187
             virtualBackgrounds
188 188
         } = action.value;
189 189
 
@@ -200,10 +200,10 @@ ReducerRegistry.register<IDynamicBrandingState>(STORE_NAME, (state = DEFAULT_STA
200 200
             logoImageUrl,
201 201
             muiBrandedTheme,
202 202
             premeetingBackground,
203
+            sharedVideoAllowedURLDomains,
203 204
             showGiphyIntegration,
204 205
             customizationFailed: false,
205 206
             customizationReady: true,
206
-            urlWhitelist,
207 207
             useDynamicBrandingData: true,
208 208
             virtualBackgrounds: formatImages(virtualBackgrounds || [])
209 209
         };

+ 2
- 2
react/features/shared-video/actionTypes.ts 查看文件

@@ -33,7 +33,7 @@ export const SET_DISABLE_BUTTON = 'SET_DISABLE_BUTTON';
33 33
  * The type of the action which sets an array of whitelisted urls.
34 34
  *
35 35
  * {
36
- *     type: SET_URL_WHITELIST
36
+ *     type: SET_ALLOWED_URL_DOMAINS
37 37
  * }
38 38
  */
39
-export const SET_URL_WHITELIST = 'SET_URL_WHITELIST';
39
+export const SET_ALLOWED_URL_DOMAINS = 'SET_ALLOWED_URL_DOMAINS';

+ 10
- 8
react/features/shared-video/actions.any.ts 查看文件

@@ -3,7 +3,7 @@ import { getCurrentConference } from '../base/conference/functions';
3 3
 import { openDialog } from '../base/dialog/actions';
4 4
 import { getLocalParticipant } from '../base/participants/functions';
5 5
 
6
-import { RESET_SHARED_VIDEO_STATUS, SET_SHARED_VIDEO_STATUS, SET_URL_WHITELIST } from './actionTypes';
6
+import { RESET_SHARED_VIDEO_STATUS, SET_ALLOWED_URL_DOMAINS, SET_SHARED_VIDEO_STATUS } from './actionTypes';
7 7
 import { SharedVideoDialog } from './components';
8 8
 import { isSharedVideoEnabled, isURLAllowedForSharedVideo } from './functions';
9 9
 
@@ -90,7 +90,8 @@ export function stopSharedVideo() {
90 90
  */
91 91
 export function playSharedVideo(videoUrl: string) {
92 92
     return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
93
-        if (!isSharedVideoEnabled(getState()) || !isURLAllowedForSharedVideo(getState(), videoUrl)) {
93
+        if (!isSharedVideoEnabled(getState())
94
+            || !isURLAllowedForSharedVideo(videoUrl, getState()['features/shared-video'].allowedUrlDomains, true)) {
94 95
             return;
95 96
         }
96 97
         const conference = getCurrentConference(getState());
@@ -128,16 +129,17 @@ export function toggleSharedVideo() {
128 129
 }
129 130
 
130 131
 /**
131
- * Resets the status of the shared video.
132
+ * Sets the allowed URL domains of the shared video.
132 133
  *
133
- *@param {Array<string>} urlWhitelist - The new whitelist to be set.
134
+ * @param {Array<string>} allowedUrlDomains - The new whitelist to be set.
134 135
  * @returns {{
135
- *     type: SET_SHARED_VIDEO_STATUS,
136
+ *     type: SET_ALLOWED_URL_DOMAINS,
137
+ *     allowedUrlDomains: Array<string>
136 138
  * }}
137 139
  */
138
-export function setUrlWhitelist(urlWhitelist: Array<string>) {
140
+export function setAllowedUrlDomians(allowedUrlDomains: Array<string>) {
139 141
     return {
140
-        type: SET_URL_WHITELIST,
141
-        urlWhitelist
142
+        type: SET_ALLOWED_URL_DOMAINS,
143
+        allowedUrlDomains
142 144
     };
143 145
 }

+ 7
- 2
react/features/shared-video/components/AbstractSharedVideoDialog.tsx 查看文件

@@ -10,6 +10,11 @@ import { extractYoutubeIdOrURL } from '../functions';
10 10
  */
11 11
 export interface IProps extends WithTranslation {
12 12
 
13
+    /**
14
+     * The allowed URL domains for shared video.
15
+     */
16
+    _allowedUrlDomains: Array<string>;
17
+
13 18
     /**
14 19
      * Invoked to update the shared video link.
15 20
      */
@@ -48,9 +53,9 @@ export default class AbstractSharedVideoDialog<S> extends Component < IProps, S
48 53
      * @returns {boolean}
49 54
      */
50 55
     _onSetVideoLink(link: string) {
51
-        const { onPostSubmit } = this.props;
56
+        const { _allowedUrlDomains, onPostSubmit } = this.props;
52 57
 
53
-        const id = extractYoutubeIdOrURL(link);
58
+        const id = extractYoutubeIdOrURL(link, _allowedUrlDomains);
54 59
 
55 60
         if (!id) {
56 61
             return false;

+ 17
- 1
react/features/shared-video/components/native/SharedVideoDialog.tsx 查看文件

@@ -1,6 +1,7 @@
1 1
 import React from 'react';
2 2
 import { connect } from 'react-redux';
3 3
 
4
+import { IReduxState } from '../../../app/types';
4 5
 import InputDialog from '../../../base/dialog/components/native/InputDialog';
5 6
 import { translate } from '../../../base/i18n/functions';
6 7
 import AbstractSharedVideoDialog, { IProps } from '../AbstractSharedVideoDialog';
@@ -67,4 +68,19 @@ class SharedVideoDialog extends AbstractSharedVideoDialog<IState> {
67 68
     }
68 69
 }
69 70
 
70
-export default translate(connect()(SharedVideoDialog));
71
+/**
72
+ * Maps part of the Redux state to the props of this component.
73
+ *
74
+ * @param {Object} state - The Redux state.
75
+ * @private
76
+ * @returns {IProps}
77
+ */
78
+function mapStateToProps(state: IReduxState) {
79
+    const { allowedUrlDomains } = state['features/shared-video'];
80
+
81
+    return {
82
+        _allowedUrlDomains: allowedUrlDomains
83
+    };
84
+}
85
+
86
+export default translate(connect(mapStateToProps)(SharedVideoDialog));

+ 17
- 1
react/features/shared-video/components/web/SharedVideoDialog.tsx 查看文件

@@ -1,6 +1,7 @@
1 1
 import React from 'react';
2 2
 import { connect } from 'react-redux';
3 3
 
4
+import { IReduxState } from '../../../app/types';
4 5
 import { hideDialog } from '../../../base/dialog/actions';
5 6
 import { translate } from '../../../base/i18n/functions';
6 7
 import Dialog from '../../../base/ui/components/web/Dialog';
@@ -99,4 +100,19 @@ class SharedVideoDialog extends AbstractSharedVideoDialog<any> {
99 100
     }
100 101
 }
101 102
 
102
-export default translate(connect()(SharedVideoDialog));
103
+/**
104
+ * Maps part of the Redux state to the props of this component.
105
+ *
106
+ * @param {Object} state - The Redux state.
107
+ * @private
108
+ * @returns {IProps}
109
+ */
110
+function mapStateToProps(state: IReduxState) {
111
+    const { allowedUrlDomains } = state['features/shared-video'];
112
+
113
+    return {
114
+        _allowedUrlDomains: allowedUrlDomains
115
+    };
116
+}
117
+
118
+export default translate(connect(mapStateToProps)(SharedVideoDialog));

+ 7
- 2
react/features/shared-video/constants.ts 查看文件

@@ -35,6 +35,11 @@ export const PLAYBACK_STATUSES = {
35 35
 export const YOUTUBE_URL_DOMAIN = 'youtube.com';
36 36
 
37 37
 /**
38
- * The white listed domains for shared video.
38
+ * The constant to allow URL domains.
39 39
  */
40
-export const URL_WHITELIST = [ YOUTUBE_URL_DOMAIN ];
40
+export const ALLOW_ALL_URL_DOMAINS = '*';
41
+
42
+/**
43
+ * The default white listed domains for shared video.
44
+ */
45
+export const DEFAULT_ALLOWED_URL_DOMAINS = [ YOUTUBE_URL_DOMAIN ];

+ 17
- 23
react/features/shared-video/functions.ts 查看文件

@@ -3,7 +3,7 @@ import { getFakeParticipants } from '../base/participants/functions';
3 3
 import { toState } from '../base/redux/functions';
4 4
 
5 5
 import {
6
-    URL_WHITELIST,
6
+    ALLOW_ALL_URL_DOMAINS,
7 7
     VIDEO_PLAYER_PARTICIPANT_NAME,
8 8
     YOUTUBE_PLAYER_PARTICIPANT_NAME,
9 9
     YOUTUBE_URL_DOMAIN
@@ -63,9 +63,10 @@ export function isVideoPlaying(stateful: IStateful): boolean {
63 63
  * Extracts a Youtube id or URL from the user input.
64 64
  *
65 65
  * @param {string} input - The user input.
66
+ * @param {Array<string>} allowedUrlDomains - The allowed URL domains for shared video.
66 67
  * @returns {string|undefined}
67 68
  */
68
-export function extractYoutubeIdOrURL(input: string) {
69
+export function extractYoutubeIdOrURL(input: string, allowedUrlDomains?: Array<string>) {
69 70
     if (!input) {
70 71
         return;
71 72
     }
@@ -76,7 +77,7 @@ export function extractYoutubeIdOrURL(input: string) {
76 77
         return;
77 78
     }
78 79
 
79
-    if (areYoutubeURLsAllowedForSharedVideo()) {
80
+    if (areYoutubeURLsAllowedForSharedVideo(allowedUrlDomains)) {
80 81
         const youtubeId = getYoutubeId(trimmedLink);
81 82
 
82 83
         if (youtubeId) {
@@ -84,15 +85,7 @@ export function extractYoutubeIdOrURL(input: string) {
84 85
         }
85 86
     }
86 87
 
87
-    // Check if the URL is valid, native may crash otherwise.
88
-    try {
89
-        // eslint-disable-next-line no-new
90
-        const url = new URL(trimmedLink);
91
-
92
-        if (!URL_WHITELIST.includes(url?.hostname)) {
93
-            return;
94
-        }
95
-    } catch (_) {
88
+    if (!isURLAllowedForSharedVideo(trimmedLink, allowedUrlDomains)) {
96 89
         return;
97 90
     }
98 91
 
@@ -108,32 +101,33 @@ export function extractYoutubeIdOrURL(input: string) {
108 101
 export function isSharedVideoEnabled(stateful: IStateful) {
109 102
     const state = toState(stateful);
110 103
 
111
-    const { urlWhitelist = [] } = toState(stateful)['features/shared-video'];
104
+    const { allowedUrlDomains = [] } = toState(stateful)['features/shared-video'];
112 105
     const { disableThirdPartyRequests = false } = state['features/base/config'];
113 106
 
114
-    return !disableThirdPartyRequests && urlWhitelist.length > 0;
107
+    return !disableThirdPartyRequests && allowedUrlDomains.length > 0;
115 108
 }
116 109
 
117 110
 /**
118 111
  * Checks if you youtube URLs should be allowed for shared videos.
119 112
  *
113
+ * @param {Array<string>} allowedUrlDomains - The allowed URL domains for shared video.
120 114
  * @returns {boolean}
121 115
  */
122
-export function areYoutubeURLsAllowedForSharedVideo() {
123
-    return URL_WHITELIST.includes(YOUTUBE_URL_DOMAIN);
116
+export function areYoutubeURLsAllowedForSharedVideo(allowedUrlDomains?: Array<string>) {
117
+    return Boolean(allowedUrlDomains?.includes(YOUTUBE_URL_DOMAIN));
124 118
 }
125 119
 
126 120
 /**
127 121
  * Returns true if the passed url is allowed to be used for shared video or not.
128 122
  *
129
- * @param {IStateful} stateful - The redux store, state, or
130
- * {@code getState} function.
131 123
  * @param {string} url - The URL.
124
+ * @param {Array<string>} allowedUrlDomains - The allowed url domains.
125
+ * @param {boolean} considerNonURLsAllowedForYoututbe - If true, the invalid URLs will be considered youtube IDs
126
+ * and if youtube is allowed the function will return true.
132 127
  * @returns {boolean}
133 128
  */
134
-export function isURLAllowedForSharedVideo(stateful: IStateful, url: string) {
135
-    const { urlWhitelist } = toState(stateful)['features/shared-video'];
136
-
129
+export function isURLAllowedForSharedVideo(url: string,
130
+        allowedUrlDomains: Array<string> = [], considerNonURLsAllowedForYoututbe = false) {
137 131
     if (!url) {
138 132
         return false;
139 133
     }
@@ -142,10 +136,10 @@ export function isURLAllowedForSharedVideo(stateful: IStateful, url: string) {
142 136
         const urlObject = new URL(url);
143 137
 
144 138
         if ([ 'http:', 'https:' ].includes(urlObject?.protocol?.toLowerCase())) {
145
-            return urlWhitelist?.includes(urlObject?.hostname);
139
+            return allowedUrlDomains.includes(ALLOW_ALL_URL_DOMAINS) || allowedUrlDomains.includes(urlObject?.hostname);
146 140
         }
147 141
     } catch (_e) { // it should be YouTube id.
148
-        return urlWhitelist?.includes(YOUTUBE_URL_DOMAIN);
142
+        return considerNonURLsAllowedForYoututbe && allowedUrlDomains.includes(YOUTUBE_URL_DOMAIN);
149 143
     }
150 144
 
151 145
     return false;

+ 32
- 5
react/features/shared-video/middleware.any.ts 查看文件

@@ -4,19 +4,27 @@ import { IStore } from '../app/types';
4 4
 import { CONFERENCE_JOIN_IN_PROGRESS, CONFERENCE_LEFT } from '../base/conference/actionTypes';
5 5
 import { getCurrentConference } from '../base/conference/functions';
6 6
 import { IJitsiConference } from '../base/conference/reducer';
7
+import { SET_CONFIG } from '../base/config/actionTypes';
7 8
 import { MEDIA_TYPE } from '../base/media/constants';
8 9
 import { PARTICIPANT_LEFT } from '../base/participants/actionTypes';
9 10
 import { participantJoined, participantLeft, pinParticipant } from '../base/participants/actions';
10 11
 import { getLocalParticipant, getParticipantById } from '../base/participants/functions';
11 12
 import { FakeParticipant } from '../base/participants/types';
12 13
 import MiddlewareRegistry from '../base/redux/MiddlewareRegistry';
14
+import { SET_DYNAMIC_BRANDING_DATA } from '../dynamic-branding/actionTypes';
13 15
 
14 16
 import { RESET_SHARED_VIDEO_STATUS, SET_SHARED_VIDEO_STATUS } from './actionTypes';
15 17
 import {
16 18
     resetSharedVideoStatus,
19
+    setAllowedUrlDomians,
17 20
     setSharedVideoStatus
18 21
 } from './actions.any';
19
-import { PLAYBACK_STATUSES, SHARED_VIDEO, VIDEO_PLAYER_PARTICIPANT_NAME } from './constants';
22
+import {
23
+    DEFAULT_ALLOWED_URL_DOMAINS,
24
+    PLAYBACK_STATUSES,
25
+    SHARED_VIDEO,
26
+    VIDEO_PLAYER_PARTICIPANT_NAME
27
+} from './constants';
20 28
 import { isSharedVideoEnabled, isSharingStatus, isURLAllowedForSharedVideo } from './functions';
21 29
 import logger from './logger';
22 30
 
@@ -30,22 +38,22 @@ import logger from './logger';
30 38
  */
31 39
 MiddlewareRegistry.register(store => next => action => {
32 40
     const { dispatch, getState } = store;
33
-    const state = getState();
34 41
 
35
-    if (!isSharedVideoEnabled(state)) {
42
+    if (!isSharedVideoEnabled(getState())) {
36 43
         return next(action);
37 44
     }
38 45
 
39 46
     switch (action.type) {
40 47
     case CONFERENCE_JOIN_IN_PROGRESS: {
41 48
         const { conference } = action;
42
-        const localParticipantId = getLocalParticipant(state)?.id;
49
+        const localParticipantId = getLocalParticipant(getState())?.id;
43 50
 
44 51
         conference.addCommandListener(SHARED_VIDEO,
45 52
             ({ value, attributes }: { attributes: {
46 53
                 from: string; muted: string; state: string; time: string; }; value: string; }) => {
54
+                const state = getState();
47 55
 
48
-                if (!isURLAllowedForSharedVideo(state, value)) {
56
+                if (!isURLAllowedForSharedVideo(value, getState()['features/shared-video'].allowedUrlDomains, true)) {
49 57
                     logger.debug(`Shared Video: Received a not allowed URL ${value}`);
50 58
 
51 59
                     return;
@@ -72,9 +80,11 @@ MiddlewareRegistry.register(store => next => action => {
72 80
         break;
73 81
     }
74 82
     case CONFERENCE_LEFT:
83
+        dispatch(setAllowedUrlDomians(DEFAULT_ALLOWED_URL_DOMAINS));
75 84
         dispatch(resetSharedVideoStatus());
76 85
         break;
77 86
     case PARTICIPANT_LEFT: {
87
+        const state = getState();
78 88
         const conference = getCurrentConference(state);
79 89
         const { ownerId: stateOwnerId, videoUrl: statevideoUrl } = state['features/shared-video'];
80 90
 
@@ -86,7 +96,23 @@ MiddlewareRegistry.register(store => next => action => {
86 96
         }
87 97
         break;
88 98
     }
99
+    case SET_CONFIG:
100
+    case SET_DYNAMIC_BRANDING_DATA: {
101
+        const result = next(action);
102
+        const state = getState();
103
+        const { sharedVideoAllowedURLDomains: allowedURLDomainsFromConfig = [] } = state['features/base/config'];
104
+        const { sharedVideoAllowedURLDomains: allowedURLDomainsFromBranding = [] } = state['features/dynamic-branding'];
105
+
106
+        dispatch(setAllowedUrlDomians([
107
+            ...DEFAULT_ALLOWED_URL_DOMAINS,
108
+            ...allowedURLDomainsFromBranding,
109
+            ...allowedURLDomainsFromConfig
110
+        ]));
111
+
112
+        return result;
113
+    }
89 114
     case SET_SHARED_VIDEO_STATUS: {
115
+        const state = getState();
90 116
         const conference = getCurrentConference(state);
91 117
         const localParticipantId = getLocalParticipant(state)?.id;
92 118
         const { videoUrl, status, ownerId, time, muted, volume } = action;
@@ -112,6 +138,7 @@ MiddlewareRegistry.register(store => next => action => {
112 138
         break;
113 139
     }
114 140
     case RESET_SHARED_VIDEO_STATUS: {
141
+        const state = getState();
115 142
         const localParticipantId = getLocalParticipant(state)?.id;
116 143
         const { ownerId: stateOwnerId, videoUrl: statevideoUrl } = state['features/shared-video'];
117 144
 

+ 13
- 8
react/features/shared-video/reducer.ts 查看文件

@@ -2,21 +2,23 @@ import ReducerRegistry from '../base/redux/ReducerRegistry';
2 2
 
3 3
 import {
4 4
     RESET_SHARED_VIDEO_STATUS,
5
+    SET_ALLOWED_URL_DOMAINS,
5 6
     SET_DISABLE_BUTTON,
6
-    SET_SHARED_VIDEO_STATUS,
7
-    SET_URL_WHITELIST
7
+    SET_SHARED_VIDEO_STATUS
8 8
 } from './actionTypes';
9
-import { YOUTUBE_URL_DOMAIN } from './constants';
9
+import { DEFAULT_ALLOWED_URL_DOMAINS } from './constants';
10 10
 
11
-const initialState = {};
11
+const initialState = {
12
+    allowedUrlDomains: DEFAULT_ALLOWED_URL_DOMAINS
13
+};
12 14
 
13 15
 export interface ISharedVideoState {
16
+    allowedUrlDomains: Array<string>;
14 17
     disabled?: boolean;
15 18
     muted?: boolean;
16 19
     ownerId?: string;
17 20
     status?: string;
18 21
     time?: number;
19
-    urlWhitelist?: Array<string>;
20 22
     videoUrl?: string;
21 23
     volume?: number;
22 24
 }
@@ -30,7 +32,10 @@ ReducerRegistry.register<ISharedVideoState>('features/shared-video',
30 32
 
31 33
     switch (action.type) {
32 34
     case RESET_SHARED_VIDEO_STATUS:
33
-        return initialState;
35
+        return {
36
+            ...initialState,
37
+            allowedUrlDomains: state.allowedUrlDomains
38
+        };
34 39
     case SET_SHARED_VIDEO_STATUS:
35 40
         return {
36 41
             ...state,
@@ -48,10 +53,10 @@ ReducerRegistry.register<ISharedVideoState>('features/shared-video',
48 53
             disabled
49 54
         };
50 55
 
51
-    case SET_URL_WHITELIST: {
56
+    case SET_ALLOWED_URL_DOMAINS: {
52 57
         return {
53 58
             ...state,
54
-            urlWhitelist: [ YOUTUBE_URL_DOMAIN, ...action.urlWhitelist ]
59
+            allowedUrlDomains: action.allowedUrlDomains
55 60
         };
56 61
     }
57 62
 

Loading…
取消
儲存