Prevent people from adding widgets in rooms where they have no permission

This commit is contained in:
Travis Ralston 2017-12-10 02:35:24 -07:00
parent cbcee69b57
commit 51344656b2
7 changed files with 108 additions and 77 deletions

View File

@ -18,22 +18,16 @@ import { ScalarCloseComponent } from "./riot/scalar-close/scalar-close.component
import { IntegrationService } from "./shared/integration.service"; import { IntegrationService } from "./shared/integration.service";
import { BootstrapModalModule } from "ngx-modialog/plugins/bootstrap"; import { BootstrapModalModule } from "ngx-modialog/plugins/bootstrap";
import { ModalModule } from "ngx-modialog"; 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 { 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 { MyFilterPipe } from "./shared/my-filter.pipe";
import { GenericWidgetWrapperComponent } from "./widget_wrappers/generic/generic.component"; import { GenericWidgetWrapperComponent } from "./widget_wrappers/generic/generic.component";
import { ToggleFullscreenDirective } from "./shared/toggle-fullscreen.directive"; import { ToggleFullscreenDirective } from "./shared/toggle-fullscreen.directive";
import { FullscreenButtonComponent } from "./fullscreen-button/fullscreen-button.component"; 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 { VideoWidgetWrapperComponent } from "./widget_wrappers/video/video.component";
import { JitsiWidgetConfigComponent } from "./configs/widget/jitsi/jitsi-config.component";
import { JitsiWidgetWrapperComponent } from "./widget_wrappers/jitsi/jitsi.component"; import { JitsiWidgetWrapperComponent } from "./widget_wrappers/jitsi/jitsi.component";
const WIDGET_CONFIGURATION_COMPONENTS: any[] = IntegrationService.getAllConfigComponents();
@NgModule({ @NgModule({
imports: [ imports: [
BrowserModule, BrowserModule,
@ -48,24 +42,17 @@ import { JitsiWidgetWrapperComponent } from "./widget_wrappers/jitsi/jitsi.compo
BootstrapModalModule, BootstrapModalModule,
], ],
declarations: [ declarations: [
...WIDGET_CONFIGURATION_COMPONENTS,
AppComponent, AppComponent,
HomeComponent, HomeComponent,
RiotComponent, RiotComponent,
IntegrationComponent, IntegrationComponent,
ScalarCloseComponent, ScalarCloseComponent,
RssConfigComponent,
IrcConfigComponent,
TravisCiConfigComponent,
CustomWidgetConfigComponent,
MyFilterPipe, MyFilterPipe,
GenericWidgetWrapperComponent, GenericWidgetWrapperComponent,
ToggleFullscreenDirective, ToggleFullscreenDirective,
FullscreenButtonComponent, FullscreenButtonComponent,
YoutubeWidgetConfigComponent,
TwitchWidgetConfigComponent,
EtherpadWidgetConfigComponent,
VideoWidgetWrapperComponent, VideoWidgetWrapperComponent,
JitsiWidgetConfigComponent,
JitsiWidgetWrapperComponent, JitsiWidgetWrapperComponent,
// Vendor // Vendor
@ -81,14 +68,7 @@ import { JitsiWidgetWrapperComponent } from "./widget_wrappers/jitsi/jitsi.compo
], ],
bootstrap: [AppComponent], bootstrap: [AppComponent],
entryComponents: [ entryComponents: [
RssConfigComponent, ...WIDGET_CONFIGURATION_COMPONENTS,
TravisCiConfigComponent,
IrcConfigComponent,
CustomWidgetConfigComponent,
YoutubeWidgetConfigComponent,
TwitchWidgetConfigComponent,
EtherpadWidgetConfigComponent,
JitsiWidgetConfigComponent,
] ]
}) })
export class AppModule { export class AppModule {

View File

@ -7,7 +7,7 @@
<ui-switch [checked]="integration.isEnabled" size="small" [disabled]="integration.isBroken" <ui-switch [checked]="integration.isEnabled" size="small" [disabled]="integration.isBroken"
(change)="update()"></ui-switch> (change)="update()"></ui-switch>
</div> </div>
<div class="switch" *ngIf="integration.type == 'bridge' && !integration.isEnabled"> <div class="switch" *ngIf="canHaveErrors(integration) && !integration.isEnabled">
<i class="fa fa-warning text-warning" ngbTooltip="{{ integration.bridgeError }}"></i> <i class="fa fa-warning text-warning" ngbTooltip="{{ integration.bridgeError }}"></i>
</div> </div>
<div class="toolbar"> <div class="toolbar">

View File

@ -41,4 +41,8 @@ export class IntegrationComponent {
size: "lg" size: "lg"
}, BSModalContext)); }, BSModalContext));
} }
public canHaveErrors(integration: Integration): boolean {
return integration.type === 'bridge' || integration.type === "widget";
}
} }

View File

@ -6,10 +6,6 @@ import { ToasterService } from "angular2-toaster";
import { Integration } from "../shared/models/integration"; import { Integration } from "../shared/models/integration";
import { IntegrationService } from "../shared/integration.service"; import { IntegrationService } from "../shared/integration.service";
import * as _ from "lodash"; 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"; import { IntegrationComponent } from "../integration/integration.component";
@Component({ @Component({
@ -73,21 +69,11 @@ export class RiotComponent {
let type = null; let type = null;
let integrationType = null; let integrationType = null;
if (!this.requestedScreen) return; if (!this.requestedScreen) return;
if (this.requestedScreen === "type_" + WIDGET_DIM_CUSTOM) {
type = "widget"; const targetIntegration = IntegrationService.getIntegrationForScreen(this.requestedScreen);
integrationType = "customwidget"; if (targetIntegration) {
} else if (this.requestedScreen === "type_" + WIDGET_DIM_YOUTUBE) { type = targetIntegration.type;
type = "widget"; integrationType = targetIntegration.integrationType;
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";
} else { } else {
console.log("Unknown screen requested: " + this.requestedScreen); console.log("Unknown screen requested: " + this.requestedScreen);
} }
@ -109,9 +95,8 @@ export class RiotComponent {
integration.hasConfig = IntegrationService.hasConfig(integration); integration.hasConfig = IntegrationService.hasConfig(integration);
if (integration.type === "widget") { if (integration.type === "widget") {
integration.isEnabled = true; if (!integration.requirements) integration.requirements = {};
integration.isBroken = false; integration.requirements["canSetWidget"] = true;
return Promise.resolve();
} }
if (integration.requirements) { if (integration.requirements) {
@ -163,6 +148,15 @@ export class RiotComponent {
? Promise.resolve() ? Promise.resolve()
: Promise.reject(new Error("The room must be " + requirement + " to use this integration.")); : Promise.reject(new Error("The room must be " + requirement + " to use this integration."));
}); });
case "canSetWidget":
const processPayload = payload => {
const response = <any>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: default:
return Promise.reject(new Error("Requirement '" + key + "' not found")); return Promise.reject(new Error("Requirement '" + key + "' not found"));
} }

View File

@ -9,49 +9,76 @@ import { YoutubeWidgetConfigComponent } from "../configs/widget/youtube/youtube-
import { TwitchWidgetConfigComponent } from "../configs/widget/twitch/twitch-config.component"; import { TwitchWidgetConfigComponent } from "../configs/widget/twitch/twitch-config.component";
import { EtherpadWidgetConfigComponent } from "../configs/widget/etherpad/etherpad-config.component"; import { EtherpadWidgetConfigComponent } from "../configs/widget/etherpad/etherpad-config.component";
import { JitsiWidgetConfigComponent } from "../configs/widget/jitsi/jitsi-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() @Injectable()
export class IntegrationService { export class IntegrationService {
private static supportedTypeMap = { private static supportedIntegrationsMap = {
"bot": true, "bot": {}, // empty == supported
"complex-bot": { "complex-bot": {
"rss": true, "rss": {
"travisci": true, component: RssConfigComponent,
},
"travisci": {
component: TravisCiConfigComponent,
},
}, },
"bridge": { "bridge": {
"irc": true, "irc": {
component: IrcConfigComponent,
},
}, },
"widget": { "widget": {
"customwidget": true, "customwidget": {
"youtube": true, component: CustomWidgetConfigComponent,
"twitch": true, screenId: "type_" + WIDGET_DIM_CUSTOM,
"etherpad": true, },
"jitsi": true, "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 = { static getAllConfigComponents(): ContainerContent[] {
"complex-bot": { const components = [];
"rss": RssConfigComponent,
"travisci": TravisCiConfigComponent, for (const iType of Object.keys(IntegrationService.supportedIntegrationsMap)) {
}, for (const iiType of Object.keys(IntegrationService.supportedIntegrationsMap[iType])) {
"bridge": { const component = IntegrationService.supportedIntegrationsMap[iType][iiType].component;
"irc": IrcConfigComponent, if (component) components.push(component);
}, }
"widget": { }
"customwidget": CustomWidgetConfigComponent,
"youtube": YoutubeWidgetConfigComponent, return components;
"twitch": TwitchWidgetConfigComponent, }
"etherpad": EtherpadWidgetConfigComponent,
"jitsi": JitsiWidgetConfigComponent,
},
};
static isSupported(integration: Integration): boolean { static isSupported(integration: Integration): boolean {
if (IntegrationService.supportedTypeMap[integration.type] === true) return true; const forType = IntegrationService.supportedIntegrationsMap[integration.type];
if (!IntegrationService.supportedTypeMap[integration.type]) return false; if (!forType) return false;
return IntegrationService.supportedTypeMap[integration.type][integration.integrationType] === true;
if (Object.keys(forType).length === 0) return true;
return forType[integration.integrationType]; // has sub type
} }
static hasConfig(integration: Integration): boolean { static hasConfig(integration: Integration): boolean {
@ -59,7 +86,18 @@ export class IntegrationService {
} }
static getConfigComponent(integration: Integration): ContainerContent { 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() { constructor() {

View File

@ -46,3 +46,9 @@ export interface WidgetsResponse extends ScalarRoomResponse {
} }
}[]; }[];
} }
export interface CanSendEventResponse extends ScalarRoomResponse {
event_type: string;
is_state: boolean;
response: boolean;
}

View File

@ -1,6 +1,7 @@
import { Injectable } from "@angular/core"; import { Injectable } from "@angular/core";
import * as randomString from "random-string"; import * as randomString from "random-string";
import { import {
CanSendEventResponse,
JoinRuleStateResponse, JoinRuleStateResponse,
MembershipStateResponse, MembershipStateResponse,
ScalarSuccessResponse, ScalarSuccessResponse,
@ -72,6 +73,14 @@ export class ScalarService {
this.callAction("close_scalar", {}); this.callAction("close_scalar", {});
} }
public canSendEvent(roomId: string, eventType: string, isState: boolean): Promise<CanSendEventResponse> {
return this.callAction("can_send_event", {
room_id: roomId,
event_type: eventType,
is_state: isState,
});
}
private callAction(action, payload): Promise<any> { private callAction(action, payload): Promise<any> {
let requestKey = randomString({length: 20}); let requestKey = randomString({length: 20});
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {