From 51344656b287764629977b9601bdbc45ca213ed3 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Sun, 10 Dec 2017 02:35:24 -0700 Subject: [PATCH] Prevent people from adding widgets in rooms where they have no permission --- web/app/app.module.ts | 28 +----- .../integration/integration.component.html | 2 +- web/app/integration/integration.component.ts | 4 + web/app/riot/riot.component.ts | 38 +++---- web/app/shared/integration.service.ts | 98 +++++++++++++------ web/app/shared/models/scalar_responses.ts | 6 ++ web/app/shared/scalar.service.ts | 9 ++ 7 files changed, 108 insertions(+), 77 deletions(-) diff --git a/web/app/app.module.ts b/web/app/app.module.ts index 033e0b9..7c2ef02 100644 --- a/web/app/app.module.ts +++ b/web/app/app.module.ts @@ -18,22 +18,16 @@ import { ScalarCloseComponent } from "./riot/scalar-close/scalar-close.component import { IntegrationService } from "./shared/integration.service"; import { BootstrapModalModule } from "ngx-modialog/plugins/bootstrap"; import { ModalModule } from "ngx-modialog"; -import { RssConfigComponent } from "./configs/rss/rss-config.component"; -import { IrcConfigComponent } from "./configs/irc/irc-config.component"; import { IrcApiService } from "./shared/irc-api.service"; -import { TravisCiConfigComponent } from "./configs/travisci/travisci-config.component"; -import { CustomWidgetConfigComponent } from "./configs/widget/custom_widget/custom_widget-config.component"; import { MyFilterPipe } from "./shared/my-filter.pipe"; import { GenericWidgetWrapperComponent } from "./widget_wrappers/generic/generic.component"; import { ToggleFullscreenDirective } from "./shared/toggle-fullscreen.directive"; import { FullscreenButtonComponent } from "./fullscreen-button/fullscreen-button.component"; -import { YoutubeWidgetConfigComponent } from "./configs/widget/youtube/youtube-config.component"; -import { TwitchWidgetConfigComponent } from "./configs/widget/twitch/twitch-config.component"; -import { EtherpadWidgetConfigComponent } from "./configs/widget/etherpad/etherpad-config.component"; import { VideoWidgetWrapperComponent } from "./widget_wrappers/video/video.component"; -import { JitsiWidgetConfigComponent } from "./configs/widget/jitsi/jitsi-config.component"; import { JitsiWidgetWrapperComponent } from "./widget_wrappers/jitsi/jitsi.component"; +const WIDGET_CONFIGURATION_COMPONENTS: any[] = IntegrationService.getAllConfigComponents(); + @NgModule({ imports: [ BrowserModule, @@ -48,24 +42,17 @@ import { JitsiWidgetWrapperComponent } from "./widget_wrappers/jitsi/jitsi.compo BootstrapModalModule, ], declarations: [ + ...WIDGET_CONFIGURATION_COMPONENTS, AppComponent, HomeComponent, RiotComponent, IntegrationComponent, ScalarCloseComponent, - RssConfigComponent, - IrcConfigComponent, - TravisCiConfigComponent, - CustomWidgetConfigComponent, MyFilterPipe, GenericWidgetWrapperComponent, ToggleFullscreenDirective, FullscreenButtonComponent, - YoutubeWidgetConfigComponent, - TwitchWidgetConfigComponent, - EtherpadWidgetConfigComponent, VideoWidgetWrapperComponent, - JitsiWidgetConfigComponent, JitsiWidgetWrapperComponent, // Vendor @@ -81,14 +68,7 @@ import { JitsiWidgetWrapperComponent } from "./widget_wrappers/jitsi/jitsi.compo ], bootstrap: [AppComponent], entryComponents: [ - RssConfigComponent, - TravisCiConfigComponent, - IrcConfigComponent, - CustomWidgetConfigComponent, - YoutubeWidgetConfigComponent, - TwitchWidgetConfigComponent, - EtherpadWidgetConfigComponent, - JitsiWidgetConfigComponent, + ...WIDGET_CONFIGURATION_COMPONENTS, ] }) export class AppModule { diff --git a/web/app/integration/integration.component.html b/web/app/integration/integration.component.html index 6731d5a..dd7f0b0 100644 --- a/web/app/integration/integration.component.html +++ b/web/app/integration/integration.component.html @@ -7,7 +7,7 @@ -
+
diff --git a/web/app/integration/integration.component.ts b/web/app/integration/integration.component.ts index 5dba4df..f2079fd 100644 --- a/web/app/integration/integration.component.ts +++ b/web/app/integration/integration.component.ts @@ -41,4 +41,8 @@ export class IntegrationComponent { size: "lg" }, BSModalContext)); } + + public canHaveErrors(integration: Integration): boolean { + return integration.type === 'bridge' || integration.type === "widget"; + } } diff --git a/web/app/riot/riot.component.ts b/web/app/riot/riot.component.ts index cac0958..71464a0 100644 --- a/web/app/riot/riot.component.ts +++ b/web/app/riot/riot.component.ts @@ -6,10 +6,6 @@ import { ToasterService } from "angular2-toaster"; import { Integration } from "../shared/models/integration"; import { IntegrationService } from "../shared/integration.service"; import * as _ from "lodash"; -import { - WIDGET_DIM_CUSTOM, WIDGET_DIM_ETHERPAD, WIDGET_DIM_JITSI, WIDGET_DIM_TWITCH, - WIDGET_DIM_YOUTUBE -} from "../shared/models/widget"; import { IntegrationComponent } from "../integration/integration.component"; @Component({ @@ -73,21 +69,11 @@ export class RiotComponent { let type = null; let integrationType = null; if (!this.requestedScreen) return; - if (this.requestedScreen === "type_" + WIDGET_DIM_CUSTOM) { - type = "widget"; - integrationType = "customwidget"; - } else if (this.requestedScreen === "type_" + WIDGET_DIM_YOUTUBE) { - type = "widget"; - integrationType = "youtube"; - } else if (this.requestedScreen === "type_" + WIDGET_DIM_TWITCH) { - type = "widget"; - integrationType = "twitch"; - } else if (this.requestedScreen === "type_" + WIDGET_DIM_ETHERPAD) { - type = "widget"; - integrationType = "etherpad"; - } else if (this.requestedScreen === "type_" + WIDGET_DIM_JITSI) { - type = "widget"; - integrationType = "jitsi"; + + const targetIntegration = IntegrationService.getIntegrationForScreen(this.requestedScreen); + if (targetIntegration) { + type = targetIntegration.type; + integrationType = targetIntegration.integrationType; } else { console.log("Unknown screen requested: " + this.requestedScreen); } @@ -109,9 +95,8 @@ export class RiotComponent { integration.hasConfig = IntegrationService.hasConfig(integration); if (integration.type === "widget") { - integration.isEnabled = true; - integration.isBroken = false; - return Promise.resolve(); + if (!integration.requirements) integration.requirements = {}; + integration.requirements["canSetWidget"] = true; } if (integration.requirements) { @@ -163,6 +148,15 @@ export class RiotComponent { ? Promise.resolve() : Promise.reject(new Error("The room must be " + requirement + " to use this integration.")); }); + case "canSetWidget": + const processPayload = payload => { + const response = payload.response; + if (!response || response.error || response.error.message) + return Promise.reject(new Error("You cannot modify widgets in this room")); + if (payload.response === true) return Promise.resolve(); + return Promise.reject("Error communicating with Riot"); + }; + return this.scalar.canSendEvent(this.roomId, "im.vector.modular.widgets", true).then(processPayload).catch(processPayload); default: return Promise.reject(new Error("Requirement '" + key + "' not found")); } diff --git a/web/app/shared/integration.service.ts b/web/app/shared/integration.service.ts index 0a8e77e..62a4bbd 100644 --- a/web/app/shared/integration.service.ts +++ b/web/app/shared/integration.service.ts @@ -9,49 +9,76 @@ import { YoutubeWidgetConfigComponent } from "../configs/widget/youtube/youtube- import { TwitchWidgetConfigComponent } from "../configs/widget/twitch/twitch-config.component"; import { EtherpadWidgetConfigComponent } from "../configs/widget/etherpad/etherpad-config.component"; import { JitsiWidgetConfigComponent } from "../configs/widget/jitsi/jitsi-config.component"; +import { + WIDGET_DIM_CUSTOM, + WIDGET_DIM_ETHERPAD, + WIDGET_DIM_JITSI, + WIDGET_DIM_TWITCH, + WIDGET_DIM_YOUTUBE +} from "./models/widget"; @Injectable() export class IntegrationService { - private static supportedTypeMap = { - "bot": true, + private static supportedIntegrationsMap = { + "bot": {}, // empty == supported "complex-bot": { - "rss": true, - "travisci": true, + "rss": { + component: RssConfigComponent, + }, + "travisci": { + component: TravisCiConfigComponent, + }, }, "bridge": { - "irc": true, + "irc": { + component: IrcConfigComponent, + }, }, "widget": { - "customwidget": true, - "youtube": true, - "twitch": true, - "etherpad": true, - "jitsi": true, + "customwidget": { + component: CustomWidgetConfigComponent, + screenId: "type_" + WIDGET_DIM_CUSTOM, + }, + "youtube": { + component: YoutubeWidgetConfigComponent, + screenId: "type_" + WIDGET_DIM_YOUTUBE, + }, + "etherpad": { + component: EtherpadWidgetConfigComponent, + screenId: "type_" + WIDGET_DIM_ETHERPAD, + }, + "twitch": { + component: TwitchWidgetConfigComponent, + screenId: "type_" + WIDGET_DIM_TWITCH, + }, + "jitsi": { + component: JitsiWidgetConfigComponent, + screenId: "type_" + WIDGET_DIM_JITSI, + }, }, }; - private static components = { - "complex-bot": { - "rss": RssConfigComponent, - "travisci": TravisCiConfigComponent, - }, - "bridge": { - "irc": IrcConfigComponent, - }, - "widget": { - "customwidget": CustomWidgetConfigComponent, - "youtube": YoutubeWidgetConfigComponent, - "twitch": TwitchWidgetConfigComponent, - "etherpad": EtherpadWidgetConfigComponent, - "jitsi": JitsiWidgetConfigComponent, - }, - }; + static getAllConfigComponents(): ContainerContent[] { + const components = []; + + for (const iType of Object.keys(IntegrationService.supportedIntegrationsMap)) { + for (const iiType of Object.keys(IntegrationService.supportedIntegrationsMap[iType])) { + const component = IntegrationService.supportedIntegrationsMap[iType][iiType].component; + if (component) components.push(component); + } + } + + return components; + } static isSupported(integration: Integration): boolean { - if (IntegrationService.supportedTypeMap[integration.type] === true) return true; - if (!IntegrationService.supportedTypeMap[integration.type]) return false; - return IntegrationService.supportedTypeMap[integration.type][integration.integrationType] === true; + const forType = IntegrationService.supportedIntegrationsMap[integration.type]; + if (!forType) return false; + + if (Object.keys(forType).length === 0) return true; + + return forType[integration.integrationType]; // has sub type } static hasConfig(integration: Integration): boolean { @@ -59,7 +86,18 @@ export class IntegrationService { } static getConfigComponent(integration: Integration): ContainerContent { - return IntegrationService.components[integration.type][integration.integrationType]; + return IntegrationService.supportedIntegrationsMap[integration.type][integration.integrationType].component; + } + + static getIntegrationForScreen(screen: string): { type: string, integrationType: string } { + for (const iType of Object.keys(IntegrationService.supportedIntegrationsMap)) { + for (const iiType of Object.keys(IntegrationService.supportedIntegrationsMap[iType])) { + const iScreen = IntegrationService.supportedIntegrationsMap[iType][iiType].screenId; + if (screen === iScreen) return {type: iType, integrationType: iiType}; + } + } + + return null; } constructor() { diff --git a/web/app/shared/models/scalar_responses.ts b/web/app/shared/models/scalar_responses.ts index 3c15674..870afd5 100644 --- a/web/app/shared/models/scalar_responses.ts +++ b/web/app/shared/models/scalar_responses.ts @@ -45,4 +45,10 @@ export interface WidgetsResponse extends ScalarRoomResponse { data?: any; } }[]; +} + +export interface CanSendEventResponse extends ScalarRoomResponse { + event_type: string; + is_state: boolean; + response: boolean; } \ No newline at end of file diff --git a/web/app/shared/scalar.service.ts b/web/app/shared/scalar.service.ts index f8d908d..44046c3 100644 --- a/web/app/shared/scalar.service.ts +++ b/web/app/shared/scalar.service.ts @@ -1,6 +1,7 @@ import { Injectable } from "@angular/core"; import * as randomString from "random-string"; import { + CanSendEventResponse, JoinRuleStateResponse, MembershipStateResponse, ScalarSuccessResponse, @@ -72,6 +73,14 @@ export class ScalarService { this.callAction("close_scalar", {}); } + public canSendEvent(roomId: string, eventType: string, isState: boolean): Promise { + return this.callAction("can_send_event", { + room_id: roomId, + event_type: eventType, + is_state: isState, + }); + } + private callAction(action, payload): Promise { let requestKey = randomString({length: 20}); return new Promise((resolve, reject) => {