From 9941a5d349721fd3053797e166d06cbdcb7ef521 Mon Sep 17 00:00:00 2001 From: Dennis Benz Date: Wed, 5 Jul 2023 15:13:03 +0200 Subject: [PATCH 1/4] Add callback url for integration (#1084) After finishing the editing, the user can now navigate back to the calling system using a button. For this, the integrating system can pass a `callbackUrl` url parameter to provide a return link. In the editor settings, the prefixes of the allowed callback urls must be provided to prevent malicious callback urls. --- README.md | 27 ++++++++++++++------------- editor-settings.toml | 17 +++++++++++++++++ public/editor-settings.toml | 4 ++++ src/config.ts | 23 ++++++++++++++++++++++- src/i18n/locales/de-DE.json | 3 ++- src/i18n/locales/en-US.json | 3 ++- src/main/Finish.tsx | 34 +++++++++++++++++++++++++++++++++- src/main/Save.tsx | 3 ++- src/main/TheEnd.tsx | 2 ++ 9 files changed, 98 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index d2c15039c..7af198257 100644 --- a/README.md +++ b/README.md @@ -65,19 +65,20 @@ If an option can be specified both ways, the URL parameter will always take prec Options which are usually specified in the configuration file are documented in there as well. Metadata configuration options are only documented in the configuration file. -| Option | URL | File | Description -| --------------------|-----|------|------------ -| id | ✓ | ✓ | Id of the event that the editor should open by default. -| mediaPackageId | ✓ | ✓ | Deprecated. Use `id` instead. -| opencast.url | ✗ | ✓ | URL of the opencast server to connect to. -| opencast.name | ✗ | ✓ | Opencast user to use. For demo purposes only. -| opencast.password | ✗ | ✓ | Password to use for authentication. For demo purposes only. -| metadata.show | ✓ | ✓ | Show metadata tab. -| trackSelection.show | ✓ | ✓ | Show track selection tab. -| thumbnail.show | ✓ | ✓ | Show thumbnail tab. Demo only. -| debug | ✓ | ✗ | Enable internationalization debugging. -| lng | ✓ | ✗ | Select a specific language. Use language codes like `de` or `en-US`. - +| Option | URL | File | Description | +|-------------------------|-----|------|----------------------------------------------------------------------| +| id | ✓ | ✓ | Id of the event that the editor should open by default. | +| mediaPackageId | ✓ | ✓ | Deprecated. Use `id` instead. | +| allowedCallbackPrefixes | ✗ | ✓ | Allowed callback prefixes in callback url. | +| callbackUrl | ✓ | ✓ | Callback url to go back after finish. | +| opencast.url | ✗ | ✓ | URL of the opencast server to connect to. | +| opencast.name | ✗ | ✓ | Opencast user to use. For demo purposes only. | +| opencast.password | ✗ | ✓ | Password to use for authentication. For demo purposes only. | +| metadata.show | ✓ | ✓ | Show metadata tab. | +| trackSelection.show | ✓ | ✓ | Show track selection tab. | +| thumbnail.show | ✓ | ✓ | Show thumbnail tab. Demo only. | +| debug | ✓ | ✗ | Enable internationalization debugging. | +| lng | ✓ | ✗ | Select a specific language. Use language codes like `de` or `en-US`. | How to cut a release for Opencast --------------------------------- diff --git a/editor-settings.toml b/editor-settings.toml index 1935a1abe..0242cdc29 100644 --- a/editor-settings.toml +++ b/editor-settings.toml @@ -8,6 +8,23 @@ # ⚠️ When deployed, this file is publicly accessibly! +#### +# General Settings +## + +# Allowed prefixes in callback urls to prevent malicious urls +# If empty, no callback url is allowed +# Type: string[] +# Default: [] +#allowedCallbackDomains = [] + +# Url to go back after finishing editing +# If undefined, no return link will be shown on the end pages +# Type: string | undefined +# Default: undefined +#callbackUrl = + + #### # Metadata ## diff --git a/public/editor-settings.toml b/public/editor-settings.toml index 0923b0d84..b4519cf84 100644 --- a/public/editor-settings.toml +++ b/public/editor-settings.toml @@ -11,6 +11,10 @@ # Pick a default event identifier which should be on develop.opencast.org id = 'ID-dual-stream-demo' +# Callback to develop.opencast.org +allowedCallbackPrefixes = ["https://develop.opencast.org"] +callbackUrl = "https://develop.opencast.org" + [opencast] # Connect to develop.opencast.org and use the default demo user url = 'https://develop.opencast.org' diff --git a/src/config.ts b/src/config.ts index c932b2b83..5c58fd171 100644 --- a/src/config.ts +++ b/src/config.ts @@ -41,6 +41,8 @@ export interface subtitleTags { */ interface iSettings { id: string | undefined, + allowedCallbackPrefixes: string[], + callbackUrl: string | undefined, opencast: { url: string, name: string | undefined, @@ -76,6 +78,8 @@ interface iSettings { */ const defaultSettings: iSettings = { id: undefined, + allowedCallbackPrefixes: [], + callbackUrl: undefined, opencast: { url: window.location.origin, name: undefined, @@ -136,7 +140,7 @@ export const init = async () => { // Create empty objects for full path (if the key contains '.') and set // the value at the end. let obj : {[k: string]: any} = rawUrlSettings; - if (key.startsWith('opencast.')) { + if (key.startsWith('opencast.') || key === 'allowedCallbackDomains') { return; } @@ -163,6 +167,11 @@ export const init = async () => { // Prepare local setting to avoid complicated checks later settings.opencast.local = settings.opencast.local && settings.opencast.url === window.location.origin; + // Prevent malicious callback urls + settings.callbackUrl = settings.allowedCallbackPrefixes.some( + p => settings.callbackUrl?.startsWith(p) + ) ? settings.callbackUrl : undefined; + // Configure hotkeys configure({ ignoreTags: [], // Do not ignore hotkeys when focused on a textarea, input, select @@ -321,6 +330,16 @@ const types = { throw new Error("is not a boolean"); } }, + 'array': (v: any, _allowParse: any) => { + if (!Array.isArray(v)) { + throw new Error("is not an array, but should be"); + } + for (const entry in v) { + if (typeof entry !== 'string') { + throw new Error("is not a string, but should be"); + } + } + }, 'map': (v: any, _allowParse: any) => { for (const key in v) { if (typeof key !== 'string') { @@ -368,6 +387,8 @@ const types = { // above for some examples. const SCHEMA = { id: types.string, + allowedCallbackPrefixes: types.array, + callbackUrl: types.string, opencast: { url: types.string, name: types.string, diff --git a/src/i18n/locales/de-DE.json b/src/i18n/locales/de-DE.json index d43d94192..98218d2e8 100644 --- a/src/i18n/locales/de-DE.json +++ b/src/i18n/locales/de-DE.json @@ -201,7 +201,8 @@ "various": { "error-details-text": "Details: {{errorMessage}}\n", "error-text": "Ein Fehler ist aufgetreten. Bitte versuchen Sie es später noch einmal.", - "goBack-button": "Nein, zurück" + "goBack-button": "Nein, zurück", + "callback-button": "Zurück zum vorigen System" }, "trackSelection": { "title": "Spur(en) für die Verarbeitung auswählen", diff --git a/src/i18n/locales/en-US.json b/src/i18n/locales/en-US.json index d8a7a0d58..9acae4404 100644 --- a/src/i18n/locales/en-US.json +++ b/src/i18n/locales/en-US.json @@ -216,7 +216,8 @@ "various": { "error-details-text": "Details: {{errorMessage}}\n", "error-text": "An error has occurred. Please wait a bit and try again.", - "goBack-button": "No, take me back" + "goBack-button": "No, take me back", + "callback-button": "Back to previous system" }, "trackSelection": { diff --git a/src/main/Finish.tsx b/src/main/Finish.tsx index 39439b3f8..547e7917e 100644 --- a/src/main/Finish.tsx +++ b/src/main/Finish.tsx @@ -7,13 +7,15 @@ import WorkflowSelection from "./WorkflowSelection"; import WorkflowConfiguration from "./WorkflowConfiguration"; import { css } from '@emotion/react' -import { basicButtonStyle } from '../cssStyles' +import { basicButtonStyle, navigationButtonStyle } from '../cssStyles' import { IconType } from "react-icons"; import { useDispatch, useSelector } from 'react-redux'; import { selectPageNumber, setPageNumber } from '../redux/finishSlice'; import { useTheme } from "../themes"; +import { settings } from "../config"; +import { useTranslation } from "react-i18next"; /** * Displays a menu for selecting what should be done with the current changes @@ -86,5 +88,35 @@ export const PageButton : React.FC<{pageNumber: number, label: string, Icon: Ico ); } +/** + * Takes you back to the callback url resource + */ +export const CallbackButton : React.FC = () => { + + const { t } = useTranslation(); + + const theme = useSelector(selectTheme); + + const openCallbackUrl = () => { + window.open(settings.callbackUrl, "_self"); + } + + return ( + <> + {settings.callbackUrl !== undefined && +
) => { if (event.key === " " || event.key === "Enter") { + openCallbackUrl() + } }}> + + {t("various.callback-button")} +
+ } + + ); +} + export default Finish; diff --git a/src/main/Save.tsx b/src/main/Save.tsx index 816c38bbe..87346b29a 100644 --- a/src/main/Save.tsx +++ b/src/main/Save.tsx @@ -11,7 +11,7 @@ import { selectFinishState } from '../redux/finishSlice' import { selectHasChanges, selectSegments, selectTracks, setHasChanges as videoSetHasChanges } from '../redux/videoSlice' import { postVideoInformation, selectStatus, selectError } from '../redux/workflowPostSlice' -import { PageButton } from './Finish' +import { CallbackButton, PageButton } from './Finish' import { useTranslation } from 'react-i18next'; import { AppDispatch } from "../redux/store"; @@ -58,6 +58,7 @@ const Save : React.FC = () => { <>
{t("save.success-text")}
+ ) // Pre save diff --git a/src/main/TheEnd.tsx b/src/main/TheEnd.tsx index 9b5e67af8..ffcec89b7 100644 --- a/src/main/TheEnd.tsx +++ b/src/main/TheEnd.tsx @@ -11,6 +11,7 @@ import { basicButtonStyle, flexGapReplacementStyle, navigationButtonStyle } from import { useTranslation } from 'react-i18next'; import { useTheme } from "../themes"; import { ThemedTooltip } from "./Tooltip"; +import { CallbackButton } from "./Finish"; /** * This page is to be displayed when the user is "done" with the editor @@ -46,6 +47,7 @@ const TheEnd : React.FC = () => { {endState === 'discarded' ? : }
{text()}
{(endState === 'discarded') && } + ); } From 7e4a4d838146d0a84e3e58da85a38580b04393e2 Mon Sep 17 00:00:00 2001 From: Dennis Benz Date: Thu, 6 Jul 2023 08:43:43 +0200 Subject: [PATCH 2/4] Fix inconsistent option of allowed callback prefixes --- editor-settings.toml | 2 +- src/config.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/editor-settings.toml b/editor-settings.toml index 0242cdc29..3787c58cf 100644 --- a/editor-settings.toml +++ b/editor-settings.toml @@ -16,7 +16,7 @@ # If empty, no callback url is allowed # Type: string[] # Default: [] -#allowedCallbackDomains = [] +#allowedCallbackPrefixes = [] # Url to go back after finishing editing # If undefined, no return link will be shown on the end pages diff --git a/src/config.ts b/src/config.ts index 5c58fd171..8a8cb25ae 100644 --- a/src/config.ts +++ b/src/config.ts @@ -140,7 +140,7 @@ export const init = async () => { // Create empty objects for full path (if the key contains '.') and set // the value at the end. let obj : {[k: string]: any} = rawUrlSettings; - if (key.startsWith('opencast.') || key === 'allowedCallbackDomains') { + if (key.startsWith('opencast.') || key === 'allowedCallbackPrefixes') { return; } From 4c8ac9719c810f30f8c7a0faa5e7070e5cb76c77 Mon Sep 17 00:00:00 2001 From: Dennis Benz Date: Wed, 20 Sep 2023 15:09:12 +0200 Subject: [PATCH 3/4] Fix icon and style of callback button --- src/main/Finish.tsx | 6 ++++-- src/main/TheEnd.tsx | 12 ++++++++++-- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/main/Finish.tsx b/src/main/Finish.tsx index 547e7917e..a54a22c7f 100644 --- a/src/main/Finish.tsx +++ b/src/main/Finish.tsx @@ -6,6 +6,8 @@ import Discard from "./Discard" import WorkflowSelection from "./WorkflowSelection"; import WorkflowConfiguration from "./WorkflowConfiguration"; +import { LuDoorOpen} from "react-icons/lu"; + import { css } from '@emotion/react' import { basicButtonStyle, navigationButtonStyle } from '../cssStyles' @@ -95,7 +97,7 @@ export const CallbackButton : React.FC = () => { const { t } = useTranslation(); - const theme = useSelector(selectTheme); + const theme = useTheme(); const openCallbackUrl = () => { window.open(settings.callbackUrl, "_self"); @@ -110,7 +112,7 @@ export const CallbackButton : React.FC = () => { onKeyDown={(event: React.KeyboardEvent) => { if (event.key === " " || event.key === "Enter") { openCallbackUrl() } }}> - + {t("various.callback-button")} } diff --git a/src/main/TheEnd.tsx b/src/main/TheEnd.tsx index ffcec89b7..de5da41ae 100644 --- a/src/main/TheEnd.tsx +++ b/src/main/TheEnd.tsx @@ -42,12 +42,20 @@ const TheEnd : React.FC = () => { ...(flexGapReplacementStyle(20, false)), }) + const restartOrBackStyle = css({ + display: "flex", + flexDirection: 'row', + ...(flexGapReplacementStyle(20, false)), + }) + return (
{endState === 'discarded' ? : }
{text()}
- {(endState === 'discarded') && } - +
+ + {(endState === 'discarded') && } +
); } From b88c774eb0d440b372e1a27433a9141b7e605bdb Mon Sep 17 00:00:00 2001 From: Dennis Benz Date: Wed, 20 Sep 2023 16:02:08 +0200 Subject: [PATCH 4/4] Add setting option for callback system name This allows to specify a system name to be displayed in the callback button instead of a generic name. --- README.md | 1 + editor-settings.toml | 6 ++++++ public/editor-settings.toml | 1 + src/config.ts | 3 +++ src/i18n/locales/de-DE.json | 3 ++- src/i18n/locales/en-US.json | 3 ++- src/main/Finish.tsx | 2 +- 7 files changed, 16 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 7af198257..93df45ee7 100644 --- a/README.md +++ b/README.md @@ -71,6 +71,7 @@ Options which are usually specified in the configuration file are documented in | mediaPackageId | ✓ | ✓ | Deprecated. Use `id` instead. | | allowedCallbackPrefixes | ✗ | ✓ | Allowed callback prefixes in callback url. | | callbackUrl | ✓ | ✓ | Callback url to go back after finish. | +| callbackSystem | ✓ | ✓ | Callback system name to go back to. | | opencast.url | ✗ | ✓ | URL of the opencast server to connect to. | | opencast.name | ✗ | ✓ | Opencast user to use. For demo purposes only. | | opencast.password | ✗ | ✓ | Password to use for authentication. For demo purposes only. | diff --git a/editor-settings.toml b/editor-settings.toml index 3787c58cf..5cf12af19 100644 --- a/editor-settings.toml +++ b/editor-settings.toml @@ -24,6 +24,12 @@ # Default: undefined #callbackUrl = +# Name of system to go back to +# If undefined, a generic system name is used instead of a speficic name +# Type: string | undefined +# Default: undefined +#callbackSystem = + #### # Metadata diff --git a/public/editor-settings.toml b/public/editor-settings.toml index b4519cf84..94ad8b6b9 100644 --- a/public/editor-settings.toml +++ b/public/editor-settings.toml @@ -14,6 +14,7 @@ id = 'ID-dual-stream-demo' # Callback to develop.opencast.org allowedCallbackPrefixes = ["https://develop.opencast.org"] callbackUrl = "https://develop.opencast.org" +callbackSystem = "OPENCAST" [opencast] # Connect to develop.opencast.org and use the default demo user diff --git a/src/config.ts b/src/config.ts index 8a8cb25ae..e9d4d3818 100644 --- a/src/config.ts +++ b/src/config.ts @@ -43,6 +43,7 @@ interface iSettings { id: string | undefined, allowedCallbackPrefixes: string[], callbackUrl: string | undefined, + callbackSystem: string | undefined, opencast: { url: string, name: string | undefined, @@ -80,6 +81,7 @@ const defaultSettings: iSettings = { id: undefined, allowedCallbackPrefixes: [], callbackUrl: undefined, + callbackSystem: undefined, opencast: { url: window.location.origin, name: undefined, @@ -389,6 +391,7 @@ const SCHEMA = { id: types.string, allowedCallbackPrefixes: types.array, callbackUrl: types.string, + callbackSystem: types.string, opencast: { url: types.string, name: types.string, diff --git a/src/i18n/locales/de-DE.json b/src/i18n/locales/de-DE.json index 98218d2e8..ae66a324d 100644 --- a/src/i18n/locales/de-DE.json +++ b/src/i18n/locales/de-DE.json @@ -202,7 +202,8 @@ "error-details-text": "Details: {{errorMessage}}\n", "error-text": "Ein Fehler ist aufgetreten. Bitte versuchen Sie es später noch einmal.", "goBack-button": "Nein, zurück", - "callback-button": "Zurück zum vorigen System" + "callback-button-system": "Zurück zu {{system}}", + "callback-button-generic": "Zurück zum vorigen System" }, "trackSelection": { "title": "Spur(en) für die Verarbeitung auswählen", diff --git a/src/i18n/locales/en-US.json b/src/i18n/locales/en-US.json index 9acae4404..27f7d0f93 100644 --- a/src/i18n/locales/en-US.json +++ b/src/i18n/locales/en-US.json @@ -217,7 +217,8 @@ "error-details-text": "Details: {{errorMessage}}\n", "error-text": "An error has occurred. Please wait a bit and try again.", "goBack-button": "No, take me back", - "callback-button": "Back to previous system" + "callback-button-system": "Back to {{system}}", + "callback-button-generic": "Back to previous system" }, "trackSelection": { diff --git a/src/main/Finish.tsx b/src/main/Finish.tsx index a54a22c7f..728fc6672 100644 --- a/src/main/Finish.tsx +++ b/src/main/Finish.tsx @@ -113,7 +113,7 @@ export const CallbackButton : React.FC = () => { openCallbackUrl() } }}> - {t("various.callback-button")} + {settings.callbackSystem ? t("various.callback-button-system", {system: settings.callbackSystem}) : t("various.callback-button-generic")} }