1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-08-07 05:58:28 -04:00

network/iwd: improve bad credential handling

related #2804

port: 1.5
This commit is contained in:
bbedward
2026-07-10 10:36:22 -04:00
parent 4bdb7d17b2
commit 867102b82c
7 changed files with 282 additions and 16 deletions
+3
View File
@@ -126,3 +126,6 @@ __pycache__
distro/void/temp/ distro/void/temp/
distro/void/hostdir/ distro/void/hostdir/
distro/void/masterdir*/ distro/void/masterdir*/
# Often gets built
core/dms
+24 -11
View File
@@ -22,13 +22,14 @@ type ConnectionStateChecker interface {
} }
type IWDAgent struct { type IWDAgent struct {
conn *dbus.Conn conn *dbus.Conn
objPath dbus.ObjectPath objPath dbus.ObjectPath
prompts PromptBroker prompts PromptBroker
onUserCanceled func() onUserCanceled func()
onPromptRetry func(ssid string) onPromptRetry func(ssid string)
lastRequestSSID string takePendingSecret func(ssid string) (string, bool)
stateChecker ConnectionStateChecker lastRequestSSID string
stateChecker ConnectionStateChecker
} }
const iwdAgentIntrospectXML = ` const iwdAgentIntrospectXML = `
@@ -119,6 +120,13 @@ func (a *IWDAgent) RequestPassphrase(network dbus.ObjectPath) (string, *dbus.Err
return "", dbus.NewError("net.connman.iwd.Agent.Error.Canceled", nil) return "", dbus.NewError("net.connman.iwd.Agent.Error.Canceled", nil)
} }
if a.takePendingSecret != nil {
if psk, ok := a.takePendingSecret(ssid); ok {
a.lastRequestSSID = ssid
return psk, nil
}
}
if a.prompts == nil { if a.prompts == nil {
if a.onUserCanceled != nil { if a.onUserCanceled != nil {
a.onUserCanceled() a.onUserCanceled()
@@ -126,19 +134,24 @@ func (a *IWDAgent) RequestPassphrase(network dbus.ObjectPath) (string, *dbus.Err
return "", dbus.NewError("net.connman.iwd.Agent.Error.Canceled", nil) return "", dbus.NewError("net.connman.iwd.Agent.Error.Canceled", nil)
} }
if a.lastRequestSSID == ssid { retry := a.lastRequestSSID == ssid
if a.onPromptRetry != nil { if retry && a.onPromptRetry != nil {
a.onPromptRetry(ssid) a.onPromptRetry(ssid)
}
} }
a.lastRequestSSID = ssid a.lastRequestSSID = ssid
reason := ""
if retry {
reason = "wrong-password"
}
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute) ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
defer cancel() defer cancel()
token, err := a.prompts.Ask(ctx, PromptRequest{ token, err := a.prompts.Ask(ctx, PromptRequest{
SSID: ssid, SSID: ssid,
Fields: []string{"psk"}, Fields: []string{"psk"},
Reason: reason,
}) })
if err != nil { if err != nil {
if a.onUserCanceled != nil { if a.onUserCanceled != nil {
@@ -24,6 +24,7 @@ const (
type connectAttempt struct { type connectAttempt struct {
ssid string ssid string
netPath dbus.ObjectPath netPath dbus.ObjectPath
saved bool
start time.Time start time.Time
deadline time.Time deadline time.Time
sawAuthish bool sawAuthish bool
@@ -53,6 +54,37 @@ type IWDBackend struct {
attemptMutex sync.RWMutex attemptMutex sync.RWMutex
recentScans map[string]time.Time recentScans map[string]time.Time
recentScansMu sync.Mutex recentScansMu sync.Mutex
pendingPSK *pendingReplacementPSK
pendingPSKMu sync.Mutex
}
type pendingReplacementPSK struct {
ssid string
psk string
expires time.Time
}
func (b *IWDBackend) storePendingPSK(ssid, psk string) {
b.pendingPSKMu.Lock()
b.pendingPSK = &pendingReplacementPSK{
ssid: ssid,
psk: psk,
expires: time.Now().Add(30 * time.Second),
}
b.pendingPSKMu.Unlock()
}
func (b *IWDBackend) takePendingPSK(ssid string) (string, bool) {
b.pendingPSKMu.Lock()
defer b.pendingPSKMu.Unlock()
pending := b.pendingPSK
if pending == nil || pending.ssid != ssid || time.Now().After(pending.expires) {
return "", false
}
b.pendingPSK = nil
return pending.psk, true
} }
func NewIWDBackend() (*IWDBackend, error) { func NewIWDBackend() (*IWDBackend, error) {
@@ -18,6 +18,7 @@ func (b *IWDBackend) StartMonitoring(onStateChange func()) error {
} }
agent.onUserCanceled = b.OnUserCanceledPrompt agent.onUserCanceled = b.OnUserCanceledPrompt
agent.onPromptRetry = b.OnPromptRetry agent.onPromptRetry = b.OnPromptRetry
agent.takePendingSecret = b.takePendingPSK
b.iwdAgent = agent b.iwdAgent = agent
} }
@@ -1,6 +1,7 @@
package network package network
import ( import (
"context"
"testing" "testing"
"time" "time"
@@ -314,6 +315,133 @@ func TestConnectAttempt_Finalization(t *testing.T) {
backend.stateMutex.RUnlock() backend.stateMutex.RUnlock()
} }
func TestIWDBackend_PendingPSK(t *testing.T) {
backend, _ := NewIWDBackend()
_, ok := backend.takePendingPSK("Home")
assert.False(t, ok)
backend.storePendingPSK("Home", "newpass")
_, ok = backend.takePendingPSK("Other")
assert.False(t, ok, "pending PSK should not match a different SSID")
psk, ok := backend.takePendingPSK("Home")
assert.True(t, ok)
assert.Equal(t, "newpass", psk)
_, ok = backend.takePendingPSK("Home")
assert.False(t, ok, "pending PSK should be consumed on take")
backend.storePendingPSK("Home", "newpass")
backend.pendingPSKMu.Lock()
backend.pendingPSK.expires = time.Now().Add(-time.Second)
backend.pendingPSKMu.Unlock()
_, ok = backend.takePendingPSK("Home")
assert.False(t, ok, "expired pending PSK should not be returned")
}
type fakePromptBroker struct {
asked chan PromptRequest
reply PromptReply
replyErr error
}
func (f *fakePromptBroker) Ask(ctx context.Context, req PromptRequest) (string, error) {
f.asked <- req
return "token", nil
}
func (f *fakePromptBroker) Wait(ctx context.Context, token string) (PromptReply, error) {
return f.reply, f.replyErr
}
func (f *fakePromptBroker) Resolve(token string, reply PromptReply) error { return nil }
func (f *fakePromptBroker) Cancel(path string, setting string) error { return nil }
func TestIWDBackend_BadCredentialsSavedNetwork_PromptsReplacement(t *testing.T) {
backend, _ := NewIWDBackend()
backend.state = &BackendState{}
broker := &fakePromptBroker{
asked: make(chan PromptRequest, 1),
reply: PromptReply{Cancel: true},
}
backend.promptBroker = broker
att := &connectAttempt{
ssid: "Home",
netPath: "/test",
saved: true,
start: time.Now(),
deadline: time.Now().Add(15 * time.Second),
}
backend.finalizeAttempt(att, "bad-credentials")
select {
case req := <-broker.asked:
assert.Equal(t, "Home", req.SSID)
assert.Equal(t, "wrong-password", req.Reason)
assert.Equal(t, []string{"psk"}, req.Fields)
case <-time.After(time.Second):
t.Fatal("expected replacement credentials prompt for saved network")
}
}
func TestIWDBackend_BadCredentialsUnsavedNetwork_NoReplacementPrompt(t *testing.T) {
backend, _ := NewIWDBackend()
backend.state = &BackendState{}
broker := &fakePromptBroker{
asked: make(chan PromptRequest, 1),
reply: PromptReply{Cancel: true},
}
backend.promptBroker = broker
att := &connectAttempt{
ssid: "Home",
netPath: "/test",
start: time.Now(),
deadline: time.Now().Add(15 * time.Second),
}
backend.finalizeAttempt(att, "bad-credentials")
select {
case <-broker.asked:
t.Fatal("unsaved network should not trigger a replacement prompt")
case <-time.After(100 * time.Millisecond):
}
}
func TestIWDBackend_BadCredentialsAfterPromptRetry_NoReplacementPrompt(t *testing.T) {
backend, _ := NewIWDBackend()
backend.state = &BackendState{}
broker := &fakePromptBroker{
asked: make(chan PromptRequest, 1),
reply: PromptReply{Cancel: true},
}
backend.promptBroker = broker
att := &connectAttempt{
ssid: "Home",
netPath: "/test",
saved: true,
sawPromptRetry: true,
start: time.Now(),
deadline: time.Now().Add(15 * time.Second),
}
backend.finalizeAttempt(att, "bad-credentials")
select {
case <-broker.asked:
t.Fatal("attempt that already prompted should not trigger a replacement prompt")
case <-time.After(100 * time.Millisecond):
}
}
func TestConnectAttempt_DoubleFinalization(t *testing.T) { func TestConnectAttempt_DoubleFinalization(t *testing.T) {
backend, _ := NewIWDBackend() backend, _ := NewIWDBackend()
backend.state = &BackendState{} backend.state = &BackendState{}
@@ -1,10 +1,12 @@
package network package network
import ( import (
"context"
"fmt" "fmt"
"time" "time"
"github.com/AvengeMedia/DankMaterialShell/core/internal/errdefs" "github.com/AvengeMedia/DankMaterialShell/core/internal/errdefs"
"github.com/AvengeMedia/DankMaterialShell/core/internal/log"
"github.com/godbus/dbus/v5" "github.com/godbus/dbus/v5"
) )
@@ -476,6 +478,73 @@ func (b *IWDBackend) finalizeAttempt(att *connectAttempt, code string) {
if b.onStateChange != nil { if b.onStateChange != nil {
b.onStateChange() b.onStateChange()
} }
if code == errdefs.ErrBadCredentials {
b.maybeReplaceSavedPSK(att)
}
}
func (b *IWDBackend) maybeReplaceSavedPSK(att *connectAttempt) {
if b.promptBroker == nil || !att.saved {
return
}
att.mu.Lock()
prompted := att.sawPromptRetry
att.mu.Unlock()
if prompted {
return
}
b.sigWG.Add(1)
go func() {
defer b.sigWG.Done()
b.requestReplacementPSK(att.ssid)
}()
}
func (b *IWDBackend) requestReplacementPSK(ssid string) {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
defer cancel()
go func() {
select {
case <-b.stopChan:
cancel()
case <-ctx.Done():
}
}()
token, err := b.promptBroker.Ask(ctx, PromptRequest{
SSID: ssid,
SettingName: "802-11-wireless-security",
Fields: []string{"psk"},
Reason: "wrong-password",
})
if err != nil {
log.Warnf("failed to request replacement credentials for %s: %v", ssid, err)
return
}
reply, err := b.promptBroker.Wait(ctx, token)
if err != nil || reply.Cancel {
return
}
psk, ok := reply.Secrets["psk"]
if !ok || psk == "" {
return
}
if err := b.ForgetWiFiNetwork(ssid); err != nil {
log.Warnf("failed to forget %s before credential replacement: %v", ssid, err)
}
b.storePendingPSK(ssid, psk)
if err := b.ConnectWiFi(ConnectionRequest{SSID: ssid}); err != nil {
log.Warnf("failed to reconnect %s with replacement credentials: %v", ssid, err)
}
} }
func (b *IWDBackend) startAttemptWatchdog(att *connectAttempt) { func (b *IWDBackend) startAttemptWatchdog(att *connectAttempt) {
@@ -560,7 +629,7 @@ func (b *IWDBackend) ConnectWiFi(req ConnectionRequest) error {
return fmt.Errorf("no WiFi device available") return fmt.Errorf("no WiFi device available")
} }
networkPath, err := b.findNetworkPath(req.SSID) networkPath, saved, err := b.findNetworkPath(req.SSID)
if err != nil { if err != nil {
b.setConnectError(errdefs.ErrNoSuchSSID) b.setConnectError(errdefs.ErrNoSuchSSID)
if b.onStateChange != nil { if b.onStateChange != nil {
@@ -572,6 +641,7 @@ func (b *IWDBackend) ConnectWiFi(req ConnectionRequest) error {
att := &connectAttempt{ att := &connectAttempt{
ssid: req.SSID, ssid: req.SSID,
netPath: networkPath, netPath: networkPath,
saved: saved,
start: time.Now(), start: time.Now(),
deadline: time.Now().Add(15 * time.Second), deadline: time.Now().Add(15 * time.Second),
} }
@@ -619,26 +689,39 @@ func (b *IWDBackend) ConnectWiFi(req ConnectionRequest) error {
return nil return nil
} }
func (b *IWDBackend) findNetworkPath(ssid string) (dbus.ObjectPath, error) { func (b *IWDBackend) findNetworkPath(ssid string) (dbus.ObjectPath, bool, error) {
obj := b.conn.Object(iwdBusName, iwdObjectPath) obj := b.conn.Object(iwdBusName, iwdObjectPath)
var objects map[dbus.ObjectPath]map[string]map[string]dbus.Variant var objects map[dbus.ObjectPath]map[string]map[string]dbus.Variant
err := obj.Call(dbusObjectManager+".GetManagedObjects", 0).Store(&objects) err := obj.Call(dbusObjectManager+".GetManagedObjects", 0).Store(&objects)
if err != nil { if err != nil {
return "", err return "", false, err
} }
var netPath dbus.ObjectPath
saved := false
for path, interfaces := range objects { for path, interfaces := range objects {
if netProps, ok := interfaces[iwdNetworkInterface]; ok { if netProps, ok := interfaces[iwdNetworkInterface]; ok {
if nameVar, ok := netProps["Name"]; ok { if nameVar, ok := netProps["Name"]; ok {
if name, ok := nameVar.Value().(string); ok && name == ssid { if name, ok := nameVar.Value().(string); ok && name == ssid {
return path, nil netPath = path
}
}
}
if knownProps, ok := interfaces[iwdKnownNetworkInterface]; ok {
if nameVar, ok := knownProps["Name"]; ok {
if name, ok := nameVar.Value().(string); ok && name == ssid {
saved = true
} }
} }
} }
} }
return "", fmt.Errorf("network not found") if netPath == "" {
return "", false, fmt.Errorf("network not found")
}
return netPath, saved, nil
} }
func (b *IWDBackend) DisconnectWiFi() error { func (b *IWDBackend) DisconnectWiFi() error {
@@ -577,6 +577,12 @@ Singleton {
credentialsRequested = false; credentialsRequested = false;
if (credentialsReason === "wrong-password" && credentialsSSID && credentialsSetting === "802-11-wireless-security") {
pendingConnectionSSID = credentialsSSID;
pendingConnectionStartTime = Date.now();
connectionStatus = "connecting";
}
DMSService.sendRequest("network.credentials.submit", params, response => { DMSService.sendRequest("network.credentials.submit", params, response => {
if (response.error) { if (response.error) {
log.warn("Failed to submit credentials:", response.error); log.warn("Failed to submit credentials:", response.error);