Skip to content

Commit

Permalink
feat(shared-video): Get allowed URL domains from config and dynamic b…
Browse files Browse the repository at this point in the history
…randing.
  • Loading branch information
hristoterezov committed Aug 26, 2024
1 parent 49fa243 commit 5b4383d
Show file tree
Hide file tree
Showing 13 changed files with 133 additions and 59 deletions.
8 changes: 6 additions & 2 deletions config.js
Original file line number Diff line number Diff line change
Expand Up @@ -1443,8 +1443,12 @@ var config = {
*/
// dynamicBrandingUrl: '',

// Own url domains list added to the white listed domains for shared video
// ownVideoURLDomains: [ '' ],
// A list of allowed URL domains for shared video.
//
// NOTE:
// '*' is allowed value and it will allow any URL to be used for shared video. We do not recommend using '*',
// use it at your own risk!
// sharedVideoAllowedURLDomains: [ ],

// Options related to the participants pane.
// participantsPane: {
Expand Down
2 changes: 1 addition & 1 deletion modules/API/API.js
Original file line number Diff line number Diff line change
Expand Up @@ -547,7 +547,7 @@ function initCommands() {
},
'start-share-video': url => {
sendAnalytics(createApiEvent('share.video.start'));
const id = extractYoutubeIdOrURL(url);
const id = extractYoutubeIdOrURL(url, APP.store.getState()['features/shared-video'].allowedUrlDomains);

if (id) {
APP.store.dispatch(playSharedVideo(id));
Expand Down
2 changes: 1 addition & 1 deletion react/features/base/config/configType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -456,7 +456,6 @@ export interface IConfig {
notifications?: Array<string>;
openSharedDocumentOnJoin?: boolean;
opusMaxAverageBitrate?: number;
ownVideoURLDomains?: Array<string>;
p2p?: {
backToP2PDelay?: number;
codecPreferenceOrder?: Array<string>;
Expand Down Expand Up @@ -532,6 +531,7 @@ export interface IConfig {
hideLobbyButton?: boolean;
};
serviceUrl?: string;
sharedVideoAllowedURLDomains?: Array<string>;
sipInviteUrl?: string;
speakerStats?: {
disableSearch?: boolean;
Expand Down
6 changes: 3 additions & 3 deletions react/features/dynamic-branding/reducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,8 +157,8 @@ export interface IDynamicBrandingState {
logoImageUrl: string;
muiBrandedTheme?: boolean;
premeetingBackground: string;
sharedVideoAllowedURLDomains?: Array<string>;
showGiphyIntegration?: boolean;
urlWhitelist?: Array<string>;
useDynamicBrandingData: boolean;
virtualBackgrounds: Array<Image>;
}
Expand All @@ -182,8 +182,8 @@ ReducerRegistry.register<IDynamicBrandingState>(STORE_NAME, (state = DEFAULT_STA
logoImageUrl,
muiBrandedTheme,
premeetingBackground,
sharedVideoAllowedURLDomains,
showGiphyIntegration,
urlWhitelist,
virtualBackgrounds
} = action.value;

Expand All @@ -200,10 +200,10 @@ ReducerRegistry.register<IDynamicBrandingState>(STORE_NAME, (state = DEFAULT_STA
logoImageUrl,
muiBrandedTheme,
premeetingBackground,
sharedVideoAllowedURLDomains,
showGiphyIntegration,
customizationFailed: false,
customizationReady: true,
urlWhitelist,
useDynamicBrandingData: true,
virtualBackgrounds: formatImages(virtualBackgrounds || [])
};
Expand Down
4 changes: 2 additions & 2 deletions react/features/shared-video/actionTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export const SET_DISABLE_BUTTON = 'SET_DISABLE_BUTTON';
* The type of the action which sets an array of whitelisted urls.
*
* {
* type: SET_URL_WHITELIST
* type: SET_ALLOWED_URL_DOMAINS
* }
*/
export const SET_URL_WHITELIST = 'SET_URL_WHITELIST';
export const SET_ALLOWED_URL_DOMAINS = 'SET_ALLOWED_URL_DOMAINS';
18 changes: 10 additions & 8 deletions react/features/shared-video/actions.any.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { getCurrentConference } from '../base/conference/functions';
import { openDialog } from '../base/dialog/actions';
import { getLocalParticipant } from '../base/participants/functions';

import { RESET_SHARED_VIDEO_STATUS, SET_SHARED_VIDEO_STATUS, SET_URL_WHITELIST } from './actionTypes';
import { RESET_SHARED_VIDEO_STATUS, SET_ALLOWED_URL_DOMAINS, SET_SHARED_VIDEO_STATUS } from './actionTypes';
import { SharedVideoDialog } from './components';
import { isSharedVideoEnabled, isURLAllowedForSharedVideo } from './functions';

Expand Down Expand Up @@ -90,7 +90,8 @@ export function stopSharedVideo() {
*/
export function playSharedVideo(videoUrl: string) {
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
if (!isSharedVideoEnabled(getState()) || !isURLAllowedForSharedVideo(getState(), videoUrl)) {
if (!isSharedVideoEnabled(getState())
|| !isURLAllowedForSharedVideo(videoUrl, getState()['features/shared-video'].allowedUrlDomains, true)) {
return;
}
const conference = getCurrentConference(getState());
Expand Down Expand Up @@ -128,16 +129,17 @@ export function toggleSharedVideo() {
}

/**
* Resets the status of the shared video.
* Sets the allowed URL domains of the shared video.
*
*@param {Array<string>} urlWhitelist - The new whitelist to be set.
* @param {Array<string>} allowedUrlDomains - The new whitelist to be set.
* @returns {{
* type: SET_SHARED_VIDEO_STATUS,
* type: SET_ALLOWED_URL_DOMAINS,
* allowedUrlDomains: Array<string>
* }}
*/
export function setUrlWhitelist(urlWhitelist: Array<string>) {
export function setAllowedUrlDomians(allowedUrlDomains: Array<string>) {
return {
type: SET_URL_WHITELIST,
urlWhitelist
type: SET_ALLOWED_URL_DOMAINS,
allowedUrlDomains
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ import { extractYoutubeIdOrURL } from '../functions';
*/
export interface IProps extends WithTranslation {

/**
* The allowed URL domains for shared video.
*/
_allowedUrlDomains: Array<string>;

/**
* Invoked to update the shared video link.
*/
Expand Down Expand Up @@ -48,9 +53,9 @@ export default class AbstractSharedVideoDialog<S> extends Component < IProps, S
* @returns {boolean}
*/
_onSetVideoLink(link: string) {
const { onPostSubmit } = this.props;
const { _allowedUrlDomains, onPostSubmit } = this.props;

const id = extractYoutubeIdOrURL(link);
const id = extractYoutubeIdOrURL(link, _allowedUrlDomains);

if (!id) {
return false;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React from 'react';
import { connect } from 'react-redux';

import { IReduxState } from '../../../app/types';
import InputDialog from '../../../base/dialog/components/native/InputDialog';
import { translate } from '../../../base/i18n/functions';
import AbstractSharedVideoDialog, { IProps } from '../AbstractSharedVideoDialog';
Expand Down Expand Up @@ -67,4 +68,19 @@ class SharedVideoDialog extends AbstractSharedVideoDialog<IState> {
}
}

export default translate(connect()(SharedVideoDialog));
/**
* Maps part of the Redux state to the props of this component.
*
* @param {Object} state - The Redux state.
* @private
* @returns {IProps}
*/
function mapStateToProps(state: IReduxState) {
const { allowedUrlDomains } = state['features/shared-video'];

return {
_allowedUrlDomains: allowedUrlDomains
};
}

export default translate(connect(mapStateToProps)(SharedVideoDialog));
18 changes: 17 additions & 1 deletion react/features/shared-video/components/web/SharedVideoDialog.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React from 'react';
import { connect } from 'react-redux';

import { IReduxState } from '../../../app/types';
import { hideDialog } from '../../../base/dialog/actions';
import { translate } from '../../../base/i18n/functions';
import Dialog from '../../../base/ui/components/web/Dialog';
Expand Down Expand Up @@ -99,4 +100,19 @@ class SharedVideoDialog extends AbstractSharedVideoDialog<any> {
}
}

export default translate(connect()(SharedVideoDialog));
/**
* Maps part of the Redux state to the props of this component.
*
* @param {Object} state - The Redux state.
* @private
* @returns {IProps}
*/
function mapStateToProps(state: IReduxState) {
const { allowedUrlDomains } = state['features/shared-video'];

return {
_allowedUrlDomains: allowedUrlDomains
};
}

export default translate(connect(mapStateToProps)(SharedVideoDialog));
9 changes: 7 additions & 2 deletions react/features/shared-video/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ export const PLAYBACK_STATUSES = {
export const YOUTUBE_URL_DOMAIN = 'youtube.com';

/**
* The white listed domains for shared video.
* The constant to allow URL domains.
*/
export const URL_WHITELIST = [ YOUTUBE_URL_DOMAIN ];
export const ALLOW_ALL_URL_DOMAINS = '*';

/**
* The default white listed domains for shared video.
*/
export const DEFAULT_ALLOWED_URL_DOMAINS = [ YOUTUBE_URL_DOMAIN ];
40 changes: 17 additions & 23 deletions react/features/shared-video/functions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { getFakeParticipants } from '../base/participants/functions';
import { toState } from '../base/redux/functions';

import {
URL_WHITELIST,
ALLOW_ALL_URL_DOMAINS,
VIDEO_PLAYER_PARTICIPANT_NAME,
YOUTUBE_PLAYER_PARTICIPANT_NAME,
YOUTUBE_URL_DOMAIN
Expand Down Expand Up @@ -63,9 +63,10 @@ export function isVideoPlaying(stateful: IStateful): boolean {
* Extracts a Youtube id or URL from the user input.
*
* @param {string} input - The user input.
* @param {Array<string>} allowedUrlDomains - The allowed URL domains for shared video.
* @returns {string|undefined}
*/
export function extractYoutubeIdOrURL(input: string) {
export function extractYoutubeIdOrURL(input: string, allowedUrlDomains?: Array<string>) {
if (!input) {
return;
}
Expand All @@ -76,23 +77,15 @@ export function extractYoutubeIdOrURL(input: string) {
return;
}

if (areYoutubeURLsAllowedForSharedVideo()) {
if (areYoutubeURLsAllowedForSharedVideo(allowedUrlDomains)) {
const youtubeId = getYoutubeId(trimmedLink);

if (youtubeId) {
return youtubeId;
}
}

// Check if the URL is valid, native may crash otherwise.
try {
// eslint-disable-next-line no-new
const url = new URL(trimmedLink);

if (!URL_WHITELIST.includes(url?.hostname)) {
return;
}
} catch (_) {
if (!isURLAllowedForSharedVideo(trimmedLink, allowedUrlDomains)) {
return;
}

Expand All @@ -108,32 +101,33 @@ export function extractYoutubeIdOrURL(input: string) {
export function isSharedVideoEnabled(stateful: IStateful) {
const state = toState(stateful);

const { urlWhitelist = [] } = toState(stateful)['features/shared-video'];
const { allowedUrlDomains = [] } = toState(stateful)['features/shared-video'];
const { disableThirdPartyRequests = false } = state['features/base/config'];

return !disableThirdPartyRequests && urlWhitelist.length > 0;
return !disableThirdPartyRequests && allowedUrlDomains.length > 0;
}

/**
* Checks if you youtube URLs should be allowed for shared videos.
*
* @param {Array<string>} allowedUrlDomains - The allowed URL domains for shared video.
* @returns {boolean}
*/
export function areYoutubeURLsAllowedForSharedVideo() {
return URL_WHITELIST.includes(YOUTUBE_URL_DOMAIN);
export function areYoutubeURLsAllowedForSharedVideo(allowedUrlDomains?: Array<string>) {
return Boolean(allowedUrlDomains?.includes(YOUTUBE_URL_DOMAIN));
}

/**
* Returns true if the passed url is allowed to be used for shared video or not.
*
* @param {IStateful} stateful - The redux store, state, or
* {@code getState} function.
* @param {string} url - The URL.
* @param {Array<string>} allowedUrlDomains - The allowed url domains.
* @param {boolean} considerNonURLsAllowedForYoututbe - If true, the invalid URLs will be considered youtube IDs
* and if youtube is allowed the function will return true.
* @returns {boolean}
*/
export function isURLAllowedForSharedVideo(stateful: IStateful, url: string) {
const { urlWhitelist } = toState(stateful)['features/shared-video'];

export function isURLAllowedForSharedVideo(url: string,
allowedUrlDomains: Array<string> = [], considerNonURLsAllowedForYoututbe = false) {
if (!url) {
return false;
}
Expand All @@ -142,10 +136,10 @@ export function isURLAllowedForSharedVideo(stateful: IStateful, url: string) {
const urlObject = new URL(url);

if ([ 'http:', 'https:' ].includes(urlObject?.protocol?.toLowerCase())) {
return urlWhitelist?.includes(urlObject?.hostname);
return allowedUrlDomains.includes(ALLOW_ALL_URL_DOMAINS) || allowedUrlDomains.includes(urlObject?.hostname);
}
} catch (_e) { // it should be YouTube id.
return urlWhitelist?.includes(YOUTUBE_URL_DOMAIN);
return considerNonURLsAllowedForYoututbe && allowedUrlDomains.includes(YOUTUBE_URL_DOMAIN);
}

return false;
Expand Down
Loading

0 comments on commit 5b4383d

Please sign in to comment.