Improving security: only iframes opened with "openWebsiteAllowApi" property are now able to send/receive messages.
This commit is contained in:
parent
e927e0fa16
commit
7d67f55012
@ -7,19 +7,29 @@ import {UserInputChatEvent} from "./Events/UserInputChatEvent";
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Listens to messages from iframes and turn those messages into easy to use observables.
|
* Listens to messages from iframes and turn those messages into easy to use observables.
|
||||||
|
* Also allows to send messages to those iframes.
|
||||||
*/
|
*/
|
||||||
class IframeListener {
|
class IframeListener {
|
||||||
private readonly _chatStream: Subject<ChatEvent> = new Subject();
|
private readonly _chatStream: Subject<ChatEvent> = new Subject();
|
||||||
public readonly chatStream = this._chatStream.asObservable();
|
public readonly chatStream = this._chatStream.asObservable();
|
||||||
|
|
||||||
|
private readonly iframes = new Set<HTMLIFrameElement>();
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
window.addEventListener("message", (message) => {
|
window.addEventListener("message", (message) => {
|
||||||
// Do we trust the sender of this message?
|
// Do we trust the sender of this message?
|
||||||
//if (message.origin !== "http://example.com:8080")
|
// Let's only accept messages from the iframe that are allowed.
|
||||||
// return;
|
// Note: maybe we could restrict on the domain too for additional security (in case the iframe goes to another domain).
|
||||||
|
let found = false;
|
||||||
// message.source is window.opener
|
for (const iframe of this.iframes) {
|
||||||
// message.data is the data sent by the iframe
|
if (iframe.contentWindow === message.source) {
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!found) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const payload = message.data;
|
const payload = message.data;
|
||||||
if (isIframeEventWrapper(payload)) {
|
if (isIframeEventWrapper(payload)) {
|
||||||
@ -31,7 +41,17 @@ class IframeListener {
|
|||||||
|
|
||||||
}, false);
|
}, false);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows the passed iFrame to send/receive messages via the API.
|
||||||
|
*/
|
||||||
|
registerIframe(iframe: HTMLIFrameElement): void {
|
||||||
|
this.iframes.add(iframe);
|
||||||
|
}
|
||||||
|
|
||||||
|
unregisterIframe(iframe: HTMLIFrameElement): void {
|
||||||
|
this.iframes.delete(iframe);
|
||||||
}
|
}
|
||||||
|
|
||||||
sendUserInputChat(message: string) {
|
sendUserInputChat(message: string) {
|
||||||
@ -44,11 +64,10 @@ class IframeListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sends the message... to absolutely all the iFrames that can be found in the current document.
|
* Sends the message... to all allowed iframes.
|
||||||
*/
|
*/
|
||||||
private postMessage(message: IframeEvent) {
|
private postMessage(message: IframeEvent) {
|
||||||
// TODO: not the most effecient implementation if there are many events sent!
|
for (const iframe of this.iframes) {
|
||||||
for (const iframe of document.querySelectorAll<HTMLIFrameElement>('iframe')) {
|
|
||||||
iframe.contentWindow?.postMessage(message, '*');
|
iframe.contentWindow?.postMessage(message, '*');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -654,7 +654,7 @@ export class GameScene extends ResizableScene implements CenterListener {
|
|||||||
coWebsiteManager.closeCoWebsite();
|
coWebsiteManager.closeCoWebsite();
|
||||||
}else{
|
}else{
|
||||||
const openWebsiteFunction = () => {
|
const openWebsiteFunction = () => {
|
||||||
coWebsiteManager.loadCoWebsite(newValue as string, this.MapUrlFile, allProps.get('openWebsitePolicy') as string | undefined);
|
coWebsiteManager.loadCoWebsite(newValue as string, this.MapUrlFile, allProps.get('openWebsiteAllowApi') as boolean | undefined, allProps.get('openWebsitePolicy') as string | undefined);
|
||||||
layoutManager.removeActionButton('openWebsite', this.userInputManager);
|
layoutManager.removeActionButton('openWebsite', this.userInputManager);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import {HtmlUtils} from "./HtmlUtils";
|
import {HtmlUtils} from "./HtmlUtils";
|
||||||
|
import {iframeListener} from "../Api/IframeListener";
|
||||||
|
|
||||||
export type CoWebsiteStateChangedCallback = () => void;
|
export type CoWebsiteStateChangedCallback = () => void;
|
||||||
|
|
||||||
@ -42,7 +43,7 @@ class CoWebsiteManager {
|
|||||||
this.opened = iframeStates.opened;
|
this.opened = iframeStates.opened;
|
||||||
}
|
}
|
||||||
|
|
||||||
public loadCoWebsite(url: string, base: string, allowPolicy?: string): void {
|
public loadCoWebsite(url: string, base: string, allowApi?: boolean, allowPolicy?: string): void {
|
||||||
this.load();
|
this.load();
|
||||||
this.cowebsiteDiv.innerHTML = `<button class="close-btn" id="cowebsite-close">
|
this.cowebsiteDiv.innerHTML = `<button class="close-btn" id="cowebsite-close">
|
||||||
<img src="resources/logos/close.svg">
|
<img src="resources/logos/close.svg">
|
||||||
@ -62,6 +63,9 @@ class CoWebsiteManager {
|
|||||||
const onloadPromise = new Promise((resolve) => {
|
const onloadPromise = new Promise((resolve) => {
|
||||||
iframe.onload = () => resolve();
|
iframe.onload = () => resolve();
|
||||||
});
|
});
|
||||||
|
if (allowApi) {
|
||||||
|
iframeListener.registerIframe(iframe);
|
||||||
|
}
|
||||||
this.cowebsiteDiv.appendChild(iframe);
|
this.cowebsiteDiv.appendChild(iframe);
|
||||||
const onTimeoutPromise = new Promise((resolve) => {
|
const onTimeoutPromise = new Promise((resolve) => {
|
||||||
setTimeout(() => resolve(), 2000);
|
setTimeout(() => resolve(), 2000);
|
||||||
@ -92,6 +96,10 @@ class CoWebsiteManager {
|
|||||||
if(this.opened === iframeStates.closed) resolve(); //this method may be called twice, in case of iframe error for example
|
if(this.opened === iframeStates.closed) resolve(); //this method may be called twice, in case of iframe error for example
|
||||||
this.close();
|
this.close();
|
||||||
this.fire();
|
this.fire();
|
||||||
|
const iframe = this.cowebsiteDiv.querySelector('iframe');
|
||||||
|
if (iframe) {
|
||||||
|
iframeListener.unregisterIframe(iframe);
|
||||||
|
}
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.cowebsiteDiv.innerHTML = `<button class="close-btn" id="cowebsite-close">
|
this.cowebsiteDiv.innerHTML = `<button class="close-btn" id="cowebsite-close">
|
||||||
<img src="resources/logos/close.svg">
|
<img src="resources/logos/close.svg">
|
||||||
|
@ -44,6 +44,11 @@
|
|||||||
"name":"openWebsite",
|
"name":"openWebsite",
|
||||||
"type":"string",
|
"type":"string",
|
||||||
"value":"iframe.html"
|
"value":"iframe.html"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"openWebsiteAllowApi",
|
||||||
|
"type":"bool",
|
||||||
|
"value":true
|
||||||
}],
|
}],
|
||||||
"type":"tilelayer",
|
"type":"tilelayer",
|
||||||
"visible":true,
|
"visible":true,
|
||||||
|
Loading…
Reference in New Issue
Block a user