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 { 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 {

View File

@ -7,7 +7,7 @@
<ui-switch [checked]="integration.isEnabled" size="small" [disabled]="integration.isBroken"
(change)="update()"></ui-switch>
</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>
</div>
<div class="toolbar">

View File

@ -41,4 +41,8 @@ export class IntegrationComponent {
size: "lg"
}, 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 { 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 = <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:
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 { 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() {

View File

@ -45,4 +45,10 @@ export interface WidgetsResponse extends ScalarRoomResponse {
data?: any;
}
}[];
}
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 * 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<CanSendEventResponse> {
return this.callAction("can_send_event", {
room_id: roomId,
event_type: eventType,
is_state: isState,
});
}
private callAction(action, payload): Promise<any> {
let requestKey = randomString({length: 20});
return new Promise((resolve, reject) => {