1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-08-01 19:18: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/hostdir/
distro/void/masterdir*/
# Often gets built
core/dms
+24 -11
View File
@@ -22,13 +22,14 @@ type ConnectionStateChecker interface {
}
type IWDAgent struct {
conn *dbus.Conn
objPath dbus.ObjectPath
prompts PromptBroker
onUserCanceled func()
onPromptRetry func(ssid string)
lastRequestSSID string
stateChecker ConnectionStateChecker
conn *dbus.Conn
objPath dbus.ObjectPath
prompts PromptBroker
onUserCanceled func()
onPromptRetry func(ssid string)
takePendingSecret func(ssid string) (string, bool)
lastRequestSSID string
stateChecker ConnectionStateChecker
}
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)
}
if a.takePendingSecret != nil {
if psk, ok := a.takePendingSecret(ssid); ok {
a.lastRequestSSID = ssid
return psk, nil
}
}
if a.prompts == nil {
if a.onUserCanceled != nil {
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)
}
if a.lastRequestSSID == ssid {
if a.onPromptRetry != nil {
a.onPromptRetry(ssid)
}
retry := a.lastRequestSSID == ssid
if retry && a.onPromptRetry != nil {
a.onPromptRetry(ssid)
}
a.lastRequestSSID = ssid
reason := ""
if retry {
reason = "wrong-password"
}
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
defer cancel()
token, err := a.prompts.Ask(ctx, PromptRequest{
SSID: ssid,
Fields: []string{"psk"},
Reason: reason,
})
if err != nil {
if a.onUserCanceled != nil {
@@ -24,6 +24,7 @@ const (
type connectAttempt struct {
ssid string
netPath dbus.ObjectPath
saved bool
start time.Time
deadline time.Time
sawAuthish bool
@@ -53,6 +54,37 @@ type IWDBackend struct {
attemptMutex sync.RWMutex
recentScans map[string]time.Time
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) {
@@ -18,6 +18,7 @@ func (b *IWDBackend) StartMonitoring(onStateChange func()) error {
}
agent.onUserCanceled = b.OnUserCanceledPrompt
agent.onPromptRetry = b.OnPromptRetry
agent.takePendingSecret = b.takePendingPSK
b.iwdAgent = agent
}
@@ -1,6 +1,7 @@
package network
import (
"context"
"testing"
"time"
@@ -314,6 +315,133 @@ func TestConnectAttempt_Finalization(t *testing.T) {
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) {
backend, _ := NewIWDBackend()
backend.state = &BackendState{}
@@ -1,10 +1,12 @@
package network
import (
"context"
"fmt"
"time"
"github.com/AvengeMedia/DankMaterialShell/core/internal/errdefs"
"github.com/AvengeMedia/DankMaterialShell/core/internal/log"
"github.com/godbus/dbus/v5"
)
@@ -476,6 +478,73 @@ func (b *IWDBackend) finalizeAttempt(att *connectAttempt, code string) {
if b.onStateChange != nil {
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) {
@@ -560,7 +629,7 @@ func (b *IWDBackend) ConnectWiFi(req ConnectionRequest) error {
return fmt.Errorf("no WiFi device available")
}
networkPath, err := b.findNetworkPath(req.SSID)
networkPath, saved, err := b.findNetworkPath(req.SSID)
if err != nil {
b.setConnectError(errdefs.ErrNoSuchSSID)
if b.onStateChange != nil {
@@ -572,6 +641,7 @@ func (b *IWDBackend) ConnectWiFi(req ConnectionRequest) error {
att := &connectAttempt{
ssid: req.SSID,
netPath: networkPath,
saved: saved,
start: time.Now(),
deadline: time.Now().Add(15 * time.Second),
}
@@ -619,26 +689,39 @@ func (b *IWDBackend) ConnectWiFi(req ConnectionRequest) error {
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)
var objects map[dbus.ObjectPath]map[string]map[string]dbus.Variant
err := obj.Call(dbusObjectManager+".GetManagedObjects", 0).Store(&objects)
if err != nil {
return "", err
return "", false, err
}
var netPath dbus.ObjectPath
saved := false
for path, interfaces := range objects {
if netProps, ok := interfaces[iwdNetworkInterface]; ok {
if nameVar, ok := netProps["Name"]; ok {
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 {
@@ -577,6 +577,12 @@ Singleton {
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 => {
if (response.error) {
log.warn("Failed to submit credentials:", response.error);