Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow storing thread credentials in phone keychain #20743

Merged
merged 3 commits into from
May 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
13 changes: 12 additions & 1 deletion src/external_app/external_messaging.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -146,7 +155,8 @@ type EMOutgoingMessageWithoutAnswer =
| EMOutgoingMessageMatterCommission
| EMOutgoingMessageSidebarShow
| EMOutgoingMessageTagWrite
| EMOutgoingMessageThemeUpdate;
| EMOutgoingMessageThemeUpdate
| EMOutgoingMessageThreadStoreInPlatformKeychain;

interface EMIncomingMessageRestart {
id: number;
Expand Down Expand Up @@ -239,6 +249,7 @@ export interface ExternalConfig {
hasExoPlayer: boolean;
canCommissionMatter: boolean;
canImportThreadCredentials: boolean;
canTransferThreadCredentialsToKeychain: boolean;
hasAssist: boolean;
hasBarCodeScanner: number;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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 &&
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This shouldn't have "!", right?

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({
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need a confirmation (alert, toast notification) when it's done?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

iOS currently has a circle checkmark that appears in the middle of the screen when it completes.

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:
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this needed? I think @bgoncal said iOS uses the extended mac address as the border_agent_id. My understanding is that the iOS app is only looking for mac_extended_address and active_operational_dataset currently.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But android will use the border agent id most probably

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;
}
}
Original file line number Diff line number Diff line change
@@ -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,
});
};
Original file line number Diff line number Diff line change
Expand Up @@ -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[];
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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() {
Expand Down