Fix passwordless OWE Wi-Fi handling (#7238)

This commit is contained in:
David Heinemeier Hansson
2026-08-17 03:36:59 -04:00
committed by GitHub
parent 30f7a06090
commit 1c1116b626
3 changed files with 127 additions and 45 deletions
+24 -13
View File
@@ -299,8 +299,17 @@ function wifiSectionTitle(wifiNetworks, index) {
return ""
}
function isProtected(security, openSecurity) {
return security !== openSecurity
// OWE (Enhanced Open) encrypts traffic without authenticating the user, so it
// has no credentials to collect. The panel's lock is a credentials-required
// affordance, so OWE should neither show it nor open its attached prompt.
function requiresCredentials(security, openSecurity, oweSecurity) {
// Only explicit passwordless types bypass the prompt. Unknown security
// stays credentialed as the conservative fallback.
return security !== openSecurity && security !== oweSecurity
}
function canForgetNetwork(network) {
return !!(network && network.known && !network.connected)
}
// The password arrives on stdin and reaches nmcli through the scriptable
@@ -316,10 +325,10 @@ var enterpriseConnectScript =
" && nmcli connection up uuid \"$u\"" +
" || { nmcli connection delete uuid \"$u\" >/dev/null 2>&1; false; }"
function networkFailureReason(reason, reasons) {
function networkFailureReason(reason, needsCredentials, reasons) {
var r = reasons || {}
if (reason === r.NoSecrets) return "Passphrase required"
if (reason === r.WifiAuthTimeout) return "Wrong password"
if (needsCredentials && reason === r.NoSecrets) return "Passphrase required"
if (needsCredentials && reason === r.WifiAuthTimeout) return "Wrong password"
if (reason === r.WifiNetworkLost) return "Network lost"
if (reason === r.WifiClientDisconnected) return "Disconnected"
if (reason === r.WifiClientFailed) return "Connection failed"
@@ -327,14 +336,15 @@ function networkFailureReason(reason, reasons) {
}
// Whether a failed connect should reopen the passphrase prompt. NoSecrets
// always means credentials are missing. An auth timeout on a protected
// network means the saved passphrase is wrong (the same profile a first
// failed attempt leaves behind as "known"), so the user needs a chance to
// re-enter it -- connectWithPsk overwrites the stored PSK on submit.
function shouldRepromptPassphrase(reason, isProtected, reasons) {
// means credentials are missing only for a network that actually uses them.
// An auth timeout on such a network means the saved passphrase is wrong (the
// same profile a first failed attempt leaves behind as "known"), so the user
// needs a chance to re-enter it -- connectWithPsk overwrites the stored PSK on
// submit.
function shouldRepromptPassphrase(reason, needsCredentials, reasons) {
var r = reasons || {}
if (reason === r.NoSecrets) return true
return !!isProtected && reason === r.WifiAuthTimeout
if (!needsCredentials) return false
return reason === r.NoSecrets || reason === r.WifiAuthTimeout
}
if (typeof module !== "undefined") {
@@ -361,7 +371,8 @@ if (typeof module !== "undefined") {
wifiRow: wifiRow,
sortWifiRows: sortWifiRows,
wifiSectionTitle: wifiSectionTitle,
isProtected: isProtected,
requiresCredentials: requiresCredentials,
canForgetNetwork: canForgetNetwork,
enterpriseConnectScript: enterpriseConnectScript,
networkFailureReason: networkFailureReason,
shouldRepromptPassphrase: shouldRepromptPassphrase
+28 -26
View File
@@ -398,7 +398,7 @@ Panel {
}
function canForgetNetwork(net) {
return !!(net && net.known && isProtected(net.security) && !net.connected)
return Model.canForgetNetwork(net)
}
function canShareNetwork(net) {
@@ -417,8 +417,8 @@ Panel {
}
// Enter/Space on the highlighted row. Mirrors row-click semantics:
// connected → disconnect, protected-unknown → password prompt,
// open/known → connect.
// connected → disconnect, credentials-required/unknown → prompt,
// passwordless/known → connect.
function activateSelected() {
if (busy || selectedIndex < 0 || selectedIndex >= wifiNetworks.length) return
var net = wifiNetworks[selectedIndex]
@@ -428,8 +428,8 @@ Panel {
// connectedWifiNetwork when handed null, so a row left stale by scan churn
// would otherwise tear down whatever is connected now instead.
if (net.connected) { disconnectRow(net.ssid); return }
if (isProtected(net.security) && !net.known) { openPasswordPrompt(net.ssid); return }
connectKnown(net.ssid)
if (requiresCredentials(net.security) && !net.known) { openPasswordPrompt(net.ssid); return }
connectDirectly(net.ssid)
}
// Bar pill state, derived from the native NetworkManager service so the
@@ -680,8 +680,8 @@ Panel {
root.close()
}
function isProtected(security) {
return Model.isProtected(security, WifiSecurityType.Open)
function requiresCredentials(security) {
return Model.requiresCredentials(security, WifiSecurityType.Open, WifiSecurityType.Owe)
}
function openPasswordPrompt(ssid) {
@@ -735,18 +735,18 @@ Panel {
if (!network || actionKind === "" || actionSsid !== (network.name || "")) return
actionTimeout.stop()
failureSsid = actionSsid
failureReason = networkFailureReason(reason)
failureReason = networkFailureReason(reason, requiresCredentials(network.security))
actionSsid = ""
actionKind = ""
refresh()
}
function networkFailureReason(reason) {
return Model.networkFailureReason(reason, connectionFailReasons)
function networkFailureReason(reason, needsCredentials) {
return Model.networkFailureReason(reason, needsCredentials, connectionFailReasons)
}
function shouldRepromptPassphrase(reason, isProtected) {
return Model.shouldRepromptPassphrase(reason, isProtected, connectionFailReasons)
function shouldRepromptPassphrase(reason, needsCredentials) {
return Model.shouldRepromptPassphrase(reason, needsCredentials, connectionFailReasons)
}
function checkActionCompletion(network) {
@@ -756,7 +756,7 @@ Panel {
else if (actionKind === "forget" && !network.known && !network.stateChanging) clearNetworkAction()
}
function connectKnown(ssid) {
function connectDirectly(ssid) {
runNetworkAction("connect", networkForSsid(ssid), function(network) { network.connect() })
}
@@ -1587,8 +1587,8 @@ Panel {
}
// A single Wi-Fi network entry. Collapses to a one-line pill normally;
// expands inline to a passphrase prompt when the user picks a protected
// network we don't have credentials for. Clicking a connected row
// expands inline to a passphrase prompt when the user picks a network that
// requires credentials we do not have. Clicking a connected row
// disconnects.
component NetworkRow: CursorSurface {
id: row
@@ -1597,14 +1597,14 @@ Panel {
readonly property bool isConnected: net && net.connected
readonly property bool isKnown: !!(net && net.known)
readonly property bool isProtected: net ? root.isProtected(net.security) : false
readonly property bool requiresCredentials: net ? root.requiresCredentials(net.security) : false
readonly property bool isEnterprise: net
? (net.security === WifiSecurityType.Wpa2Eap || net.security === WifiSecurityType.WpaEap)
: false
readonly property bool canForgetFromLock: isKnown && isProtected && !isConnected
readonly property bool canForget: root.canForgetNetwork(net)
readonly property bool isSelected: root.focusSection === "wifi" && root.selectedIndex === index
readonly property bool forgetFocused: isSelected && root.wifiActionFocused && canForgetFromLock
readonly property bool forgetVisible: canForgetFromLock && (forgetFocused || rightMouse.containsMouse)
readonly property bool forgetFocused: isSelected && root.wifiActionFocused && canForget
readonly property bool forgetVisible: canForget && (!requiresCredentials || forgetFocused || rightMouse.containsMouse)
hasCursor: root.cursorActive && isSelected && !root.wifiActionFocused
current: isConnected
@@ -1631,7 +1631,7 @@ Panel {
// failNetworkAction, which clears the action state.
var ours = root.actionKind === "connect" && root.actionSsid === (row.net.ssid || "")
root.failNetworkAction(root.networkForSsid(row.net.ssid), reason)
if (ours && root.shouldRepromptPassphrase(reason, row.isProtected)) root.openPasswordPrompt(row.net.ssid)
if (ours && root.shouldRepromptPassphrase(reason, row.requiresCredentials)) root.openPasswordPrompt(row.net.ssid)
}
function onConnectedChanged() {
if (row.net) root.checkActionCompletion(root.networkForSsid(row.net.ssid))
@@ -1692,11 +1692,11 @@ Panel {
root.disconnectRow(row.net.ssid)
return
}
if (row.isProtected && !row.isKnown) {
if (row.requiresCredentials && !row.isKnown) {
root.openPasswordPrompt(row.net.ssid)
return
}
root.connectKnown(row.net.ssid)
root.connectDirectly(row.net.ssid)
}
}
@@ -1719,11 +1719,12 @@ Panel {
anchors.verticalCenter: parent.verticalCenter
}
// Shows a lock glyph for protected networks. Known disconnected
// networks reveal the forget action when hovering that right edge.
// The right edge shows a lock for networks that require credentials and
// reveals Forget on hover. Known passwordless networks show Forget
// directly rather than reserving an invisible or misleading target.
Item {
id: rightAction
visible: row.isProtected
visible: row.requiresCredentials || row.canForget
width: Style.space(22)
implicitHeight: lockIndicator.implicitHeight
anchors.right: parent.right
@@ -1731,6 +1732,7 @@ Panel {
Text {
id: lockIndicator
visible: row.requiresCredentials || row.forgetVisible
width: parent.width
anchors.verticalCenter: parent.verticalCenter
horizontalAlignment: Text.AlignHCenter
@@ -1754,7 +1756,7 @@ Panel {
anchors.fill: parent
hoverEnabled: true
acceptedButtons: Qt.LeftButton
enabled: row.canForgetFromLock && !root.busy
enabled: row.canForget && !root.busy
cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
onContainsMouseChanged: if (containsMouse) { root.cursorActive = true; root.focusSection = "wifi"; root.selectedIndex = row.index; root.wifiActionFocused = true }
onClicked: if (row.net) root.forget(row.net)