2017-11-29 18:11:03 +00:00
|
|
|
/*
|
2017-11-30 10:20:29 +00:00
|
|
|
Copyright 2017 New Vector Ltd
|
2019-03-13 06:34:34 +00:00
|
|
|
Copyright 2019 Travis Ralston
|
2017-11-29 18:11:03 +00:00
|
|
|
|
|
|
|
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.
|
|
|
|
*/
|
|
|
|
|
2017-12-01 14:44:14 +00:00
|
|
|
/*
|
2018-02-23 15:11:28 +00:00
|
|
|
* See - https://docs.google.com/document/d/1uPF7XWY_dXTKVKV7jZQ2KmsI19wn9-kFRgQ1tFQP7wQ/edit?usp=sharing for
|
|
|
|
* spec. details / documentation.
|
2017-12-01 14:44:14 +00:00
|
|
|
*/
|
|
|
|
|
2018-03-28 11:22:06 +00:00
|
|
|
import FromWidgetPostMessageApi from './FromWidgetPostMessageApi';
|
|
|
|
import ToWidgetPostMessageApi from './ToWidgetPostMessageApi';
|
2019-03-13 06:34:34 +00:00
|
|
|
import Modal from "./Modal";
|
2019-12-20 21:13:46 +00:00
|
|
|
import {MatrixClientPeg} from "./MatrixClientPeg";
|
2019-03-16 03:33:31 +00:00
|
|
|
import SettingsStore from "./settings/SettingsStore";
|
|
|
|
import WidgetOpenIDPermissionsDialog from "./components/views/dialogs/WidgetOpenIDPermissionsDialog";
|
2019-03-24 05:25:31 +00:00
|
|
|
import WidgetUtils from "./utils/WidgetUtils";
|
2020-03-24 15:55:54 +00:00
|
|
|
import {KnownWidgetActions} from "./widgets/WidgetApi";
|
2017-12-05 00:08:17 +00:00
|
|
|
|
2018-03-28 11:22:06 +00:00
|
|
|
if (!global.mxFromWidgetMessaging) {
|
|
|
|
global.mxFromWidgetMessaging = new FromWidgetPostMessageApi();
|
|
|
|
global.mxFromWidgetMessaging.start();
|
2017-12-04 17:54:00 +00:00
|
|
|
}
|
2018-03-28 11:22:06 +00:00
|
|
|
if (!global.mxToWidgetMessaging) {
|
|
|
|
global.mxToWidgetMessaging = new ToWidgetPostMessageApi();
|
|
|
|
global.mxToWidgetMessaging.start();
|
2017-12-04 17:54:00 +00:00
|
|
|
}
|
2017-11-30 11:30:30 +00:00
|
|
|
|
2018-03-28 11:22:06 +00:00
|
|
|
const OUTBOUND_API_NAME = 'toWidget';
|
2018-01-18 13:16:06 +00:00
|
|
|
|
2018-03-28 11:22:06 +00:00
|
|
|
export default class WidgetMessaging {
|
2020-05-13 15:10:40 +00:00
|
|
|
/**
|
|
|
|
* @param {string} widgetId The widget's ID
|
|
|
|
* @param {string} wurl The raw URL of the widget as in the event (the 'wURL')
|
|
|
|
* @param {string} renderedUrl The url used in the widget's iframe (either similar to the wURL
|
|
|
|
* or a different URL of the clients choosing if it is using its own impl).
|
|
|
|
* @param {bool} isUserWidget If true, the widget is a user widget, otherwise it's a room widget
|
|
|
|
* @param {object} target Where widget messages should be sent (eg. the iframe object)
|
|
|
|
*/
|
|
|
|
constructor(widgetId, wurl, renderedUrl, isUserWidget, target) {
|
2018-03-28 11:22:06 +00:00
|
|
|
this.widgetId = widgetId;
|
2020-05-13 15:10:40 +00:00
|
|
|
this.wurl = wurl;
|
|
|
|
this.renderedUrl = renderedUrl;
|
2019-03-24 05:25:31 +00:00
|
|
|
this.isUserWidget = isUserWidget;
|
2018-03-28 11:22:06 +00:00
|
|
|
this.target = target;
|
|
|
|
this.fromWidget = global.mxFromWidgetMessaging;
|
|
|
|
this.toWidget = global.mxToWidgetMessaging;
|
2019-03-24 04:50:26 +00:00
|
|
|
this._onOpenIdRequest = this._onOpenIdRequest.bind(this);
|
2018-03-28 11:22:06 +00:00
|
|
|
this.start();
|
2017-12-01 16:17:18 +00:00
|
|
|
}
|
|
|
|
|
2018-03-28 11:22:06 +00:00
|
|
|
messageToWidget(action) {
|
2018-05-12 19:49:43 +00:00
|
|
|
action.widgetId = this.widgetId; // Required to be sent for all outbound requests
|
|
|
|
|
2018-03-28 11:22:06 +00:00
|
|
|
return this.toWidget.exec(action, this.target).then((data) => {
|
|
|
|
// Check for errors and reject if found
|
2017-12-15 15:24:22 +00:00
|
|
|
if (data.response === undefined) { // null is valid
|
|
|
|
throw new Error("Missing 'response' field");
|
|
|
|
}
|
|
|
|
if (data.response && data.response.error) {
|
|
|
|
const err = data.response.error;
|
|
|
|
const msg = String(err.message ? err.message : "An error was returned");
|
|
|
|
if (err._error) {
|
|
|
|
console.error(err._error);
|
|
|
|
}
|
|
|
|
// Potential XSS attack if 'msg' is not appropriately sanitized,
|
2020-08-03 15:02:26 +00:00
|
|
|
// as it is untrusted input by our parent window (which we assume is Element).
|
2017-12-15 15:24:22 +00:00
|
|
|
// We can't aggressively sanitize [A-z0-9] since it might be a translation.
|
|
|
|
throw new Error(msg);
|
|
|
|
}
|
2018-03-28 11:22:06 +00:00
|
|
|
// Return the response field for the request
|
2017-12-15 15:24:22 +00:00
|
|
|
return data.response;
|
|
|
|
});
|
2017-12-01 16:17:18 +00:00
|
|
|
}
|
2017-12-15 15:24:22 +00:00
|
|
|
|
2020-03-24 15:55:54 +00:00
|
|
|
/**
|
|
|
|
* Tells the widget that the client is ready to handle further widget requests.
|
2020-03-24 16:05:57 +00:00
|
|
|
* @returns {Promise<*>} Resolves after the widget has acknowledged the ready message.
|
2020-03-24 15:55:54 +00:00
|
|
|
*/
|
|
|
|
flagReadyToContinue() {
|
|
|
|
return this.messageToWidget({
|
|
|
|
api: OUTBOUND_API_NAME,
|
|
|
|
action: KnownWidgetActions.ClientReady,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2020-04-18 11:52:41 +00:00
|
|
|
/**
|
|
|
|
* Tells the widget that it should terminate now.
|
|
|
|
* @returns {Promise<*>} Resolves when widget has acknowledged the message.
|
|
|
|
*/
|
|
|
|
terminate() {
|
|
|
|
return this.messageToWidget({
|
|
|
|
api: OUTBOUND_API_NAME,
|
|
|
|
action: KnownWidgetActions.Terminate,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2017-12-15 16:39:04 +00:00
|
|
|
/**
|
|
|
|
* Request a screenshot from a widget
|
2018-02-25 22:21:30 +00:00
|
|
|
* @return {Promise} To be resolved with screenshot data when it has been generated
|
2017-12-15 16:39:04 +00:00
|
|
|
*/
|
|
|
|
getScreenshot() {
|
2020-04-01 19:59:48 +00:00
|
|
|
console.log('Requesting screenshot for', this.widgetId);
|
2018-03-28 11:22:06 +00:00
|
|
|
return this.messageToWidget({
|
2018-02-23 15:11:28 +00:00
|
|
|
api: OUTBOUND_API_NAME,
|
2017-12-15 21:36:02 +00:00
|
|
|
action: "screenshot",
|
2018-03-28 11:22:06 +00:00
|
|
|
})
|
|
|
|
.catch((error) => new Error("Failed to get screenshot: " + error.message))
|
|
|
|
.then((response) => response.screenshot);
|
2017-12-15 16:39:04 +00:00
|
|
|
}
|
2017-12-16 09:16:24 +00:00
|
|
|
|
2018-02-25 22:21:30 +00:00
|
|
|
/**
|
|
|
|
* Request capabilities required by the widget
|
|
|
|
* @return {Promise} To be resolved with an array of requested widget capabilities
|
|
|
|
*/
|
2017-12-16 09:16:24 +00:00
|
|
|
getCapabilities() {
|
2020-04-01 19:59:48 +00:00
|
|
|
console.log('Requesting capabilities for', this.widgetId);
|
2018-03-28 11:22:06 +00:00
|
|
|
return this.messageToWidget({
|
2018-02-23 15:11:28 +00:00
|
|
|
api: OUTBOUND_API_NAME,
|
2017-12-16 09:16:24 +00:00
|
|
|
action: "capabilities",
|
2018-03-28 11:22:06 +00:00
|
|
|
}).then((response) => {
|
2020-04-01 19:59:48 +00:00
|
|
|
console.log('Got capabilities for', this.widgetId, response.capabilities);
|
2018-03-28 11:22:06 +00:00
|
|
|
return response.capabilities;
|
|
|
|
});
|
2017-12-16 09:16:24 +00:00
|
|
|
}
|
2017-12-01 14:56:27 +00:00
|
|
|
|
2018-05-11 15:22:54 +00:00
|
|
|
sendVisibility(visible) {
|
|
|
|
return this.messageToWidget({
|
|
|
|
api: OUTBOUND_API_NAME,
|
|
|
|
action: "visibility",
|
|
|
|
visible,
|
|
|
|
})
|
|
|
|
.catch((error) => {
|
|
|
|
console.error("Failed to send visibility: ", error);
|
|
|
|
});
|
|
|
|
}
|
2018-03-28 11:22:06 +00:00
|
|
|
|
|
|
|
start() {
|
2020-05-13 15:10:40 +00:00
|
|
|
this.fromWidget.addEndpoint(this.widgetId, this.renderedUrl);
|
2019-03-24 04:50:26 +00:00
|
|
|
this.fromWidget.addListener("get_openid", this._onOpenIdRequest);
|
2018-03-28 11:22:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
stop() {
|
2020-05-13 15:10:40 +00:00
|
|
|
this.fromWidget.removeEndpoint(this.widgetId, this.renderedUrl);
|
2019-03-24 04:50:26 +00:00
|
|
|
this.fromWidget.removeListener("get_openid", this._onOpenIdRequest);
|
2019-03-13 06:34:34 +00:00
|
|
|
}
|
|
|
|
|
2019-03-16 03:33:31 +00:00
|
|
|
async _onOpenIdRequest(ev, rawEv) {
|
2019-03-13 06:34:34 +00:00
|
|
|
if (ev.widgetId !== this.widgetId) return; // not interesting
|
|
|
|
|
2020-05-13 15:10:40 +00:00
|
|
|
const widgetSecurityKey = WidgetUtils.getWidgetSecurityKey(this.widgetId, this.wurl, this.isUserWidget);
|
2019-03-24 05:25:31 +00:00
|
|
|
|
2019-03-16 03:33:31 +00:00
|
|
|
const settings = SettingsStore.getValue("widgetOpenIDPermissions");
|
2019-03-26 03:14:21 +00:00
|
|
|
if (settings.deny && settings.deny.includes(widgetSecurityKey)) {
|
2019-03-16 03:33:31 +00:00
|
|
|
this.fromWidget.sendResponse(rawEv, {state: "blocked"});
|
|
|
|
return;
|
|
|
|
}
|
2019-03-26 03:14:21 +00:00
|
|
|
if (settings.allow && settings.allow.includes(widgetSecurityKey)) {
|
2019-03-16 03:33:31 +00:00
|
|
|
const responseBody = {state: "allowed"};
|
|
|
|
const credentials = await MatrixClientPeg.get().getOpenIdToken();
|
|
|
|
Object.assign(responseBody, credentials);
|
|
|
|
this.fromWidget.sendResponse(rawEv, responseBody);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-03-13 06:34:34 +00:00
|
|
|
// Confirm that we received the request
|
|
|
|
this.fromWidget.sendResponse(rawEv, {state: "request"});
|
|
|
|
|
|
|
|
// Actually ask for permission to send the user's data
|
2019-03-16 03:33:31 +00:00
|
|
|
Modal.createTrackedDialog("OpenID widget permissions", '',
|
|
|
|
WidgetOpenIDPermissionsDialog, {
|
2020-05-13 15:10:40 +00:00
|
|
|
widgetUrl: this.wurl,
|
2019-03-16 03:33:31 +00:00
|
|
|
widgetId: this.widgetId,
|
2019-03-24 05:25:31 +00:00
|
|
|
isUserWidget: this.isUserWidget,
|
2019-03-16 03:33:31 +00:00
|
|
|
|
|
|
|
onFinished: async (confirm) => {
|
2020-09-05 21:06:31 +00:00
|
|
|
const responseBody = {
|
|
|
|
// Legacy (early draft) fields
|
|
|
|
success: confirm,
|
|
|
|
|
2020-09-05 21:10:28 +00:00
|
|
|
// New style MSC1960 fields
|
2020-09-05 21:06:31 +00:00
|
|
|
state: confirm ? "allowed" : "blocked",
|
|
|
|
original_request_id: ev.requestId, // eslint-disable-line camelcase
|
|
|
|
};
|
2019-03-16 03:33:31 +00:00
|
|
|
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);
|
|
|
|
});
|
2019-03-13 06:34:34 +00:00
|
|
|
},
|
|
|
|
},
|
2019-03-16 03:33:31 +00:00
|
|
|
);
|
2017-11-30 11:30:30 +00:00
|
|
|
}
|
|
|
|
}
|