Allow storing thread credentials in phone keychain (#20743)

* Allow storing thread credentials in phone keychain

* Update dialog-thread-dataset.ts

* use preferred of dataset if available
This commit is contained in:
Bram Kragten 2024-05-16 10:01:40 +02:00 committed by GitHub
parent 9264adb799
commit bfa293ae3a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 126 additions and 30 deletions

View File

@ -129,6 +129,15 @@ interface EMOutgoingMessageAssistShow extends EMMessage {
};
}
interface EMOutgoingMessageThreadStoreInPlatformKeychain extends EMMessage {
type: "thread/store_in_platform_keychain";
payload: {
mac_extended_address: string;
border_agent_id: string | null;
active_operational_dataset: string;
};
}
type EMOutgoingMessageWithoutAnswer =
| EMMessageResultError
| EMMessageResultSuccess
@ -146,7 +155,8 @@ type EMOutgoingMessageWithoutAnswer =
| EMOutgoingMessageMatterCommission
| EMOutgoingMessageSidebarShow
| EMOutgoingMessageTagWrite
| EMOutgoingMessageThemeUpdate;
| EMOutgoingMessageThemeUpdate
| EMOutgoingMessageThreadStoreInPlatformKeychain;
interface EMIncomingMessageRestart {
id: number;
@ -239,6 +249,7 @@ export interface ExternalConfig {
hasExoPlayer: boolean;
canCommissionMatter: boolean;
canImportThreadCredentials: boolean;
canTransferThreadCredentialsToKeychain: boolean;
hasAssist: boolean;
hasBarCodeScanner: number;
}

View File

@ -0,0 +1,90 @@
import { LitElement, html, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { fireEvent } from "../../../../../common/dom/fire_event";
import { HassDialog } from "../../../../../dialogs/make-dialog-manager";
import { HomeAssistant } from "../../../../../types";
import { DialogThreadDatasetParams } from "./show-dialog-thread-dataset";
import { createCloseHeading } from "../../../../../components/ha-dialog";
@customElement("ha-dialog-thread-dataset")
class DialogThreadDataset extends LitElement implements HassDialog {
@property({ attribute: false }) public hass!: HomeAssistant;
@state() private _params?: DialogThreadDatasetParams;
public async showDialog(
params: DialogThreadDatasetParams
): Promise<Promise<void>> {
this._params = params;
}
public closeDialog(): void {
this._params = undefined;
fireEvent(this, "dialog-closed", { dialog: this.localName });
}
protected render() {
if (!this._params) {
return nothing;
}
const network = this._params.network;
const dataset = network.dataset!;
const otbrInfo = this._params.otbrInfo;
const hasOTBR =
otbrInfo &&
dataset.extended_pan_id &&
otbrInfo.active_dataset_tlvs?.includes(dataset.extended_pan_id);
const canImportKeychain =
hasOTBR &&
!this.hass.auth.external?.config.canTransferThreadCredentialsToKeychain &&
network.routers?.length;
return html`<ha-dialog
open
.hideActions=${!canImportKeychain}
@closed=${this.closeDialog}
.heading=${createCloseHeading(this.hass, network.name)}
>
<div>
Network name: ${dataset.network_name}<br />
Channel: ${dataset.channel}<br />
Dataset id: ${dataset.dataset_id}<br />
Pan id: ${dataset.pan_id}<br />
Extended Pan id: ${dataset.extended_pan_id}<br />
${hasOTBR
? html`OTBR URL: ${otbrInfo.url}<br />
Active dataset TLVs: ${otbrInfo.active_dataset_tlvs}`
: nothing}
</div>
${canImportKeychain
? html`<ha-button slot="primary-action" @click=${this._sendCredentials}
>Send credentials to phone</ha-button
>`
: nothing}
</ha-dialog>`;
}
private _sendCredentials() {
this.hass.auth.external!.fireMessage({
type: "thread/store_in_platform_keychain",
payload: {
mac_extended_address:
this._params?.network.dataset?.preferred_extended_address ||
this._params!.network.routers![0]!.extended_address,
border_agent_id:
this._params?.network.dataset?.preferred_border_agent_id ||
this._params!.network.routers![0]!.border_agent_id,
active_operational_dataset: this._params!.otbrInfo!.active_dataset_tlvs,
},
});
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-dialog-thread-dataset": DialogThreadDataset;
}
}

View File

@ -0,0 +1,19 @@
import { fireEvent } from "../../../../../common/dom/fire_event";
import { OTBRInfo } from "../../../../../data/otbr";
import { ThreadNetwork } from "./thread-config-panel";
export interface DialogThreadDatasetParams {
network: ThreadNetwork;
otbrInfo?: OTBRInfo;
}
export const showThreadDatasetDialog = (
element: HTMLElement,
dialogParams: DialogThreadDatasetParams
): void => {
fireEvent(element, "show-dialog", {
dialogTag: "ha-dialog-thread-dataset",
dialogImport: () => import("./dialog-thread-dataset"),
dialogParams,
});
};

View File

@ -55,8 +55,9 @@ import { HomeAssistant } from "../../../../../types";
import { brandsUrl } from "../../../../../util/brands-url";
import { fileDownload } from "../../../../../util/file_download";
import { documentationUrl } from "../../../../../util/documentation-url";
import { showThreadDatasetDialog } from "./show-dialog-thread-dataset";
interface ThreadNetwork {
export interface ThreadNetwork {
name: string;
dataset?: ThreadDataSet;
routers?: ThreadRouter[];
@ -164,7 +165,7 @@ export class ThreadConfigPanel extends SubscribeMixin(LitElement) {
${network.name}${network.dataset
? html`<div>
<ha-icon-button
.networkDataset=${network.dataset}
.network=${network}
.path=${mdiInformationOutline}
@click=${this._showDatasetInfo}
></ha-icon-button
@ -306,33 +307,8 @@ export class ThreadConfigPanel extends SubscribeMixin(LitElement) {
}
private async _showDatasetInfo(ev: Event) {
const dataset = (ev.currentTarget as any).networkDataset as ThreadDataSet;
if (this._otbrInfo) {
if (
dataset.extended_pan_id &&
this._otbrInfo.active_dataset_tlvs?.includes(dataset.extended_pan_id)
) {
showAlertDialog(this, {
title: dataset.network_name,
text: html`Network name: ${dataset.network_name}<br />
Channel: ${dataset.channel}<br />
Dataset id: ${dataset.dataset_id}<br />
Pan id: ${dataset.pan_id}<br />
Extended Pan id: ${dataset.extended_pan_id}<br />
OTBR URL: ${this._otbrInfo.url}<br />
Active dataset TLVs: ${this._otbrInfo.active_dataset_tlvs}`,
});
return;
}
}
showAlertDialog(this, {
title: dataset.network_name,
text: html`Network name: ${dataset.network_name}<br />
Channel: ${dataset.channel}<br />
Dataset id: ${dataset.dataset_id}<br />
Pan id: ${dataset.pan_id}<br />
Extended Pan id: ${dataset.extended_pan_id}`,
});
const network = (ev.currentTarget as any).network as ThreadNetwork;
showThreadDatasetDialog(this, { network, otbrInfo: this._otbrInfo });
}
private _importExternalThreadCredentials() {