mirror of
https://github.com/home-assistant/iOS.git
synced 2026-06-16 13:26:27 -05:00
<!-- Thank you for submitting a Pull Request and helping to improve Home Assistant. Please complete the following sections to help the processing and review of your changes. Please do not delete anything from this template. --> ## Summary <!-- Provide a brief summary of the changes you have made and most importantly what they aim to achieve --> The issue is the new WebView bridge origin check. It compares configured URL hosts with WKSecurityOrigin.host and IPv6 can appear as fd00::abcd in one API and [fd00::abcd] in another, so the bridge rejects valid frontend messages. That blocks external auth and leaves the frontend loading, while widgets still work. This PR normalizes the url ## Screenshots <!-- If this is a user-facing change not in the frontend, please include screenshots in light and dark mode. --> ## Link to pull request in Documentation repository <!-- Pull requests that add, change or remove functionality must have a corresponding pull request in the Companion App Documentation repository (https://github.com/home-assistant/companion.home-assistant). Please add the number of this pull request after the "#" --> Documentation: home-assistant/companion.home-assistant# ## Any other notes <!-- If there is any other information of note, like if this Pull Request is part of a bigger change, please include it here. -->
109 lines
4.0 KiB
Swift
109 lines
4.0 KiB
Swift
@testable import HomeAssistant
|
|
import Shared
|
|
import Testing
|
|
import WebKit
|
|
|
|
struct SafeScriptMessageHandlerTests {
|
|
@Test func allowsMainFrameMessageFromConfiguredServerOrigin() {
|
|
ServerFixture.reset()
|
|
let handler = SafeScriptMessageHandler(
|
|
server: ServerFixture.withRemoteConnection,
|
|
delegate: NoOpScriptMessageHandler()
|
|
)
|
|
|
|
#expect(handler.shouldAllowMessage(isMainFrame: true, scheme: "https", host: "external.example.com", port: 443))
|
|
#expect(handler.shouldAllowMessage(isMainFrame: true, scheme: "http", host: "internal.example.com", port: 80))
|
|
#expect(handler.shouldAllowMessage(isMainFrame: true, scheme: "https", host: "ui.nabu.casa", port: 443))
|
|
}
|
|
|
|
@Test func allowsMainFrameMessageWhenImplicitPortsAreReportedAsZero() {
|
|
ServerFixture.reset()
|
|
let handler = SafeScriptMessageHandler(
|
|
server: ServerFixture.withRemoteConnection,
|
|
delegate: NoOpScriptMessageHandler()
|
|
)
|
|
|
|
#expect(handler.shouldAllowMessage(isMainFrame: true, scheme: "https", host: "external.example.com", port: 0))
|
|
#expect(handler.shouldAllowMessage(isMainFrame: true, scheme: "http", host: "internal.example.com", port: 0))
|
|
#expect(handler.shouldAllowMessage(isMainFrame: true, scheme: "https", host: "ui.nabu.casa", port: 0))
|
|
}
|
|
|
|
@Test func allowsMainFrameMessageFromBracketedIPv6Host() {
|
|
let handler = SafeScriptMessageHandler(
|
|
server: server(internalURL: URL(string: "http://[fd00::abcd]:8123")!),
|
|
delegate: NoOpScriptMessageHandler()
|
|
)
|
|
|
|
#expect(handler.shouldAllowMessage(isMainFrame: true, scheme: "http", host: "[fd00::abcd]", port: 8123))
|
|
#expect(handler.shouldAllowMessage(isMainFrame: true, scheme: "http", host: "fd00::abcd", port: 8123))
|
|
}
|
|
|
|
@Test func rejectsMessageFromOriginOutsideConfiguredServerOrigins() {
|
|
ServerFixture.reset()
|
|
let handler = SafeScriptMessageHandler(
|
|
server: ServerFixture.withRemoteConnection,
|
|
delegate: NoOpScriptMessageHandler()
|
|
)
|
|
|
|
#expect(!handler.shouldAllowMessage(isMainFrame: true, scheme: "https", host: "evil.example.com", port: 443))
|
|
#expect(!handler.shouldAllowMessage(
|
|
isMainFrame: true,
|
|
scheme: "https",
|
|
host: "external.example.com",
|
|
port: 8123
|
|
))
|
|
#expect(!handler.shouldAllowMessage(isMainFrame: true, scheme: "http", host: "external.example.com", port: 443))
|
|
}
|
|
|
|
@Test func rejectsIframeMessageEvenWhenHostIsAllowed() {
|
|
ServerFixture.reset()
|
|
let handler = SafeScriptMessageHandler(
|
|
server: ServerFixture.withRemoteConnection,
|
|
delegate: NoOpScriptMessageHandler()
|
|
)
|
|
|
|
#expect(!handler.shouldAllowMessage(
|
|
isMainFrame: false,
|
|
scheme: "https",
|
|
host: "external.example.com",
|
|
port: 443
|
|
))
|
|
}
|
|
}
|
|
|
|
private func server(internalURL: URL) -> Server {
|
|
var info = ServerInfo(
|
|
name: "IPv6 Server",
|
|
connection: .init(
|
|
externalURL: nil,
|
|
internalURL: internalURL,
|
|
cloudhookURL: nil,
|
|
remoteUIURL: nil,
|
|
webhookID: "webhook-id",
|
|
webhookSecret: nil,
|
|
internalSSIDs: nil,
|
|
internalHardwareAddresses: nil,
|
|
isLocalPushEnabled: false,
|
|
securityExceptions: .init(exceptions: []),
|
|
connectionAccessSecurityLevel: .undefined
|
|
),
|
|
token: .init(
|
|
accessToken: "access-token",
|
|
refreshToken: "refresh-token",
|
|
expiration: Date()
|
|
),
|
|
version: "2026.4.1"
|
|
)
|
|
|
|
return Server(identifier: "ipv6", getter: {
|
|
info
|
|
}, setter: { newInfo in
|
|
info = newInfo
|
|
return true
|
|
})
|
|
}
|
|
|
|
private final class NoOpScriptMessageHandler: NSObject, WKScriptMessageHandler {
|
|
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {}
|
|
}
|