mirror of https://github.com/vector-im/riot-web
Merge remote-tracking branch 'origin/develop' into dbkr/trust_cross_signing_flag
commit
a9e7bf9899
|
@ -145,13 +145,34 @@ const onSecretRequested = async function({
|
||||||
console.log(`CrossSigningManager: Ignoring request from untrusted device ${deviceId}`);
|
console.log(`CrossSigningManager: Ignoring request from untrusted device ${deviceId}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const callbacks = client.getCrossSigningCacheCallbacks();
|
if (name.startsWith("m.cross_signing")) {
|
||||||
if (!callbacks.getCrossSigningKeyCache) return;
|
const callbacks = client.getCrossSigningCacheCallbacks();
|
||||||
if (name === "m.cross_signing.self_signing") {
|
if (!callbacks.getCrossSigningKeyCache) return;
|
||||||
const key = await callbacks.getCrossSigningKeyCache("self_signing");
|
/* Explicit enumeration here is deliberate – never share the master key! */
|
||||||
return key && encodeBase64(key);
|
if (name === "m.cross_signing.self_signing") {
|
||||||
} else if (name === "m.cross_signing.user_signing") {
|
const key = await callbacks.getCrossSigningKeyCache("self_signing");
|
||||||
const key = await callbacks.getCrossSigningKeyCache("user_signing");
|
if (!key) {
|
||||||
|
console.log(
|
||||||
|
`self_signing requested by ${deviceId}, but not found in cache`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return key && encodeBase64(key);
|
||||||
|
} else if (name === "m.cross_signing.user_signing") {
|
||||||
|
const key = await callbacks.getCrossSigningKeyCache("user_signing");
|
||||||
|
if (!key) {
|
||||||
|
console.log(
|
||||||
|
`user_signing requested by ${deviceId}, but not found in cache`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return key && encodeBase64(key);
|
||||||
|
}
|
||||||
|
} else if (name === "m.megolm_backup.v1") {
|
||||||
|
const key = await client._crypto.getSessionBackupPrivateKey();
|
||||||
|
if (!key) {
|
||||||
|
console.log(
|
||||||
|
`session backup key requested by ${deviceId}, but not found in cache`,
|
||||||
|
);
|
||||||
|
}
|
||||||
return key && encodeBase64(key);
|
return key && encodeBase64(key);
|
||||||
}
|
}
|
||||||
console.warn("onSecretRequested didn't recognise the secret named ", name);
|
console.warn("onSecretRequested didn't recognise the secret named ", name);
|
||||||
|
|
|
@ -24,6 +24,8 @@ import {MatrixClientPeg} from "./MatrixClientPeg";
|
||||||
import RoomViewStore from "./stores/RoomViewStore";
|
import RoomViewStore from "./stores/RoomViewStore";
|
||||||
import {IntegrationManagers} from "./integrations/IntegrationManagers";
|
import {IntegrationManagers} from "./integrations/IntegrationManagers";
|
||||||
import SettingsStore from "./settings/SettingsStore";
|
import SettingsStore from "./settings/SettingsStore";
|
||||||
|
import {Capability, KnownWidgetActions} from "./widgets/WidgetApi";
|
||||||
|
import SdkConfig from "./SdkConfig";
|
||||||
|
|
||||||
const WIDGET_API_VERSION = '0.0.2'; // Current API version
|
const WIDGET_API_VERSION = '0.0.2'; // Current API version
|
||||||
const SUPPORTED_WIDGET_API_VERSIONS = [
|
const SUPPORTED_WIDGET_API_VERSIONS = [
|
||||||
|
@ -213,11 +215,18 @@ export default class FromWidgetPostMessageApi {
|
||||||
const data = event.data.data;
|
const data = event.data.data;
|
||||||
const val = data.value;
|
const val = data.value;
|
||||||
|
|
||||||
if (ActiveWidgetStore.widgetHasCapability(widgetId, 'm.always_on_screen')) {
|
if (ActiveWidgetStore.widgetHasCapability(widgetId, Capability.AlwaysOnScreen)) {
|
||||||
ActiveWidgetStore.setWidgetPersistence(widgetId, val);
|
ActiveWidgetStore.setWidgetPersistence(widgetId, val);
|
||||||
}
|
}
|
||||||
} else if (action === 'get_openid') {
|
} else if (action === 'get_openid') {
|
||||||
// Handled by caller
|
// Handled by caller
|
||||||
|
} else if (action === KnownWidgetActions.GetRiotWebConfig) {
|
||||||
|
if (ActiveWidgetStore.widgetHasCapability(widgetId, Capability.GetRiotWebConfig)) {
|
||||||
|
this.sendResponse(event, {
|
||||||
|
api: INBOUND_API_NAME,
|
||||||
|
config: SdkConfig.get(),
|
||||||
|
});
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
console.warn('Widget postMessage event unhandled');
|
console.warn('Widget postMessage event unhandled');
|
||||||
this.sendError(event, {message: 'The postMessage was unhandled'});
|
this.sendError(event, {message: 'The postMessage was unhandled'});
|
||||||
|
|
|
@ -27,6 +27,7 @@ import {MatrixClientPeg} from "./MatrixClientPeg";
|
||||||
import SettingsStore from "./settings/SettingsStore";
|
import SettingsStore from "./settings/SettingsStore";
|
||||||
import WidgetOpenIDPermissionsDialog from "./components/views/dialogs/WidgetOpenIDPermissionsDialog";
|
import WidgetOpenIDPermissionsDialog from "./components/views/dialogs/WidgetOpenIDPermissionsDialog";
|
||||||
import WidgetUtils from "./utils/WidgetUtils";
|
import WidgetUtils from "./utils/WidgetUtils";
|
||||||
|
import {KnownWidgetActions} from "./widgets/WidgetApi";
|
||||||
|
|
||||||
if (!global.mxFromWidgetMessaging) {
|
if (!global.mxFromWidgetMessaging) {
|
||||||
global.mxFromWidgetMessaging = new FromWidgetPostMessageApi();
|
global.mxFromWidgetMessaging = new FromWidgetPostMessageApi();
|
||||||
|
@ -75,6 +76,17 @@ export default class WidgetMessaging {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tells the widget that the client is ready to handle further widget requests.
|
||||||
|
* @returns {Promise<*>} Resolves after the widget has acknowledged the ready message.
|
||||||
|
*/
|
||||||
|
flagReadyToContinue() {
|
||||||
|
return this.messageToWidget({
|
||||||
|
api: OUTBOUND_API_NAME,
|
||||||
|
action: KnownWidgetActions.ClientReady,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Request a screenshot from a widget
|
* Request a screenshot from a widget
|
||||||
* @return {Promise} To be resolved with screenshot data when it has been generated
|
* @return {Promise} To be resolved with screenshot data when it has been generated
|
||||||
|
|
|
@ -419,6 +419,12 @@ export default class AppTile extends React.Component {
|
||||||
if (this.props.onCapabilityRequest) {
|
if (this.props.onCapabilityRequest) {
|
||||||
this.props.onCapabilityRequest(requestedCapabilities);
|
this.props.onCapabilityRequest(requestedCapabilities);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We only tell Jitsi widgets that we're ready because they're realistically the only ones
|
||||||
|
// using this custom extension to the widget API.
|
||||||
|
if (this.props.type === 'jitsi') {
|
||||||
|
widgetMessaging.flagReadyToContinue();
|
||||||
|
}
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
console.log(`Failed to get capabilities for widget type ${this.props.type}`, this.props.id, err);
|
console.log(`Failed to get capabilities for widget type ${this.props.type}`, this.props.id, err);
|
||||||
});
|
});
|
||||||
|
|
|
@ -32,6 +32,8 @@ export default class CrossSigningPanel extends React.PureComponent {
|
||||||
error: null,
|
error: null,
|
||||||
crossSigningPublicKeysOnDevice: false,
|
crossSigningPublicKeysOnDevice: false,
|
||||||
crossSigningPrivateKeysInStorage: false,
|
crossSigningPrivateKeysInStorage: false,
|
||||||
|
selfSigningPrivateKeyCached: false,
|
||||||
|
userSigningPrivateKeyCached: false,
|
||||||
secretStorageKeyInAccount: false,
|
secretStorageKeyInAccount: false,
|
||||||
secretStorageKeyNeedsUpgrade: null,
|
secretStorageKeyNeedsUpgrade: null,
|
||||||
};
|
};
|
||||||
|
@ -71,10 +73,13 @@ export default class CrossSigningPanel extends React.PureComponent {
|
||||||
|
|
||||||
async _getUpdatedStatus() {
|
async _getUpdatedStatus() {
|
||||||
const cli = MatrixClientPeg.get();
|
const cli = MatrixClientPeg.get();
|
||||||
|
const pkCache = cli.getCrossSigningCacheCallbacks();
|
||||||
const crossSigning = cli._crypto._crossSigningInfo;
|
const crossSigning = cli._crypto._crossSigningInfo;
|
||||||
const secretStorage = cli._crypto._secretStorage;
|
const secretStorage = cli._crypto._secretStorage;
|
||||||
const crossSigningPublicKeysOnDevice = crossSigning.getId();
|
const crossSigningPublicKeysOnDevice = crossSigning.getId();
|
||||||
const crossSigningPrivateKeysInStorage = await crossSigning.isStoredInSecretStorage(secretStorage);
|
const crossSigningPrivateKeysInStorage = await crossSigning.isStoredInSecretStorage(secretStorage);
|
||||||
|
const selfSigningPrivateKeyCached = !!(pkCache && await pkCache.getCrossSigningKeyCache("self_signing"));
|
||||||
|
const userSigningPrivateKeyCached = !!(pkCache && await pkCache.getCrossSigningKeyCache("user_signing"));
|
||||||
const secretStorageKeyInAccount = await secretStorage.hasKey();
|
const secretStorageKeyInAccount = await secretStorage.hasKey();
|
||||||
const homeserverSupportsCrossSigning =
|
const homeserverSupportsCrossSigning =
|
||||||
await cli.doesServerSupportUnstableFeature("org.matrix.e2e_cross_signing");
|
await cli.doesServerSupportUnstableFeature("org.matrix.e2e_cross_signing");
|
||||||
|
@ -84,6 +89,8 @@ export default class CrossSigningPanel extends React.PureComponent {
|
||||||
this.setState({
|
this.setState({
|
||||||
crossSigningPublicKeysOnDevice,
|
crossSigningPublicKeysOnDevice,
|
||||||
crossSigningPrivateKeysInStorage,
|
crossSigningPrivateKeysInStorage,
|
||||||
|
selfSigningPrivateKeyCached,
|
||||||
|
userSigningPrivateKeyCached,
|
||||||
secretStorageKeyInAccount,
|
secretStorageKeyInAccount,
|
||||||
homeserverSupportsCrossSigning,
|
homeserverSupportsCrossSigning,
|
||||||
crossSigningReady,
|
crossSigningReady,
|
||||||
|
@ -130,6 +137,8 @@ export default class CrossSigningPanel extends React.PureComponent {
|
||||||
error,
|
error,
|
||||||
crossSigningPublicKeysOnDevice,
|
crossSigningPublicKeysOnDevice,
|
||||||
crossSigningPrivateKeysInStorage,
|
crossSigningPrivateKeysInStorage,
|
||||||
|
selfSigningPrivateKeyCached,
|
||||||
|
userSigningPrivateKeyCached,
|
||||||
secretStorageKeyInAccount,
|
secretStorageKeyInAccount,
|
||||||
homeserverSupportsCrossSigning,
|
homeserverSupportsCrossSigning,
|
||||||
crossSigningReady,
|
crossSigningReady,
|
||||||
|
@ -209,6 +218,14 @@ export default class CrossSigningPanel extends React.PureComponent {
|
||||||
<td>{_t("Cross-signing private keys:")}</td>
|
<td>{_t("Cross-signing private keys:")}</td>
|
||||||
<td>{crossSigningPrivateKeysInStorage ? _t("in secret storage") : _t("not found")}</td>
|
<td>{crossSigningPrivateKeysInStorage ? _t("in secret storage") : _t("not found")}</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>{_t("Self signing private key:")}</td>
|
||||||
|
<td>{selfSigningPrivateKeyCached ? _t("cached locally") : _t("not found locally")}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>{_t("User signing private key:")}</td>
|
||||||
|
<td>{userSigningPrivateKeyCached ? _t("cached locally") : _t("not found locally")}</td>
|
||||||
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>{_t("Secret storage public key:")}</td>
|
<td>{_t("Secret storage public key:")}</td>
|
||||||
<td>{secretStorageKeyInAccount ? _t("in account data") : _t("not found")}</td>
|
<td>{secretStorageKeyInAccount ? _t("in account data") : _t("not found")}</td>
|
||||||
|
|
|
@ -583,6 +583,10 @@
|
||||||
"not found": "not found",
|
"not found": "not found",
|
||||||
"Cross-signing private keys:": "Cross-signing private keys:",
|
"Cross-signing private keys:": "Cross-signing private keys:",
|
||||||
"in secret storage": "in secret storage",
|
"in secret storage": "in secret storage",
|
||||||
|
"Self signing private key:": "Self signing private key:",
|
||||||
|
"cached locally": "cached locally",
|
||||||
|
"not found locally": "not found locally",
|
||||||
|
"User signing private key:": "User signing private key:",
|
||||||
"Secret storage public key:": "Secret storage public key:",
|
"Secret storage public key:": "Secret storage public key:",
|
||||||
"in account data": "in account data",
|
"in account data": "in account data",
|
||||||
"Homeserver feature support:": "Homeserver feature support:",
|
"Homeserver feature support:": "Homeserver feature support:",
|
||||||
|
|
|
@ -118,6 +118,10 @@ export default async function sendBugReport(bugReportEndpoint, opts) {
|
||||||
try {
|
try {
|
||||||
body.append("storageManager_persisted", await navigator.storage.persisted());
|
body.append("storageManager_persisted", await navigator.storage.persisted());
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
|
} else if (document.hasStorageAccess) { // Safari
|
||||||
|
try {
|
||||||
|
body.append("storageManager_persisted", await document.hasStorageAccess());
|
||||||
|
} catch (e) {}
|
||||||
}
|
}
|
||||||
if (navigator.storage && navigator.storage.estimate) {
|
if (navigator.storage && navigator.storage.estimate) {
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -48,6 +48,11 @@ export function tryPersistStorage() {
|
||||||
navigator.storage.persist().then(persistent => {
|
navigator.storage.persist().then(persistent => {
|
||||||
console.log("StorageManager: Persistent?", persistent);
|
console.log("StorageManager: Persistent?", persistent);
|
||||||
});
|
});
|
||||||
|
} else if (document.requestStorageAccess) { // Safari
|
||||||
|
document.requestStorageAccess().then(
|
||||||
|
() => console.log("StorageManager: Persistent?", true),
|
||||||
|
() => console.log("StorageManager: Persistent?", false),
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
console.log("StorageManager: Persistence unsupported");
|
console.log("StorageManager: Persistence unsupported");
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,6 +28,7 @@ const WIDGET_WAIT_TIME = 20000;
|
||||||
import SettingsStore from "../settings/SettingsStore";
|
import SettingsStore from "../settings/SettingsStore";
|
||||||
import ActiveWidgetStore from "../stores/ActiveWidgetStore";
|
import ActiveWidgetStore from "../stores/ActiveWidgetStore";
|
||||||
import {IntegrationManagers} from "../integrations/IntegrationManagers";
|
import {IntegrationManagers} from "../integrations/IntegrationManagers";
|
||||||
|
import {Capability} from "../widgets/WidgetApi";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encodes a URI according to a set of template variables. Variables will be
|
* Encodes a URI according to a set of template variables. Variables will be
|
||||||
|
@ -454,12 +455,15 @@ export default class WidgetUtils {
|
||||||
static getCapWhitelistForAppTypeInRoomId(appType, roomId) {
|
static getCapWhitelistForAppTypeInRoomId(appType, roomId) {
|
||||||
const enableScreenshots = SettingsStore.getValue("enableWidgetScreenshots", roomId);
|
const enableScreenshots = SettingsStore.getValue("enableWidgetScreenshots", roomId);
|
||||||
|
|
||||||
const capWhitelist = enableScreenshots ? ["m.capability.screenshot"] : [];
|
const capWhitelist = enableScreenshots ? [Capability.Screenshot] : [];
|
||||||
|
|
||||||
// Obviously anyone that can add a widget can claim it's a jitsi widget,
|
// Obviously anyone that can add a widget can claim it's a jitsi widget,
|
||||||
// so this doesn't really offer much over the set of domains we load
|
// so this doesn't really offer much over the set of domains we load
|
||||||
// widgets from at all, but it probably makes sense for sanity.
|
// widgets from at all, but it probably makes sense for sanity.
|
||||||
if (appType == 'jitsi') capWhitelist.push("m.always_on_screen");
|
if (appType === 'jitsi') {
|
||||||
|
capWhitelist.push(Capability.AlwaysOnScreen);
|
||||||
|
capWhitelist.push(Capability.GetRiotWebConfig);
|
||||||
|
}
|
||||||
|
|
||||||
return capWhitelist;
|
return capWhitelist;
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,7 @@ export enum Capability {
|
||||||
Screenshot = "m.capability.screenshot",
|
Screenshot = "m.capability.screenshot",
|
||||||
Sticker = "m.sticker",
|
Sticker = "m.sticker",
|
||||||
AlwaysOnScreen = "m.always_on_screen",
|
AlwaysOnScreen = "m.always_on_screen",
|
||||||
|
GetRiotWebConfig = "im.vector.web.riot_config",
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum KnownWidgetActions {
|
export enum KnownWidgetActions {
|
||||||
|
@ -33,7 +34,10 @@ export enum KnownWidgetActions {
|
||||||
UpdateVisibility = "visibility",
|
UpdateVisibility = "visibility",
|
||||||
ReceiveOpenIDCredentials = "openid_credentials",
|
ReceiveOpenIDCredentials = "openid_credentials",
|
||||||
SetAlwaysOnScreen = "set_always_on_screen",
|
SetAlwaysOnScreen = "set_always_on_screen",
|
||||||
|
GetRiotWebConfig = "im.vector.web.riot_config",
|
||||||
|
ClientReady = "im.vector.ready",
|
||||||
}
|
}
|
||||||
|
|
||||||
export type WidgetAction = KnownWidgetActions | string;
|
export type WidgetAction = KnownWidgetActions | string;
|
||||||
|
|
||||||
export enum WidgetApiType {
|
export enum WidgetApiType {
|
||||||
|
@ -63,10 +67,15 @@ export interface FromWidgetRequest extends WidgetRequest {
|
||||||
*/
|
*/
|
||||||
export class WidgetApi {
|
export class WidgetApi {
|
||||||
private origin: string;
|
private origin: string;
|
||||||
private inFlightRequests: {[requestId: string]: (reply: FromWidgetRequest) => void} = {};
|
private inFlightRequests: { [requestId: string]: (reply: FromWidgetRequest) => void } = {};
|
||||||
private readyPromise: Promise<any>;
|
private readyPromise: Promise<any>;
|
||||||
private readyPromiseResolve: () => void;
|
private readyPromiseResolve: () => void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set this to true if your widget is expecting a ready message from the client. False otherwise (default).
|
||||||
|
*/
|
||||||
|
public expectingExplicitReady = false;
|
||||||
|
|
||||||
constructor(currentUrl: string, private widgetId: string, private requestedCapabilities: string[]) {
|
constructor(currentUrl: string, private widgetId: string, private requestedCapabilities: string[]) {
|
||||||
this.origin = new URL(currentUrl).origin;
|
this.origin = new URL(currentUrl).origin;
|
||||||
|
|
||||||
|
@ -83,7 +92,14 @@ export class WidgetApi {
|
||||||
|
|
||||||
if (payload.action === KnownWidgetActions.GetCapabilities) {
|
if (payload.action === KnownWidgetActions.GetCapabilities) {
|
||||||
this.onCapabilitiesRequest(<ToWidgetRequest>payload);
|
this.onCapabilitiesRequest(<ToWidgetRequest>payload);
|
||||||
|
if (!this.expectingExplicitReady) {
|
||||||
|
this.readyPromiseResolve();
|
||||||
|
}
|
||||||
|
} else if (payload.action === KnownWidgetActions.ClientReady) {
|
||||||
this.readyPromiseResolve();
|
this.readyPromiseResolve();
|
||||||
|
|
||||||
|
// Automatically acknowledge so we can move on
|
||||||
|
this.replyToRequest(<ToWidgetRequest>payload, {});
|
||||||
} else {
|
} else {
|
||||||
console.warn(`[WidgetAPI] Got unexpected action: ${payload.action}`);
|
console.warn(`[WidgetAPI] Got unexpected action: ${payload.action}`);
|
||||||
}
|
}
|
||||||
|
@ -126,7 +142,10 @@ export class WidgetApi {
|
||||||
data: payload,
|
data: payload,
|
||||||
response: {}, // Not used at this layer - it's used when the client responds
|
response: {}, // Not used at this layer - it's used when the client responds
|
||||||
};
|
};
|
||||||
this.inFlightRequests[request.requestId] = callback;
|
|
||||||
|
if (callback) {
|
||||||
|
this.inFlightRequests[request.requestId] = callback;
|
||||||
|
}
|
||||||
|
|
||||||
console.log(`[WidgetAPI] Sending request: `, request);
|
console.log(`[WidgetAPI] Sending request: `, request);
|
||||||
window.parent.postMessage(request, "*");
|
window.parent.postMessage(request, "*");
|
||||||
|
@ -134,7 +153,16 @@ export class WidgetApi {
|
||||||
|
|
||||||
public setAlwaysOnScreen(onScreen: boolean): Promise<any> {
|
public setAlwaysOnScreen(onScreen: boolean): Promise<any> {
|
||||||
return new Promise<any>(resolve => {
|
return new Promise<any>(resolve => {
|
||||||
this.callAction(KnownWidgetActions.SetAlwaysOnScreen, {value: onScreen}, resolve);
|
this.callAction(KnownWidgetActions.SetAlwaysOnScreen, {value: onScreen}, null);
|
||||||
|
resolve(); // SetAlwaysOnScreen is currently fire-and-forget, but that could change.
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public getRiotConfig(): Promise<any> {
|
||||||
|
return new Promise<any>(resolve => {
|
||||||
|
this.callAction(KnownWidgetActions.GetRiotWebConfig, {}, response => {
|
||||||
|
resolve(response.response.config);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue