Merge pull request #2781 from matrix-org/travis/openid-widget
Widget OpenID reauth implementation
This commit is contained in:
commit
ddcb7a68bd
10 changed files with 282 additions and 7 deletions
|
@ -70,6 +70,7 @@
|
||||||
@import "./views/dialogs/_ShareDialog.scss";
|
@import "./views/dialogs/_ShareDialog.scss";
|
||||||
@import "./views/dialogs/_UnknownDeviceDialog.scss";
|
@import "./views/dialogs/_UnknownDeviceDialog.scss";
|
||||||
@import "./views/dialogs/_UserSettingsDialog.scss";
|
@import "./views/dialogs/_UserSettingsDialog.scss";
|
||||||
|
@import "./views/dialogs/_WidgetOpenIDPermissionsDialog.scss";
|
||||||
@import "./views/dialogs/keybackup/_CreateKeyBackupDialog.scss";
|
@import "./views/dialogs/keybackup/_CreateKeyBackupDialog.scss";
|
||||||
@import "./views/dialogs/keybackup/_KeyBackupFailedDialog.scss";
|
@import "./views/dialogs/keybackup/_KeyBackupFailedDialog.scss";
|
||||||
@import "./views/dialogs/keybackup/_RestoreKeyBackupDialog.scss";
|
@import "./views/dialogs/keybackup/_RestoreKeyBackupDialog.scss";
|
||||||
|
|
28
res/css/views/dialogs/_WidgetOpenIDPermissionsDialog.scss
Normal file
28
res/css/views/dialogs/_WidgetOpenIDPermissionsDialog.scss
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
/*
|
||||||
|
Copyright 2019 Travis Ralston
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
.mx_WidgetOpenIDPermissionsDialog .mx_SettingsFlag {
|
||||||
|
.mx_ToggleSwitch {
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: middle;
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_SettingsFlag_label {
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2018 New Vector Ltd
|
Copyright 2018 New Vector Ltd
|
||||||
|
Copyright 2019 Travis Ralston
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the 'License');
|
Licensed under the Apache License, Version 2.0 (the 'License');
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -20,17 +21,19 @@ import IntegrationManager from './IntegrationManager';
|
||||||
import WidgetMessagingEndpoint from './WidgetMessagingEndpoint';
|
import WidgetMessagingEndpoint from './WidgetMessagingEndpoint';
|
||||||
import ActiveWidgetStore from './stores/ActiveWidgetStore';
|
import ActiveWidgetStore from './stores/ActiveWidgetStore';
|
||||||
|
|
||||||
const WIDGET_API_VERSION = '0.0.1'; // Current API version
|
const WIDGET_API_VERSION = '0.0.2'; // Current API version
|
||||||
const SUPPORTED_WIDGET_API_VERSIONS = [
|
const SUPPORTED_WIDGET_API_VERSIONS = [
|
||||||
'0.0.1',
|
'0.0.1',
|
||||||
|
'0.0.2',
|
||||||
];
|
];
|
||||||
const INBOUND_API_NAME = 'fromWidget';
|
const INBOUND_API_NAME = 'fromWidget';
|
||||||
|
|
||||||
// Listen for and handle incomming requests using the 'fromWidget' postMessage
|
// Listen for and handle incoming requests using the 'fromWidget' postMessage
|
||||||
// API and initiate responses
|
// API and initiate responses
|
||||||
export default class FromWidgetPostMessageApi {
|
export default class FromWidgetPostMessageApi {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.widgetMessagingEndpoints = [];
|
this.widgetMessagingEndpoints = [];
|
||||||
|
this.widgetListeners = {}; // {action: func[]}
|
||||||
|
|
||||||
this.start = this.start.bind(this);
|
this.start = this.start.bind(this);
|
||||||
this.stop = this.stop.bind(this);
|
this.stop = this.stop.bind(this);
|
||||||
|
@ -45,6 +48,32 @@ export default class FromWidgetPostMessageApi {
|
||||||
window.removeEventListener('message', this.onPostMessage);
|
window.removeEventListener('message', this.onPostMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a listener for a given action
|
||||||
|
* @param {string} action The action to listen for.
|
||||||
|
* @param {Function} callbackFn A callback function to be called when the action is
|
||||||
|
* encountered. Called with two parameters: the interesting request information and
|
||||||
|
* the raw event received from the postMessage API. The raw event is meant to be used
|
||||||
|
* for sendResponse and similar functions.
|
||||||
|
*/
|
||||||
|
addListener(action, callbackFn) {
|
||||||
|
if (!this.widgetListeners[action]) this.widgetListeners[action] = [];
|
||||||
|
this.widgetListeners[action].push(callbackFn);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a listener for a given action.
|
||||||
|
* @param {string} action The action that was subscribed to.
|
||||||
|
* @param {Function} callbackFn The original callback function that was used to subscribe
|
||||||
|
* to updates.
|
||||||
|
*/
|
||||||
|
removeListener(action, callbackFn) {
|
||||||
|
if (!this.widgetListeners[action]) return;
|
||||||
|
|
||||||
|
const idx = this.widgetListeners[action].indexOf(callbackFn);
|
||||||
|
if (idx !== -1) this.widgetListeners[action].splice(idx, 1);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register a widget endpoint for trusted postMessage communication
|
* Register a widget endpoint for trusted postMessage communication
|
||||||
* @param {string} widgetId Unique widget identifier
|
* @param {string} widgetId Unique widget identifier
|
||||||
|
@ -117,6 +146,13 @@ export default class FromWidgetPostMessageApi {
|
||||||
return; // don't log this - debugging APIs like to spam postMessage which floods the log otherwise
|
return; // don't log this - debugging APIs like to spam postMessage which floods the log otherwise
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Call any listeners we have registered
|
||||||
|
if (this.widgetListeners[event.data.action]) {
|
||||||
|
for (const fn of this.widgetListeners[event.data.action]) {
|
||||||
|
fn(event.data, event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Although the requestId is required, we don't use it. We'll be nice and process the message
|
// Although the requestId is required, we don't use it. We'll be nice and process the message
|
||||||
// if the property is missing, but with a warning for widget developers.
|
// if the property is missing, but with a warning for widget developers.
|
||||||
if (!event.data.requestId) {
|
if (!event.data.requestId) {
|
||||||
|
@ -164,6 +200,8 @@ export default class FromWidgetPostMessageApi {
|
||||||
if (ActiveWidgetStore.widgetHasCapability(widgetId, 'm.always_on_screen')) {
|
if (ActiveWidgetStore.widgetHasCapability(widgetId, 'm.always_on_screen')) {
|
||||||
ActiveWidgetStore.setWidgetPersistence(widgetId, val);
|
ActiveWidgetStore.setWidgetPersistence(widgetId, val);
|
||||||
}
|
}
|
||||||
|
} else if (action === 'get_openid') {
|
||||||
|
// Handled by caller
|
||||||
} else {
|
} else {
|
||||||
console.warn('Widget postMessage event unhandled');
|
console.warn('Widget postMessage event unhandled');
|
||||||
this.sendError(event, {message: 'The postMessage was unhandled'});
|
this.sendError(event, {message: 'The postMessage was unhandled'});
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2017 New Vector Ltd
|
Copyright 2017 New Vector Ltd
|
||||||
|
Copyright 2019 Travis Ralston
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -21,6 +22,11 @@ limitations under the License.
|
||||||
|
|
||||||
import FromWidgetPostMessageApi from './FromWidgetPostMessageApi';
|
import FromWidgetPostMessageApi from './FromWidgetPostMessageApi';
|
||||||
import ToWidgetPostMessageApi from './ToWidgetPostMessageApi';
|
import ToWidgetPostMessageApi from './ToWidgetPostMessageApi';
|
||||||
|
import Modal from "./Modal";
|
||||||
|
import MatrixClientPeg from "./MatrixClientPeg";
|
||||||
|
import SettingsStore from "./settings/SettingsStore";
|
||||||
|
import WidgetOpenIDPermissionsDialog from "./components/views/dialogs/WidgetOpenIDPermissionsDialog";
|
||||||
|
import WidgetUtils from "./utils/WidgetUtils";
|
||||||
|
|
||||||
if (!global.mxFromWidgetMessaging) {
|
if (!global.mxFromWidgetMessaging) {
|
||||||
global.mxFromWidgetMessaging = new FromWidgetPostMessageApi();
|
global.mxFromWidgetMessaging = new FromWidgetPostMessageApi();
|
||||||
|
@ -34,12 +40,14 @@ if (!global.mxToWidgetMessaging) {
|
||||||
const OUTBOUND_API_NAME = 'toWidget';
|
const OUTBOUND_API_NAME = 'toWidget';
|
||||||
|
|
||||||
export default class WidgetMessaging {
|
export default class WidgetMessaging {
|
||||||
constructor(widgetId, widgetUrl, target) {
|
constructor(widgetId, widgetUrl, isUserWidget, target) {
|
||||||
this.widgetId = widgetId;
|
this.widgetId = widgetId;
|
||||||
this.widgetUrl = widgetUrl;
|
this.widgetUrl = widgetUrl;
|
||||||
|
this.isUserWidget = isUserWidget;
|
||||||
this.target = target;
|
this.target = target;
|
||||||
this.fromWidget = global.mxFromWidgetMessaging;
|
this.fromWidget = global.mxFromWidgetMessaging;
|
||||||
this.toWidget = global.mxToWidgetMessaging;
|
this.toWidget = global.mxToWidgetMessaging;
|
||||||
|
this._onOpenIdRequest = this._onOpenIdRequest.bind(this);
|
||||||
this.start();
|
this.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -109,9 +117,57 @@ export default class WidgetMessaging {
|
||||||
|
|
||||||
start() {
|
start() {
|
||||||
this.fromWidget.addEndpoint(this.widgetId, this.widgetUrl);
|
this.fromWidget.addEndpoint(this.widgetId, this.widgetUrl);
|
||||||
|
this.fromWidget.addListener("get_openid", this._onOpenIdRequest);
|
||||||
}
|
}
|
||||||
|
|
||||||
stop() {
|
stop() {
|
||||||
this.fromWidget.removeEndpoint(this.widgetId, this.widgetUrl);
|
this.fromWidget.removeEndpoint(this.widgetId, this.widgetUrl);
|
||||||
|
this.fromWidget.removeListener("get_openid", this._onOpenIdRequest);
|
||||||
|
}
|
||||||
|
|
||||||
|
async _onOpenIdRequest(ev, rawEv) {
|
||||||
|
if (ev.widgetId !== this.widgetId) return; // not interesting
|
||||||
|
|
||||||
|
const widgetSecurityKey = WidgetUtils.getWidgetSecurityKey(this.widgetId, this.widgetUrl, this.isUserWidget);
|
||||||
|
|
||||||
|
const settings = SettingsStore.getValue("widgetOpenIDPermissions");
|
||||||
|
if (settings.deny && settings.deny.includes(widgetSecurityKey)) {
|
||||||
|
this.fromWidget.sendResponse(rawEv, {state: "blocked"});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (settings.allow && settings.allow.includes(widgetSecurityKey)) {
|
||||||
|
const responseBody = {state: "allowed"};
|
||||||
|
const credentials = await MatrixClientPeg.get().getOpenIdToken();
|
||||||
|
Object.assign(responseBody, credentials);
|
||||||
|
this.fromWidget.sendResponse(rawEv, responseBody);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Confirm that we received the request
|
||||||
|
this.fromWidget.sendResponse(rawEv, {state: "request"});
|
||||||
|
|
||||||
|
// Actually ask for permission to send the user's data
|
||||||
|
Modal.createTrackedDialog("OpenID widget permissions", '',
|
||||||
|
WidgetOpenIDPermissionsDialog, {
|
||||||
|
widgetUrl: this.widgetUrl,
|
||||||
|
widgetId: this.widgetId,
|
||||||
|
isUserWidget: this.isUserWidget,
|
||||||
|
|
||||||
|
onFinished: async (confirm) => {
|
||||||
|
const responseBody = {success: confirm};
|
||||||
|
if (confirm) {
|
||||||
|
const credentials = await MatrixClientPeg.get().getOpenIdToken();
|
||||||
|
Object.assign(responseBody, credentials);
|
||||||
|
}
|
||||||
|
this.messageToWidget({
|
||||||
|
api: OUTBOUND_API_NAME,
|
||||||
|
action: "openid_credentials",
|
||||||
|
data: responseBody,
|
||||||
|
}).catch((error) => {
|
||||||
|
console.error("Failed to send OpenID credentials: ", error);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
103
src/components/views/dialogs/WidgetOpenIDPermissionsDialog.js
Normal file
103
src/components/views/dialogs/WidgetOpenIDPermissionsDialog.js
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
/*
|
||||||
|
Copyright 2019 Travis Ralston
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import {_t} from "../../../languageHandler";
|
||||||
|
import SettingsStore, {SettingLevel} from "../../../settings/SettingsStore";
|
||||||
|
import sdk from "../../../index";
|
||||||
|
import LabelledToggleSwitch from "../elements/LabelledToggleSwitch";
|
||||||
|
import WidgetUtils from "../../../utils/WidgetUtils";
|
||||||
|
|
||||||
|
export default class WidgetOpenIDPermissionsDialog extends React.Component {
|
||||||
|
static propTypes = {
|
||||||
|
onFinished: PropTypes.func.isRequired,
|
||||||
|
widgetUrl: PropTypes.string.isRequired,
|
||||||
|
widgetId: PropTypes.string.isRequired,
|
||||||
|
isUserWidget: PropTypes.bool.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
rememberSelection: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
_onAllow = () => {
|
||||||
|
this._onPermissionSelection(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
_onDeny = () => {
|
||||||
|
this._onPermissionSelection(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
_onPermissionSelection(allowed) {
|
||||||
|
if (this.state.rememberSelection) {
|
||||||
|
console.log(`Remembering ${this.props.widgetId} as allowed=${allowed} for OpenID`);
|
||||||
|
|
||||||
|
const currentValues = SettingsStore.getValue("widgetOpenIDPermissions");
|
||||||
|
if (!currentValues.allow) currentValues.allow = [];
|
||||||
|
if (!currentValues.deny) currentValues.deny = [];
|
||||||
|
|
||||||
|
const securityKey = WidgetUtils.getWidgetSecurityKey(
|
||||||
|
this.props.widgetId,
|
||||||
|
this.props.widgetUrl,
|
||||||
|
this.props.isUserWidget);
|
||||||
|
(allowed ? currentValues.allow : currentValues.deny).push(securityKey);
|
||||||
|
SettingsStore.setValue("widgetOpenIDPermissions", null, SettingLevel.DEVICE, currentValues);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.props.onFinished(allowed);
|
||||||
|
}
|
||||||
|
|
||||||
|
_onRememberSelectionChange = (newVal) => {
|
||||||
|
this.setState({rememberSelection: newVal});
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
||||||
|
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<BaseDialog className='mx_WidgetOpenIDPermissionsDialog' hasCancel={true}
|
||||||
|
onFinished={this.props.onFinished}
|
||||||
|
title={_t("A widget would like to verify your identity")}>
|
||||||
|
<div className='mx_WidgetOpenIDPermissionsDialog_content'>
|
||||||
|
<p>
|
||||||
|
{_t(
|
||||||
|
"A widget located at %(widgetUrl)s would like to verify your identity. " +
|
||||||
|
"By allowing this, the widget will be able to verify your user ID, but not " +
|
||||||
|
"perform actions as you.", {
|
||||||
|
widgetUrl: this.props.widgetUrl,
|
||||||
|
},
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
<LabelledToggleSwitch value={this.state.rememberSelection} toggleInFront={true}
|
||||||
|
onChange={this._onRememberSelectionChange}
|
||||||
|
label={_t("Remember my selection for this widget")} />
|
||||||
|
</div>
|
||||||
|
<DialogButtons
|
||||||
|
primaryButton={_t("Allow")}
|
||||||
|
onPrimaryButtonClick={this._onAllow}
|
||||||
|
cancelButton={_t("Deny")}
|
||||||
|
onCancel={this._onDeny}
|
||||||
|
/>
|
||||||
|
</BaseDialog>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -351,7 +351,8 @@ export default class AppTile extends React.Component {
|
||||||
_setupWidgetMessaging() {
|
_setupWidgetMessaging() {
|
||||||
// FIXME: There's probably no reason to do this here: it should probably be done entirely
|
// FIXME: There's probably no reason to do this here: it should probably be done entirely
|
||||||
// in ActiveWidgetStore.
|
// in ActiveWidgetStore.
|
||||||
const widgetMessaging = new WidgetMessaging(this.props.id, this.props.url, this.refs.appFrame.contentWindow);
|
const widgetMessaging = new WidgetMessaging(
|
||||||
|
this.props.id, this.props.url, this.props.userWidget, this.refs.appFrame.contentWindow);
|
||||||
ActiveWidgetStore.setWidgetMessaging(this.props.id, widgetMessaging);
|
ActiveWidgetStore.setWidgetMessaging(this.props.id, widgetMessaging);
|
||||||
widgetMessaging.getCapabilities().then((requestedCapabilities) => {
|
widgetMessaging.getCapabilities().then((requestedCapabilities) => {
|
||||||
console.log(`Widget ${this.props.id} requested capabilities: ` + requestedCapabilities);
|
console.log(`Widget ${this.props.id} requested capabilities: ` + requestedCapabilities);
|
||||||
|
|
|
@ -31,15 +31,29 @@ export default class LabelledToggleSwitch extends React.Component {
|
||||||
|
|
||||||
// Whether or not to disable the toggle switch
|
// Whether or not to disable the toggle switch
|
||||||
disabled: PropTypes.bool,
|
disabled: PropTypes.bool,
|
||||||
|
|
||||||
|
// True to put the toggle in front of the label
|
||||||
|
// Default false.
|
||||||
|
toggleInFront: PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
// This is a minimal version of a SettingsFlag
|
// This is a minimal version of a SettingsFlag
|
||||||
|
|
||||||
|
let firstPart = <span className="mx_SettingsFlag_label">{this.props.label}</span>;
|
||||||
|
let secondPart = <ToggleSwitch checked={this.props.value} disabled={this.props.disabled}
|
||||||
|
onChange={this.props.onChange} />;
|
||||||
|
|
||||||
|
if (this.props.toggleInFront) {
|
||||||
|
const temp = firstPart;
|
||||||
|
firstPart = secondPart;
|
||||||
|
secondPart = temp;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mx_SettingsFlag">
|
<div className="mx_SettingsFlag">
|
||||||
<span className="mx_SettingsFlag_label">{this.props.label}</span>
|
{firstPart}
|
||||||
<ToggleSwitch checked={this.props.value} disabled={this.props.disabled}
|
{secondPart}
|
||||||
onChange={this.props.onChange} />
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1186,6 +1186,10 @@
|
||||||
"Room contains unknown devices": "Room contains unknown devices",
|
"Room contains unknown devices": "Room contains unknown devices",
|
||||||
"\"%(RoomName)s\" contains devices that you haven't seen before.": "\"%(RoomName)s\" contains devices that you haven't seen before.",
|
"\"%(RoomName)s\" contains devices that you haven't seen before.": "\"%(RoomName)s\" contains devices that you haven't seen before.",
|
||||||
"Unknown devices": "Unknown devices",
|
"Unknown devices": "Unknown devices",
|
||||||
|
"A widget would like to verify your identity": "A widget would like to verify your identity",
|
||||||
|
"A widget located at %(widgetUrl)s would like to verify your identity. By allowing this, the widget will be able to verify your user ID, but not perform actions as you.": "A widget located at %(widgetUrl)s would like to verify your identity. By allowing this, the widget will be able to verify your user ID, but not perform actions as you.",
|
||||||
|
"Remember my selection for this widget": "Remember my selection for this widget",
|
||||||
|
"Deny": "Deny",
|
||||||
"Unable to load backup status": "Unable to load backup status",
|
"Unable to load backup status": "Unable to load backup status",
|
||||||
"Recovery Key Mismatch": "Recovery Key Mismatch",
|
"Recovery Key Mismatch": "Recovery Key Mismatch",
|
||||||
"Backup could not be decrypted with this key: please verify that you entered the correct recovery key.": "Backup could not be decrypted with this key: please verify that you entered the correct recovery key.",
|
"Backup could not be decrypted with this key: please verify that you entered the correct recovery key.": "Backup could not be decrypted with this key: please verify that you entered the correct recovery key.",
|
||||||
|
|
|
@ -340,6 +340,13 @@ export const SETTINGS = {
|
||||||
displayName: _td('Show developer tools'),
|
displayName: _td('Show developer tools'),
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
|
"widgetOpenIDPermissions": {
|
||||||
|
supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS,
|
||||||
|
default: {
|
||||||
|
allow: [],
|
||||||
|
deny: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
"RoomList.orderByImportance": {
|
"RoomList.orderByImportance": {
|
||||||
supportedLevels: LEVELS_ACCOUNT_SETTINGS,
|
supportedLevels: LEVELS_ACCOUNT_SETTINGS,
|
||||||
displayName: _td('Order rooms in the room list by most important first instead of most recent'),
|
displayName: _td('Order rooms in the room list by most important first instead of most recent'),
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2017 Vector Creations Ltd
|
Copyright 2017 Vector Creations Ltd
|
||||||
Copyright 2018 New Vector Ltd
|
Copyright 2018 New Vector Ltd
|
||||||
|
Copyright 2019 Travis Ralston
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -25,6 +26,7 @@ import WidgetEchoStore from '../stores/WidgetEchoStore';
|
||||||
// before waitFor[Room/User]Widget rejects its promise
|
// before waitFor[Room/User]Widget rejects its promise
|
||||||
const WIDGET_WAIT_TIME = 20000;
|
const WIDGET_WAIT_TIME = 20000;
|
||||||
import SettingsStore from "../settings/SettingsStore";
|
import SettingsStore from "../settings/SettingsStore";
|
||||||
|
import ActiveWidgetStore from "../stores/ActiveWidgetStore";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encodes a URI according to a set of template variables. Variables will be
|
* Encodes a URI according to a set of template variables. Variables will be
|
||||||
|
@ -396,4 +398,25 @@ export default class WidgetUtils {
|
||||||
|
|
||||||
return capWhitelist;
|
return capWhitelist;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static getWidgetSecurityKey(widgetId, widgetUrl, isUserWidget) {
|
||||||
|
let widgetLocation = ActiveWidgetStore.getRoomId(widgetId);
|
||||||
|
|
||||||
|
if (isUserWidget) {
|
||||||
|
const userWidget = WidgetUtils.getUserWidgetsArray()
|
||||||
|
.find((w) => w.id === widgetId && w.content && w.content.url === widgetUrl);
|
||||||
|
|
||||||
|
if (!userWidget) {
|
||||||
|
throw new Error("No matching user widget to form security key");
|
||||||
|
}
|
||||||
|
|
||||||
|
widgetLocation = userWidget.sender;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!widgetLocation) {
|
||||||
|
throw new Error("Failed to locate where the widget resides");
|
||||||
|
}
|
||||||
|
|
||||||
|
return encodeURIComponent(`${widgetLocation}::${widgetUrl}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue