mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2025-12-05 21:15:38 -05:00
network: big feature enrichment
- Dedicated view in settings - VPN profile management - Ethernet disconnection - Turn prompts into floating windows
This commit is contained in:
@@ -328,6 +328,52 @@ func (_c *MockBackend_ConnectWiFi_Call) RunAndReturn(run func(network.Connection
|
||||
return _c
|
||||
}
|
||||
|
||||
// DeleteVPN provides a mock function with given fields: uuidOrName
|
||||
func (_m *MockBackend) DeleteVPN(uuidOrName string) error {
|
||||
ret := _m.Called(uuidOrName)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for DeleteVPN")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string) error); ok {
|
||||
r0 = rf(uuidOrName)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockBackend_DeleteVPN_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DeleteVPN'
|
||||
type MockBackend_DeleteVPN_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// DeleteVPN is a helper method to define mock.On call
|
||||
// - uuidOrName string
|
||||
func (_e *MockBackend_Expecter) DeleteVPN(uuidOrName interface{}) *MockBackend_DeleteVPN_Call {
|
||||
return &MockBackend_DeleteVPN_Call{Call: _e.mock.On("DeleteVPN", uuidOrName)}
|
||||
}
|
||||
|
||||
func (_c *MockBackend_DeleteVPN_Call) Run(run func(uuidOrName string)) *MockBackend_DeleteVPN_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockBackend_DeleteVPN_Call) Return(_a0 error) *MockBackend_DeleteVPN_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockBackend_DeleteVPN_Call) RunAndReturn(run func(string) error) *MockBackend_DeleteVPN_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// DisconnectAllVPN provides a mock function with no fields
|
||||
func (_m *MockBackend) DisconnectAllVPN() error {
|
||||
ret := _m.Called()
|
||||
@@ -418,6 +464,52 @@ func (_c *MockBackend_DisconnectEthernet_Call) RunAndReturn(run func() error) *M
|
||||
return _c
|
||||
}
|
||||
|
||||
// DisconnectEthernetDevice provides a mock function with given fields: device
|
||||
func (_m *MockBackend) DisconnectEthernetDevice(device string) error {
|
||||
ret := _m.Called(device)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for DisconnectEthernetDevice")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string) error); ok {
|
||||
r0 = rf(device)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockBackend_DisconnectEthernetDevice_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DisconnectEthernetDevice'
|
||||
type MockBackend_DisconnectEthernetDevice_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// DisconnectEthernetDevice is a helper method to define mock.On call
|
||||
// - device string
|
||||
func (_e *MockBackend_Expecter) DisconnectEthernetDevice(device interface{}) *MockBackend_DisconnectEthernetDevice_Call {
|
||||
return &MockBackend_DisconnectEthernetDevice_Call{Call: _e.mock.On("DisconnectEthernetDevice", device)}
|
||||
}
|
||||
|
||||
func (_c *MockBackend_DisconnectEthernetDevice_Call) Run(run func(device string)) *MockBackend_DisconnectEthernetDevice_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockBackend_DisconnectEthernetDevice_Call) Return(_a0 error) *MockBackend_DisconnectEthernetDevice_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockBackend_DisconnectEthernetDevice_Call) RunAndReturn(run func(string) error) *MockBackend_DisconnectEthernetDevice_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// DisconnectVPN provides a mock function with given fields: uuidOrName
|
||||
func (_m *MockBackend) DisconnectVPN(uuidOrName string) error {
|
||||
ret := _m.Called(uuidOrName)
|
||||
@@ -658,6 +750,53 @@ func (_c *MockBackend_GetCurrentState_Call) RunAndReturn(run func() (*network.Ba
|
||||
return _c
|
||||
}
|
||||
|
||||
// GetEthernetDevices provides a mock function with no fields
|
||||
func (_m *MockBackend) GetEthernetDevices() []network.EthernetDevice {
|
||||
ret := _m.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetEthernetDevices")
|
||||
}
|
||||
|
||||
var r0 []network.EthernetDevice
|
||||
if rf, ok := ret.Get(0).(func() []network.EthernetDevice); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]network.EthernetDevice)
|
||||
}
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockBackend_GetEthernetDevices_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetEthernetDevices'
|
||||
type MockBackend_GetEthernetDevices_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// GetEthernetDevices is a helper method to define mock.On call
|
||||
func (_e *MockBackend_Expecter) GetEthernetDevices() *MockBackend_GetEthernetDevices_Call {
|
||||
return &MockBackend_GetEthernetDevices_Call{Call: _e.mock.On("GetEthernetDevices")}
|
||||
}
|
||||
|
||||
func (_c *MockBackend_GetEthernetDevices_Call) Run(run func()) *MockBackend_GetEthernetDevices_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run()
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockBackend_GetEthernetDevices_Call) Return(_a0 []network.EthernetDevice) *MockBackend_GetEthernetDevices_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockBackend_GetEthernetDevices_Call) RunAndReturn(run func() []network.EthernetDevice) *MockBackend_GetEthernetDevices_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// GetPromptBroker provides a mock function with no fields
|
||||
func (_m *MockBackend) GetPromptBroker() network.PromptBroker {
|
||||
ret := _m.Called()
|
||||
@@ -705,6 +844,64 @@ func (_c *MockBackend_GetPromptBroker_Call) RunAndReturn(run func() network.Prom
|
||||
return _c
|
||||
}
|
||||
|
||||
// GetVPNConfig provides a mock function with given fields: uuidOrName
|
||||
func (_m *MockBackend) GetVPNConfig(uuidOrName string) (*network.VPNConfig, error) {
|
||||
ret := _m.Called(uuidOrName)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetVPNConfig")
|
||||
}
|
||||
|
||||
var r0 *network.VPNConfig
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(string) (*network.VPNConfig, error)); ok {
|
||||
return rf(uuidOrName)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(string) *network.VPNConfig); ok {
|
||||
r0 = rf(uuidOrName)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*network.VPNConfig)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(string) error); ok {
|
||||
r1 = rf(uuidOrName)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// MockBackend_GetVPNConfig_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetVPNConfig'
|
||||
type MockBackend_GetVPNConfig_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// GetVPNConfig is a helper method to define mock.On call
|
||||
// - uuidOrName string
|
||||
func (_e *MockBackend_Expecter) GetVPNConfig(uuidOrName interface{}) *MockBackend_GetVPNConfig_Call {
|
||||
return &MockBackend_GetVPNConfig_Call{Call: _e.mock.On("GetVPNConfig", uuidOrName)}
|
||||
}
|
||||
|
||||
func (_c *MockBackend_GetVPNConfig_Call) Run(run func(uuidOrName string)) *MockBackend_GetVPNConfig_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockBackend_GetVPNConfig_Call) Return(_a0 *network.VPNConfig, _a1 error) *MockBackend_GetVPNConfig_Call {
|
||||
_c.Call.Return(_a0, _a1)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockBackend_GetVPNConfig_Call) RunAndReturn(run func(string) (*network.VPNConfig, error)) *MockBackend_GetVPNConfig_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// GetWiFiDevices provides a mock function with no fields
|
||||
func (_m *MockBackend) GetWiFiDevices() []network.WiFiDevice {
|
||||
ret := _m.Called()
|
||||
@@ -980,6 +1177,65 @@ func (_c *MockBackend_GetWiredNetworkDetails_Call) RunAndReturn(run func(string)
|
||||
return _c
|
||||
}
|
||||
|
||||
// ImportVPN provides a mock function with given fields: filePath, name
|
||||
func (_m *MockBackend) ImportVPN(filePath string, name string) (*network.VPNImportResult, error) {
|
||||
ret := _m.Called(filePath, name)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for ImportVPN")
|
||||
}
|
||||
|
||||
var r0 *network.VPNImportResult
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(string, string) (*network.VPNImportResult, error)); ok {
|
||||
return rf(filePath, name)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(string, string) *network.VPNImportResult); ok {
|
||||
r0 = rf(filePath, name)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*network.VPNImportResult)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(string, string) error); ok {
|
||||
r1 = rf(filePath, name)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// MockBackend_ImportVPN_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ImportVPN'
|
||||
type MockBackend_ImportVPN_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// ImportVPN is a helper method to define mock.On call
|
||||
// - filePath string
|
||||
// - name string
|
||||
func (_e *MockBackend_Expecter) ImportVPN(filePath interface{}, name interface{}) *MockBackend_ImportVPN_Call {
|
||||
return &MockBackend_ImportVPN_Call{Call: _e.mock.On("ImportVPN", filePath, name)}
|
||||
}
|
||||
|
||||
func (_c *MockBackend_ImportVPN_Call) Run(run func(filePath string, name string)) *MockBackend_ImportVPN_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(string), args[1].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockBackend_ImportVPN_Call) Return(_a0 *network.VPNImportResult, _a1 error) *MockBackend_ImportVPN_Call {
|
||||
_c.Call.Return(_a0, _a1)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockBackend_ImportVPN_Call) RunAndReturn(run func(string, string) (*network.VPNImportResult, error)) *MockBackend_ImportVPN_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// Initialize provides a mock function with no fields
|
||||
func (_m *MockBackend) Initialize() error {
|
||||
ret := _m.Called()
|
||||
@@ -1082,6 +1338,63 @@ func (_c *MockBackend_ListActiveVPN_Call) RunAndReturn(run func() ([]network.VPN
|
||||
return _c
|
||||
}
|
||||
|
||||
// ListVPNPlugins provides a mock function with no fields
|
||||
func (_m *MockBackend) ListVPNPlugins() ([]network.VPNPlugin, error) {
|
||||
ret := _m.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for ListVPNPlugins")
|
||||
}
|
||||
|
||||
var r0 []network.VPNPlugin
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func() ([]network.VPNPlugin, error)); ok {
|
||||
return rf()
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func() []network.VPNPlugin); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]network.VPNPlugin)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func() error); ok {
|
||||
r1 = rf()
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// MockBackend_ListVPNPlugins_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListVPNPlugins'
|
||||
type MockBackend_ListVPNPlugins_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// ListVPNPlugins is a helper method to define mock.On call
|
||||
func (_e *MockBackend_Expecter) ListVPNPlugins() *MockBackend_ListVPNPlugins_Call {
|
||||
return &MockBackend_ListVPNPlugins_Call{Call: _e.mock.On("ListVPNPlugins")}
|
||||
}
|
||||
|
||||
func (_c *MockBackend_ListVPNPlugins_Call) Run(run func()) *MockBackend_ListVPNPlugins_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run()
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockBackend_ListVPNPlugins_Call) Return(_a0 []network.VPNPlugin, _a1 error) *MockBackend_ListVPNPlugins_Call {
|
||||
_c.Call.Return(_a0, _a1)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockBackend_ListVPNPlugins_Call) RunAndReturn(run func() ([]network.VPNPlugin, error)) *MockBackend_ListVPNPlugins_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// ListVPNProfiles provides a mock function with no fields
|
||||
func (_m *MockBackend) ListVPNProfiles() ([]network.VPNProfile, error) {
|
||||
ret := _m.Called()
|
||||
@@ -1276,6 +1589,55 @@ func (_c *MockBackend_SetPromptBroker_Call) RunAndReturn(run func(network.Prompt
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetVPNCredentials provides a mock function with given fields: uuid, username, password, save
|
||||
func (_m *MockBackend) SetVPNCredentials(uuid string, username string, password string, save bool) error {
|
||||
ret := _m.Called(uuid, username, password, save)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for SetVPNCredentials")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string, string, string, bool) error); ok {
|
||||
r0 = rf(uuid, username, password, save)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockBackend_SetVPNCredentials_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SetVPNCredentials'
|
||||
type MockBackend_SetVPNCredentials_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// SetVPNCredentials is a helper method to define mock.On call
|
||||
// - uuid string
|
||||
// - username string
|
||||
// - password string
|
||||
// - save bool
|
||||
func (_e *MockBackend_Expecter) SetVPNCredentials(uuid interface{}, username interface{}, password interface{}, save interface{}) *MockBackend_SetVPNCredentials_Call {
|
||||
return &MockBackend_SetVPNCredentials_Call{Call: _e.mock.On("SetVPNCredentials", uuid, username, password, save)}
|
||||
}
|
||||
|
||||
func (_c *MockBackend_SetVPNCredentials_Call) Run(run func(uuid string, username string, password string, save bool)) *MockBackend_SetVPNCredentials_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(string), args[1].(string), args[2].(string), args[3].(bool))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockBackend_SetVPNCredentials_Call) Return(_a0 error) *MockBackend_SetVPNCredentials_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockBackend_SetVPNCredentials_Call) RunAndReturn(run func(string, string, string, bool) error) *MockBackend_SetVPNCredentials_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetWiFiAutoconnect provides a mock function with given fields: ssid, autoconnect
|
||||
func (_m *MockBackend) SetWiFiAutoconnect(ssid string, autoconnect bool) error {
|
||||
ret := _m.Called(ssid, autoconnect)
|
||||
@@ -1495,6 +1857,53 @@ func (_c *MockBackend_SubmitCredentials_Call) RunAndReturn(run func(string, map[
|
||||
return _c
|
||||
}
|
||||
|
||||
// UpdateVPNConfig provides a mock function with given fields: uuid, updates
|
||||
func (_m *MockBackend) UpdateVPNConfig(uuid string, updates map[string]interface{}) error {
|
||||
ret := _m.Called(uuid, updates)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for UpdateVPNConfig")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string, map[string]interface{}) error); ok {
|
||||
r0 = rf(uuid, updates)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockBackend_UpdateVPNConfig_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpdateVPNConfig'
|
||||
type MockBackend_UpdateVPNConfig_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// UpdateVPNConfig is a helper method to define mock.On call
|
||||
// - uuid string
|
||||
// - updates map[string]interface{}
|
||||
func (_e *MockBackend_Expecter) UpdateVPNConfig(uuid interface{}, updates interface{}) *MockBackend_UpdateVPNConfig_Call {
|
||||
return &MockBackend_UpdateVPNConfig_Call{Call: _e.mock.On("UpdateVPNConfig", uuid, updates)}
|
||||
}
|
||||
|
||||
func (_c *MockBackend_UpdateVPNConfig_Call) Run(run func(uuid string, updates map[string]interface{})) *MockBackend_UpdateVPNConfig_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(string), args[1].(map[string]interface{}))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockBackend_UpdateVPNConfig_Call) Return(_a0 error) *MockBackend_UpdateVPNConfig_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockBackend_UpdateVPNConfig_Call) RunAndReturn(run func(string, map[string]interface{}) error) *MockBackend_UpdateVPNConfig_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// NewMockBackend creates a new instance of MockBackend. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||
// The first argument is typically a *testing.T value.
|
||||
func NewMockBackend(t interface {
|
||||
|
||||
@@ -287,7 +287,7 @@ func TestNewManager(t *testing.T) {
|
||||
} else {
|
||||
assert.NotNil(t, manager)
|
||||
assert.NotNil(t, manager.state)
|
||||
assert.NotNil(t, manager.subscribers)
|
||||
assert.NotNil(t, &manager.subscribers)
|
||||
assert.NotNil(t, manager.stopChan)
|
||||
|
||||
manager.Close()
|
||||
|
||||
@@ -17,25 +17,12 @@ func TestEventType_Constants(t *testing.T) {
|
||||
|
||||
func TestSessionState_Struct(t *testing.T) {
|
||||
state := SessionState{
|
||||
SessionID: "1",
|
||||
SessionPath: "/org/freedesktop/login1/session/_31",
|
||||
Locked: false,
|
||||
Active: true,
|
||||
IdleHint: false,
|
||||
IdleSinceHint: 0,
|
||||
LockedHint: false,
|
||||
SessionType: "wayland",
|
||||
SessionClass: "user",
|
||||
User: 1000,
|
||||
UserName: "testuser",
|
||||
RemoteHost: "",
|
||||
Service: "gdm-password",
|
||||
TTY: "tty2",
|
||||
Display: ":1",
|
||||
Remote: false,
|
||||
Seat: "seat0",
|
||||
VTNr: 2,
|
||||
PreparingForSleep: false,
|
||||
SessionID: "1",
|
||||
Locked: false,
|
||||
Active: true,
|
||||
SessionType: "wayland",
|
||||
User: 1000,
|
||||
UserName: "testuser",
|
||||
}
|
||||
|
||||
assert.Equal(t, "1", state.SessionID)
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/errdefs"
|
||||
@@ -176,8 +177,8 @@ func (a *SecretAgent) GetSecrets(
|
||||
return nil, dbus.NewError("org.freedesktop.NetworkManager.SecretAgent.Error.NoSecrets", nil)
|
||||
}
|
||||
|
||||
log.Infof("[SecretAgent] VPN with empty hints but we're connecting - prompting for password")
|
||||
fields = []string{"password"}
|
||||
fields = inferVPNFields(conn, vpnSvc)
|
||||
log.Infof("[SecretAgent] VPN with empty hints but we're connecting - inferred fields: %v", fields)
|
||||
} else {
|
||||
log.Infof("[SecretAgent] VPN with empty hints - deferring to other agents for %s", vpnSvc)
|
||||
return nil, dbus.NewError("org.freedesktop.NetworkManager.SecretAgent.Error.NoSecrets", nil)
|
||||
@@ -251,6 +252,35 @@ func (a *SecretAgent) GetSecrets(
|
||||
}
|
||||
}
|
||||
|
||||
if settingName == "vpn" && a.backend != nil {
|
||||
a.backend.cachedVPNCredsMu.Lock()
|
||||
cached := a.backend.cachedVPNCreds
|
||||
if cached != nil && cached.ConnectionUUID == connUuid {
|
||||
a.backend.cachedVPNCreds = nil
|
||||
a.backend.cachedVPNCredsMu.Unlock()
|
||||
|
||||
log.Infof("[SecretAgent] Using cached password from pre-activation prompt")
|
||||
|
||||
out := nmSettingMap{}
|
||||
sec := nmVariantMap{}
|
||||
sec["password"] = dbus.MakeVariant(cached.Password)
|
||||
out[settingName] = sec
|
||||
|
||||
if cached.SavePassword {
|
||||
a.backend.pendingVPNSaveMu.Lock()
|
||||
a.backend.pendingVPNSave = &pendingVPNCredentials{
|
||||
ConnectionPath: string(path),
|
||||
Password: cached.Password,
|
||||
SavePassword: true,
|
||||
}
|
||||
a.backend.pendingVPNSaveMu.Unlock()
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
a.backend.cachedVPNCredsMu.Unlock()
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
|
||||
defer cancel()
|
||||
|
||||
@@ -261,6 +291,7 @@ func (a *SecretAgent) GetSecrets(
|
||||
VpnService: vpnSvc,
|
||||
SettingName: settingName,
|
||||
Fields: fields,
|
||||
FieldsInfo: buildFieldsInfo(settingName, fields, vpnSvc),
|
||||
Hints: hints,
|
||||
Reason: reason,
|
||||
ConnectionId: connId,
|
||||
@@ -283,6 +314,7 @@ func (a *SecretAgent) GetSecrets(
|
||||
wasConnecting := a.backend.state.IsConnecting
|
||||
wasConnectingVPN := a.backend.state.IsConnectingVPN
|
||||
cancelledSSID := a.backend.state.ConnectingSSID
|
||||
cancelledVPNUUID := a.backend.state.ConnectingVPNUUID
|
||||
if wasConnecting || wasConnectingVPN {
|
||||
log.Infof("[SecretAgent] Clearing connecting state due to cancelled prompt")
|
||||
a.backend.state.IsConnecting = false
|
||||
@@ -301,6 +333,14 @@ func (a *SecretAgent) GetSecrets(
|
||||
}
|
||||
}
|
||||
|
||||
// If this was a VPN connection that was cancelled, deactivate it
|
||||
if wasConnectingVPN && cancelledVPNUUID != "" {
|
||||
log.Infof("[SecretAgent] Deactivating cancelled VPN connection: %s", cancelledVPNUUID)
|
||||
if err := a.backend.DisconnectVPN(cancelledVPNUUID); err != nil {
|
||||
log.Warnf("[SecretAgent] Failed to deactivate cancelled VPN: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if (wasConnecting || wasConnectingVPN) && a.backend.onStateChange != nil {
|
||||
a.backend.onStateChange()
|
||||
}
|
||||
@@ -320,7 +360,12 @@ func (a *SecretAgent) GetSecrets(
|
||||
|
||||
out := nmSettingMap{}
|
||||
sec := nmVariantMap{}
|
||||
|
||||
var vpnUsername string
|
||||
for k, v := range reply.Secrets {
|
||||
if settingName == "vpn" && k == "username" {
|
||||
vpnUsername = v
|
||||
}
|
||||
sec[k] = dbus.MakeVariant(v)
|
||||
}
|
||||
out[settingName] = sec
|
||||
@@ -332,13 +377,22 @@ func (a *SecretAgent) GetSecrets(
|
||||
log.Infof("[SecretAgent] Returning VPN secrets with %d fields for %s", len(sec), vpnSvc)
|
||||
}
|
||||
|
||||
// If save=true, persist secrets in background after returning to NetworkManager
|
||||
// This MUST happen after we return secrets, in a goroutine
|
||||
if reply.Save {
|
||||
if settingName == "vpn" && a.backend != nil && (vpnUsername != "" || reply.Save) {
|
||||
pw, _ := reply.Secrets["password"]
|
||||
a.backend.pendingVPNSaveMu.Lock()
|
||||
a.backend.pendingVPNSave = &pendingVPNCredentials{
|
||||
ConnectionPath: string(path),
|
||||
Username: vpnUsername,
|
||||
Password: pw,
|
||||
SavePassword: reply.Save,
|
||||
}
|
||||
a.backend.pendingVPNSaveMu.Unlock()
|
||||
log.Infof("[SecretAgent] Queued credentials persist for after connection succeeds")
|
||||
} else if reply.Save && settingName != "vpn" {
|
||||
// Non-VPN save logic
|
||||
go func() {
|
||||
log.Infof("[SecretAgent] Persisting secrets with Update2: path=%s, setting=%s", path, settingName)
|
||||
|
||||
// Get existing connection settings
|
||||
connObj := a.conn.Object("org.freedesktop.NetworkManager", path)
|
||||
var existingSettings map[string]map[string]dbus.Variant
|
||||
if err := connObj.Call("org.freedesktop.NetworkManager.Settings.Connection.GetSettings", 0).Store(&existingSettings); err != nil {
|
||||
@@ -346,49 +400,12 @@ func (a *SecretAgent) GetSecrets(
|
||||
return
|
||||
}
|
||||
|
||||
// Build minimal settings with ONLY the section we're updating
|
||||
// This avoids D-Bus type serialization issues with complex types like IPv6 addresses
|
||||
settings := make(map[string]map[string]dbus.Variant)
|
||||
|
||||
// Copy connection section (required for Update2)
|
||||
if connSection, ok := existingSettings["connection"]; ok {
|
||||
settings["connection"] = connSection
|
||||
}
|
||||
|
||||
// Update settings based on type
|
||||
switch settingName {
|
||||
case "vpn":
|
||||
vpn, ok := existingSettings["vpn"]
|
||||
if !ok {
|
||||
vpn = make(map[string]dbus.Variant)
|
||||
}
|
||||
|
||||
var data map[string]string
|
||||
if dataVariant, ok := vpn["data"]; ok {
|
||||
if dm, ok := dataVariant.Value().(map[string]string); ok {
|
||||
data = make(map[string]string)
|
||||
for k, v := range dm {
|
||||
data[k] = v
|
||||
}
|
||||
} else {
|
||||
data = make(map[string]string)
|
||||
}
|
||||
} else {
|
||||
data = make(map[string]string)
|
||||
}
|
||||
|
||||
data["password-flags"] = "0"
|
||||
vpn["data"] = dbus.MakeVariant(data)
|
||||
|
||||
secs := make(map[string]string)
|
||||
for k, v := range reply.Secrets {
|
||||
secs[k] = v
|
||||
}
|
||||
vpn["secrets"] = dbus.MakeVariant(secs)
|
||||
settings["vpn"] = vpn
|
||||
|
||||
log.Infof("[SecretAgent] Updated VPN settings: password-flags=0, secrets with %d fields", len(secs))
|
||||
|
||||
case "802-11-wireless-security":
|
||||
wifiSec, ok := existingSettings["802-11-wireless-security"]
|
||||
if !ok {
|
||||
@@ -514,6 +531,102 @@ func fieldsNeeded(setting string, hints []string) []string {
|
||||
}
|
||||
}
|
||||
|
||||
func buildFieldsInfo(setting string, fields []string, vpnService string) []FieldInfo {
|
||||
result := make([]FieldInfo, 0, len(fields))
|
||||
for _, f := range fields {
|
||||
info := FieldInfo{Name: f}
|
||||
switch setting {
|
||||
case "802-11-wireless-security":
|
||||
info.Label = "Password"
|
||||
info.IsSecret = true
|
||||
case "802-1x":
|
||||
switch f {
|
||||
case "identity":
|
||||
info.Label = "Username"
|
||||
info.IsSecret = false
|
||||
case "password":
|
||||
info.Label = "Password"
|
||||
info.IsSecret = true
|
||||
default:
|
||||
info.Label = f
|
||||
info.IsSecret = true
|
||||
}
|
||||
case "vpn":
|
||||
info.Label, info.IsSecret = vpnFieldMeta(f, vpnService)
|
||||
default:
|
||||
info.Label = f
|
||||
info.IsSecret = true
|
||||
}
|
||||
result = append(result, info)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func inferVPNFields(conn map[string]nmVariantMap, vpnService string) []string {
|
||||
fields := []string{"password"}
|
||||
|
||||
vpnSettings, ok := conn["vpn"]
|
||||
if !ok {
|
||||
return fields
|
||||
}
|
||||
|
||||
dataVariant, ok := vpnSettings["data"]
|
||||
if !ok {
|
||||
return fields
|
||||
}
|
||||
|
||||
dataMap, ok := dataVariant.Value().(map[string]string)
|
||||
if !ok {
|
||||
return fields
|
||||
}
|
||||
|
||||
connType := dataMap["connection-type"]
|
||||
|
||||
switch {
|
||||
case strings.Contains(vpnService, "openvpn"):
|
||||
if connType == "password" || connType == "password-tls" {
|
||||
if dataMap["username"] == "" {
|
||||
fields = []string{"username", "password"}
|
||||
}
|
||||
}
|
||||
case strings.Contains(vpnService, "vpnc"), strings.Contains(vpnService, "l2tp"),
|
||||
strings.Contains(vpnService, "pptp"), strings.Contains(vpnService, "openconnect"):
|
||||
if dataMap["username"] == "" {
|
||||
fields = []string{"username", "password"}
|
||||
}
|
||||
}
|
||||
|
||||
return fields
|
||||
}
|
||||
|
||||
func vpnFieldMeta(field, vpnService string) (label string, isSecret bool) {
|
||||
switch field {
|
||||
case "password":
|
||||
return "Password", true
|
||||
case "Xauth password":
|
||||
return "IPSec Password", true
|
||||
case "IPSec secret":
|
||||
return "IPSec Pre-Shared Key", true
|
||||
case "cert-pass":
|
||||
return "Certificate Password", true
|
||||
case "http-proxy-password":
|
||||
return "HTTP Proxy Password", true
|
||||
case "username":
|
||||
return "Username", false
|
||||
case "Xauth username":
|
||||
return "IPSec Username", false
|
||||
case "proxy-password":
|
||||
return "Proxy Password", true
|
||||
case "private-key-password":
|
||||
return "Private Key Password", true
|
||||
}
|
||||
if strings.HasSuffix(field, "password") || strings.HasSuffix(field, "secret") ||
|
||||
strings.HasSuffix(field, "pass") || strings.HasSuffix(field, "psk") {
|
||||
return strings.Title(strings.ReplaceAll(field, "-", " ")), true
|
||||
}
|
||||
return strings.Title(strings.ReplaceAll(field, "-", " ")), false
|
||||
}
|
||||
|
||||
func readVPNPasswordFlags(conn map[string]nmVariantMap, settingName string) uint32 {
|
||||
if settingName != "vpn" {
|
||||
return 0xFFFF
|
||||
|
||||
@@ -18,10 +18,12 @@ type Backend interface {
|
||||
ForgetWiFiNetwork(ssid string) error
|
||||
SetWiFiAutoconnect(ssid string, autoconnect bool) error
|
||||
|
||||
GetEthernetDevices() []EthernetDevice
|
||||
GetWiredConnections() ([]WiredConnection, error)
|
||||
GetWiredNetworkDetails(uuid string) (*WiredNetworkInfoResponse, error)
|
||||
ConnectEthernet() error
|
||||
DisconnectEthernet() error
|
||||
DisconnectEthernetDevice(device string) error
|
||||
ActivateWiredConnection(uuid string) error
|
||||
|
||||
ListVPNProfiles() ([]VPNProfile, error)
|
||||
@@ -30,6 +32,12 @@ type Backend interface {
|
||||
DisconnectVPN(uuidOrName string) error
|
||||
DisconnectAllVPN() error
|
||||
ClearVPNCredentials(uuidOrName string) error
|
||||
ListVPNPlugins() ([]VPNPlugin, error)
|
||||
ImportVPN(filePath string, name string) (*VPNImportResult, error)
|
||||
GetVPNConfig(uuidOrName string) (*VPNConfig, error)
|
||||
UpdateVPNConfig(uuid string, updates map[string]interface{}) error
|
||||
SetVPNCredentials(uuid string, username string, password string, save bool) error
|
||||
DeleteVPN(uuidOrName string) error
|
||||
|
||||
GetCurrentState() (*BackendState, error)
|
||||
|
||||
@@ -49,6 +57,7 @@ type BackendState struct {
|
||||
EthernetDevice string
|
||||
EthernetConnected bool
|
||||
EthernetConnectionUuid string
|
||||
EthernetDevices []EthernetDevice
|
||||
WiFiIP string
|
||||
WiFiDevice string
|
||||
WiFiConnected bool
|
||||
|
||||
@@ -84,6 +84,7 @@ func (b *HybridIwdNetworkdBackend) GetCurrentState() (*BackendState, error) {
|
||||
merged.EthernetDevice = ls.EthernetDevice
|
||||
merged.EthernetConnectionUuid = ls.EthernetConnectionUuid
|
||||
merged.WiredConnections = ls.WiredConnections
|
||||
merged.EthernetDevices = ls.EthernetDevices
|
||||
|
||||
if ls.EthernetConnected && ls.EthernetIP != "" {
|
||||
merged.NetworkStatus = StatusEthernet
|
||||
@@ -149,6 +150,14 @@ func (b *HybridIwdNetworkdBackend) DisconnectEthernet() error {
|
||||
return b.l3.DisconnectEthernet()
|
||||
}
|
||||
|
||||
func (b *HybridIwdNetworkdBackend) DisconnectEthernetDevice(device string) error {
|
||||
return b.l3.DisconnectEthernetDevice(device)
|
||||
}
|
||||
|
||||
func (b *HybridIwdNetworkdBackend) GetEthernetDevices() []EthernetDevice {
|
||||
return b.l3.GetEthernetDevices()
|
||||
}
|
||||
|
||||
func (b *HybridIwdNetworkdBackend) ActivateWiredConnection(uuid string) error {
|
||||
return b.l3.ActivateWiredConnection(uuid)
|
||||
}
|
||||
@@ -177,6 +186,26 @@ func (b *HybridIwdNetworkdBackend) ClearVPNCredentials(uuidOrName string) error
|
||||
return fmt.Errorf("VPN not supported in hybrid mode")
|
||||
}
|
||||
|
||||
func (b *HybridIwdNetworkdBackend) ListVPNPlugins() ([]VPNPlugin, error) {
|
||||
return []VPNPlugin{}, nil
|
||||
}
|
||||
|
||||
func (b *HybridIwdNetworkdBackend) ImportVPN(filePath string, name string) (*VPNImportResult, error) {
|
||||
return nil, fmt.Errorf("VPN not supported in hybrid mode")
|
||||
}
|
||||
|
||||
func (b *HybridIwdNetworkdBackend) GetVPNConfig(uuidOrName string) (*VPNConfig, error) {
|
||||
return nil, fmt.Errorf("VPN not supported in hybrid mode")
|
||||
}
|
||||
|
||||
func (b *HybridIwdNetworkdBackend) UpdateVPNConfig(uuid string, updates map[string]interface{}) error {
|
||||
return fmt.Errorf("VPN not supported in hybrid mode")
|
||||
}
|
||||
|
||||
func (b *HybridIwdNetworkdBackend) DeleteVPN(uuidOrName string) error {
|
||||
return fmt.Errorf("VPN not supported in hybrid mode")
|
||||
}
|
||||
|
||||
func (b *HybridIwdNetworkdBackend) GetPromptBroker() PromptBroker {
|
||||
return b.wifi.GetPromptBroker()
|
||||
}
|
||||
@@ -208,3 +237,7 @@ func (b *HybridIwdNetworkdBackend) DisconnectWiFiDevice(device string) error {
|
||||
func (b *HybridIwdNetworkdBackend) GetWiFiDevices() []WiFiDevice {
|
||||
return b.wifi.GetWiFiDevices()
|
||||
}
|
||||
|
||||
func (b *HybridIwdNetworkdBackend) SetVPNCredentials(uuid, username, password string, save bool) error {
|
||||
return fmt.Errorf("VPN not supported in hybrid mode")
|
||||
}
|
||||
|
||||
@@ -18,6 +18,14 @@ func (b *IWDBackend) DisconnectEthernet() error {
|
||||
return fmt.Errorf("wired connections not supported by iwd")
|
||||
}
|
||||
|
||||
func (b *IWDBackend) DisconnectEthernetDevice(device string) error {
|
||||
return fmt.Errorf("wired connections not supported by iwd")
|
||||
}
|
||||
|
||||
func (b *IWDBackend) GetEthernetDevices() []EthernetDevice {
|
||||
return []EthernetDevice{}
|
||||
}
|
||||
|
||||
func (b *IWDBackend) ActivateWiredConnection(uuid string) error {
|
||||
return fmt.Errorf("wired connections not supported by iwd")
|
||||
}
|
||||
@@ -46,6 +54,30 @@ func (b *IWDBackend) ClearVPNCredentials(uuidOrName string) error {
|
||||
return fmt.Errorf("VPN not supported by iwd backend")
|
||||
}
|
||||
|
||||
func (b *IWDBackend) ListVPNPlugins() ([]VPNPlugin, error) {
|
||||
return nil, fmt.Errorf("VPN not supported by iwd backend")
|
||||
}
|
||||
|
||||
func (b *IWDBackend) ImportVPN(filePath string, name string) (*VPNImportResult, error) {
|
||||
return nil, fmt.Errorf("VPN not supported by iwd backend")
|
||||
}
|
||||
|
||||
func (b *IWDBackend) GetVPNConfig(uuidOrName string) (*VPNConfig, error) {
|
||||
return nil, fmt.Errorf("VPN not supported by iwd backend")
|
||||
}
|
||||
|
||||
func (b *IWDBackend) UpdateVPNConfig(uuid string, updates map[string]interface{}) error {
|
||||
return fmt.Errorf("VPN not supported by iwd backend")
|
||||
}
|
||||
|
||||
func (b *IWDBackend) DeleteVPN(uuidOrName string) error {
|
||||
return fmt.Errorf("VPN not supported by iwd backend")
|
||||
}
|
||||
|
||||
func (b *IWDBackend) SetVPNCredentials(uuid, username, password string, save bool) error {
|
||||
return fmt.Errorf("VPN not supported by iwd backend")
|
||||
}
|
||||
|
||||
func (b *IWDBackend) ScanWiFiDevice(device string) error {
|
||||
return b.ScanWiFi()
|
||||
}
|
||||
|
||||
@@ -138,6 +138,7 @@ func (b *SystemdNetworkdBackend) updateState() error {
|
||||
}
|
||||
|
||||
var wiredConns []WiredConnection
|
||||
var ethernetDevices []EthernetDevice
|
||||
for name, link := range b.links {
|
||||
if b.isVirtualInterface(name) || strings.HasPrefix(name, "wlan") || strings.HasPrefix(name, "wlp") {
|
||||
continue
|
||||
@@ -151,6 +152,37 @@ func (b *SystemdNetworkdBackend) updateState() error {
|
||||
Type: "ethernet",
|
||||
IsActive: active,
|
||||
})
|
||||
|
||||
var ip string
|
||||
var hwAddr string
|
||||
if iface, err := net.InterfaceByName(name); err == nil {
|
||||
hwAddr = iface.HardwareAddr.String()
|
||||
if addrs := b.getAddresses(name); len(addrs) > 0 {
|
||||
ip = addrs[0]
|
||||
}
|
||||
}
|
||||
|
||||
stateStr := "disconnected"
|
||||
switch link.opState {
|
||||
case "routable":
|
||||
stateStr = "routable"
|
||||
case "carrier":
|
||||
stateStr = "carrier"
|
||||
case "degraded":
|
||||
stateStr = "degraded"
|
||||
case "no-carrier":
|
||||
stateStr = "no-carrier"
|
||||
case "off":
|
||||
stateStr = "off"
|
||||
}
|
||||
|
||||
ethernetDevices = append(ethernetDevices, EthernetDevice{
|
||||
Name: name,
|
||||
HwAddress: hwAddr,
|
||||
State: stateStr,
|
||||
Connected: active,
|
||||
IP: ip,
|
||||
})
|
||||
}
|
||||
|
||||
b.stateMutex.Lock()
|
||||
@@ -162,6 +194,7 @@ func (b *SystemdNetworkdBackend) updateState() error {
|
||||
b.state.WiFiConnected = false
|
||||
b.state.WiFiIP = ""
|
||||
b.state.WiredConnections = wiredConns
|
||||
b.state.EthernetDevices = ethernetDevices
|
||||
|
||||
if wiredIface != nil {
|
||||
b.state.EthernetDevice = wiredIface.name
|
||||
|
||||
@@ -108,3 +108,13 @@ func (b *SystemdNetworkdBackend) ActivateWiredConnection(id string) error {
|
||||
linkObj := b.conn.Object(networkdBusName, link.path)
|
||||
return linkObj.Call(networkdLinkIface+".Reconfigure", 0).Err
|
||||
}
|
||||
|
||||
func (b *SystemdNetworkdBackend) GetEthernetDevices() []EthernetDevice {
|
||||
b.stateMutex.RLock()
|
||||
defer b.stateMutex.RUnlock()
|
||||
return append([]EthernetDevice(nil), b.state.EthernetDevices...)
|
||||
}
|
||||
|
||||
func (b *SystemdNetworkdBackend) DisconnectEthernetDevice(device string) error {
|
||||
return fmt.Errorf("not supported by networkd backend")
|
||||
}
|
||||
|
||||
@@ -123,3 +123,25 @@ func TestSystemdNetworkdBackend_DisconnectEthernet(t *testing.T) {
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "not supported")
|
||||
}
|
||||
|
||||
func TestSystemdNetworkdBackend_GetEthernetDevices(t *testing.T) {
|
||||
backend, _ := NewSystemdNetworkdBackend()
|
||||
|
||||
backend.state.EthernetDevices = []EthernetDevice{
|
||||
{Name: "enp0s3", State: "routable", Connected: true},
|
||||
{Name: "enp0s8", State: "no-carrier", Connected: false},
|
||||
}
|
||||
|
||||
devices := backend.GetEthernetDevices()
|
||||
assert.Len(t, devices, 2)
|
||||
assert.Equal(t, "enp0s3", devices[0].Name)
|
||||
assert.True(t, devices[0].Connected)
|
||||
}
|
||||
|
||||
func TestSystemdNetworkdBackend_DisconnectEthernetDevice(t *testing.T) {
|
||||
backend, _ := NewSystemdNetworkdBackend()
|
||||
|
||||
err := backend.DisconnectEthernetDevice("enp0s3")
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "not supported")
|
||||
}
|
||||
|
||||
@@ -54,6 +54,30 @@ func (b *SystemdNetworkdBackend) ClearVPNCredentials(uuidOrName string) error {
|
||||
return fmt.Errorf("VPN not supported by networkd backend")
|
||||
}
|
||||
|
||||
func (b *SystemdNetworkdBackend) ListVPNPlugins() ([]VPNPlugin, error) {
|
||||
return []VPNPlugin{}, nil
|
||||
}
|
||||
|
||||
func (b *SystemdNetworkdBackend) ImportVPN(filePath string, name string) (*VPNImportResult, error) {
|
||||
return nil, fmt.Errorf("VPN not supported by networkd backend")
|
||||
}
|
||||
|
||||
func (b *SystemdNetworkdBackend) GetVPNConfig(uuidOrName string) (*VPNConfig, error) {
|
||||
return nil, fmt.Errorf("VPN not supported by networkd backend")
|
||||
}
|
||||
|
||||
func (b *SystemdNetworkdBackend) UpdateVPNConfig(uuid string, updates map[string]interface{}) error {
|
||||
return fmt.Errorf("VPN not supported by networkd backend")
|
||||
}
|
||||
|
||||
func (b *SystemdNetworkdBackend) DeleteVPN(uuidOrName string) error {
|
||||
return fmt.Errorf("VPN not supported by networkd backend")
|
||||
}
|
||||
|
||||
func (b *SystemdNetworkdBackend) SetVPNCredentials(uuid, username, password string, save bool) error {
|
||||
return fmt.Errorf("VPN not supported by networkd backend")
|
||||
}
|
||||
|
||||
func (b *SystemdNetworkdBackend) SetWiFiAutoconnect(ssid string, autoconnect bool) error {
|
||||
return fmt.Errorf("WiFi autoconnect not supported by networkd backend")
|
||||
}
|
||||
|
||||
@@ -37,13 +37,21 @@ type wifiDeviceInfo struct {
|
||||
hwAddress string
|
||||
}
|
||||
|
||||
type ethernetDeviceInfo struct {
|
||||
device gonetworkmanager.Device
|
||||
wired gonetworkmanager.DeviceWired
|
||||
name string
|
||||
hwAddress string
|
||||
}
|
||||
|
||||
type NetworkManagerBackend struct {
|
||||
nmConn interface{}
|
||||
ethernetDevice interface{}
|
||||
wifiDevice interface{}
|
||||
settings interface{}
|
||||
wifiDev interface{}
|
||||
wifiDevices map[string]*wifiDeviceInfo
|
||||
nmConn interface{}
|
||||
ethernetDevice interface{}
|
||||
ethernetDevices map[string]*ethernetDeviceInfo
|
||||
wifiDevice interface{}
|
||||
settings interface{}
|
||||
wifiDev interface{}
|
||||
wifiDevices map[string]*wifiDeviceInfo
|
||||
|
||||
dbusConn *dbus.Conn
|
||||
signals chan *dbus.Signal
|
||||
@@ -60,9 +68,27 @@ type NetworkManagerBackend struct {
|
||||
lastFailedTime int64
|
||||
failedMutex sync.RWMutex
|
||||
|
||||
pendingVPNSave *pendingVPNCredentials
|
||||
pendingVPNSaveMu sync.Mutex
|
||||
cachedVPNCreds *cachedVPNCredentials
|
||||
cachedVPNCredsMu sync.Mutex
|
||||
|
||||
onStateChange func()
|
||||
}
|
||||
|
||||
type pendingVPNCredentials struct {
|
||||
ConnectionPath string
|
||||
Username string
|
||||
Password string
|
||||
SavePassword bool
|
||||
}
|
||||
|
||||
type cachedVPNCredentials struct {
|
||||
ConnectionUUID string
|
||||
Password string
|
||||
SavePassword bool
|
||||
}
|
||||
|
||||
func NewNetworkManagerBackend(nmConn ...gonetworkmanager.NetworkManager) (*NetworkManagerBackend, error) {
|
||||
var nm gonetworkmanager.NetworkManager
|
||||
var err error
|
||||
@@ -79,9 +105,10 @@ func NewNetworkManagerBackend(nmConn ...gonetworkmanager.NetworkManager) (*Netwo
|
||||
}
|
||||
|
||||
backend := &NetworkManagerBackend{
|
||||
nmConn: nm,
|
||||
stopChan: make(chan struct{}),
|
||||
wifiDevices: make(map[string]*wifiDeviceInfo),
|
||||
nmConn: nm,
|
||||
stopChan: make(chan struct{}),
|
||||
ethernetDevices: make(map[string]*ethernetDeviceInfo),
|
||||
wifiDevices: make(map[string]*wifiDeviceInfo),
|
||||
state: &BackendState{
|
||||
Backend: "networkmanager",
|
||||
},
|
||||
@@ -113,11 +140,30 @@ func (b *NetworkManagerBackend) Initialize() error {
|
||||
if managed, _ := dev.GetPropertyManaged(); !managed {
|
||||
continue
|
||||
}
|
||||
b.ethernetDevice = dev
|
||||
iface, err := dev.GetPropertyInterface()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
w, err := gonetworkmanager.NewDeviceWired(dev.GetPath())
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
hwAddr, _ := w.GetPropertyHwAddress()
|
||||
|
||||
b.ethernetDevices[iface] = ðernetDeviceInfo{
|
||||
device: dev,
|
||||
wired: w,
|
||||
name: iface,
|
||||
hwAddress: hwAddr,
|
||||
}
|
||||
|
||||
if b.ethernetDevice == nil {
|
||||
b.ethernetDevice = dev
|
||||
}
|
||||
if err := b.updateEthernetState(); err != nil {
|
||||
continue
|
||||
}
|
||||
_, err := b.listEthernetConnections()
|
||||
_, err = b.listEthernetConnections()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get wired configurations: %w", err)
|
||||
}
|
||||
@@ -165,6 +211,8 @@ func (b *NetworkManagerBackend) Initialize() error {
|
||||
b.updateAllWiFiDevices()
|
||||
}
|
||||
|
||||
b.updateAllEthernetDevices()
|
||||
|
||||
if err := b.updatePrimaryConnection(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -197,6 +245,7 @@ func (b *NetworkManagerBackend) GetCurrentState() (*BackendState, error) {
|
||||
state.WiFiNetworks = append([]WiFiNetwork(nil), b.state.WiFiNetworks...)
|
||||
state.WiFiDevices = append([]WiFiDevice(nil), b.state.WiFiDevices...)
|
||||
state.WiredConnections = append([]WiredConnection(nil), b.state.WiredConnections...)
|
||||
state.EthernetDevices = append([]EthernetDevice(nil), b.state.EthernetDevices...)
|
||||
state.VPNProfiles = append([]VPNProfile(nil), b.state.VPNProfiles...)
|
||||
state.VPNActive = append([]VPNActive(nil), b.state.VPNActive...)
|
||||
|
||||
|
||||
@@ -315,3 +315,88 @@ func (b *NetworkManagerBackend) listEthernetConnections() ([]WiredConnection, er
|
||||
|
||||
return wiredConfigs, nil
|
||||
}
|
||||
|
||||
func (b *NetworkManagerBackend) GetEthernetDevices() []EthernetDevice {
|
||||
b.stateMutex.RLock()
|
||||
defer b.stateMutex.RUnlock()
|
||||
return append([]EthernetDevice(nil), b.state.EthernetDevices...)
|
||||
}
|
||||
|
||||
func (b *NetworkManagerBackend) DisconnectEthernetDevice(device string) error {
|
||||
info, ok := b.ethernetDevices[device]
|
||||
if !ok {
|
||||
return fmt.Errorf("ethernet device %s not found", device)
|
||||
}
|
||||
|
||||
if err := info.device.Disconnect(); err != nil {
|
||||
return fmt.Errorf("failed to disconnect %s: %w", device, err)
|
||||
}
|
||||
|
||||
b.updateAllEthernetDevices()
|
||||
b.updateEthernetState()
|
||||
b.listEthernetConnections()
|
||||
b.updatePrimaryConnection()
|
||||
|
||||
if b.onStateChange != nil {
|
||||
b.onStateChange()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *NetworkManagerBackend) updateAllEthernetDevices() {
|
||||
devices := make([]EthernetDevice, 0, len(b.ethernetDevices))
|
||||
|
||||
for name, info := range b.ethernetDevices {
|
||||
state, _ := info.device.GetPropertyState()
|
||||
connected := state == gonetworkmanager.NmDeviceStateActivated
|
||||
driver, _ := info.device.GetPropertyDriver()
|
||||
|
||||
var ip string
|
||||
var speed uint32 = 0
|
||||
if connected {
|
||||
ip = b.getDeviceIP(info.device)
|
||||
}
|
||||
if info.wired != nil {
|
||||
speed, _ = info.wired.GetPropertySpeed()
|
||||
}
|
||||
|
||||
stateStr := "disconnected"
|
||||
switch state {
|
||||
case gonetworkmanager.NmDeviceStateActivated:
|
||||
stateStr = "activated"
|
||||
case gonetworkmanager.NmDeviceStatePrepare:
|
||||
stateStr = "preparing"
|
||||
case gonetworkmanager.NmDeviceStateConfig:
|
||||
stateStr = "configuring"
|
||||
case gonetworkmanager.NmDeviceStateIpConfig:
|
||||
stateStr = "ip-config"
|
||||
case gonetworkmanager.NmDeviceStateIpCheck:
|
||||
stateStr = "ip-check"
|
||||
case gonetworkmanager.NmDeviceStateSecondaries:
|
||||
stateStr = "secondaries"
|
||||
case gonetworkmanager.NmDeviceStateDeactivating:
|
||||
stateStr = "deactivating"
|
||||
case gonetworkmanager.NmDeviceStateFailed:
|
||||
stateStr = "failed"
|
||||
case gonetworkmanager.NmDeviceStateUnavailable:
|
||||
stateStr = "unavailable"
|
||||
case gonetworkmanager.NmDeviceStateUnmanaged:
|
||||
stateStr = "unmanaged"
|
||||
}
|
||||
|
||||
devices = append(devices, EthernetDevice{
|
||||
Name: name,
|
||||
HwAddress: info.hwAddress,
|
||||
State: stateStr,
|
||||
Connected: connected,
|
||||
IP: ip,
|
||||
Speed: speed,
|
||||
Driver: driver,
|
||||
})
|
||||
}
|
||||
|
||||
b.stateMutex.Lock()
|
||||
b.state.EthernetDevices = devices
|
||||
b.stateMutex.Unlock()
|
||||
}
|
||||
|
||||
@@ -82,3 +82,53 @@ func TestNetworkManagerBackend_ListEthernetConnections_NoDevice(t *testing.T) {
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "no ethernet device available")
|
||||
}
|
||||
|
||||
func TestNetworkManagerBackend_GetEthernetDevices_Empty(t *testing.T) {
|
||||
mockNM := mock_gonetworkmanager.NewMockNetworkManager(t)
|
||||
|
||||
backend, err := NewNetworkManagerBackend(mockNM)
|
||||
assert.NoError(t, err)
|
||||
|
||||
devices := backend.GetEthernetDevices()
|
||||
assert.Empty(t, devices)
|
||||
}
|
||||
|
||||
func TestNetworkManagerBackend_GetEthernetDevices_WithState(t *testing.T) {
|
||||
mockNM := mock_gonetworkmanager.NewMockNetworkManager(t)
|
||||
|
||||
backend, err := NewNetworkManagerBackend(mockNM)
|
||||
assert.NoError(t, err)
|
||||
|
||||
backend.state.EthernetDevices = []EthernetDevice{
|
||||
{Name: "enp0s3", HwAddress: "00:11:22:33:44:55", State: "activated", Connected: true, IP: "192.168.1.100"},
|
||||
{Name: "enp0s8", HwAddress: "00:11:22:33:44:66", State: "disconnected", Connected: false},
|
||||
}
|
||||
|
||||
devices := backend.GetEthernetDevices()
|
||||
assert.Len(t, devices, 2)
|
||||
assert.Equal(t, "enp0s3", devices[0].Name)
|
||||
assert.True(t, devices[0].Connected)
|
||||
assert.Equal(t, "enp0s8", devices[1].Name)
|
||||
assert.False(t, devices[1].Connected)
|
||||
}
|
||||
|
||||
func TestNetworkManagerBackend_DisconnectEthernetDevice_NotFound(t *testing.T) {
|
||||
mockNM := mock_gonetworkmanager.NewMockNetworkManager(t)
|
||||
|
||||
backend, err := NewNetworkManagerBackend(mockNM)
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = backend.DisconnectEthernetDevice("nonexistent")
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "not found")
|
||||
}
|
||||
|
||||
func TestNetworkManagerBackend_UpdateAllEthernetDevices_Empty(t *testing.T) {
|
||||
mockNM := mock_gonetworkmanager.NewMockNetworkManager(t)
|
||||
|
||||
backend, err := NewNetworkManagerBackend(mockNM)
|
||||
assert.NoError(t, err)
|
||||
|
||||
backend.updateAllEthernetDevices()
|
||||
assert.Empty(t, backend.state.EthernetDevices)
|
||||
}
|
||||
|
||||
@@ -61,6 +61,26 @@ func (b *NetworkManagerBackend) startSignalPump() error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := conn.AddMatchSignal(
|
||||
dbus.WithMatchObjectPath(dbus.ObjectPath(dbusNMPath)),
|
||||
dbus.WithMatchInterface(dbusNMInterface),
|
||||
dbus.WithMatchMember("DeviceAdded"),
|
||||
); err != nil {
|
||||
conn.RemoveSignal(signals)
|
||||
conn.Close()
|
||||
return err
|
||||
}
|
||||
|
||||
if err := conn.AddMatchSignal(
|
||||
dbus.WithMatchObjectPath(dbus.ObjectPath(dbusNMPath)),
|
||||
dbus.WithMatchInterface(dbusNMInterface),
|
||||
dbus.WithMatchMember("DeviceRemoved"),
|
||||
); err != nil {
|
||||
conn.RemoveSignal(signals)
|
||||
conn.Close()
|
||||
return err
|
||||
}
|
||||
|
||||
if b.wifiDevice != nil {
|
||||
dev := b.wifiDevice.(gonetworkmanager.Device)
|
||||
if err := conn.AddMatchSignal(
|
||||
@@ -175,6 +195,24 @@ func (b *NetworkManagerBackend) handleDBusSignal(sig *dbus.Signal) {
|
||||
return
|
||||
}
|
||||
|
||||
if sig.Name == "org.freedesktop.NetworkManager.DeviceAdded" {
|
||||
if len(sig.Body) >= 1 {
|
||||
if devicePath, ok := sig.Body[0].(dbus.ObjectPath); ok {
|
||||
b.handleDeviceAdded(devicePath)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if sig.Name == "org.freedesktop.NetworkManager.DeviceRemoved" {
|
||||
if len(sig.Body) >= 1 {
|
||||
if devicePath, ok := sig.Body[0].(dbus.ObjectPath); ok {
|
||||
b.handleDeviceRemoved(devicePath)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if len(sig.Body) < 2 {
|
||||
return
|
||||
}
|
||||
@@ -319,3 +357,156 @@ func (b *NetworkManagerBackend) handleAccessPointChange(changes map[string]dbus.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (b *NetworkManagerBackend) handleDeviceAdded(devicePath dbus.ObjectPath) {
|
||||
dev, err := gonetworkmanager.NewDevice(devicePath)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
devType, err := dev.GetPropertyDeviceType()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
managed, _ := dev.GetPropertyManaged()
|
||||
if !managed {
|
||||
return
|
||||
}
|
||||
|
||||
iface, err := dev.GetPropertyInterface()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
switch devType {
|
||||
case gonetworkmanager.NmDeviceTypeEthernet:
|
||||
w, err := gonetworkmanager.NewDeviceWired(devicePath)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
hwAddr, _ := w.GetPropertyHwAddress()
|
||||
|
||||
b.ethernetDevices[iface] = ðernetDeviceInfo{
|
||||
device: dev,
|
||||
wired: w,
|
||||
name: iface,
|
||||
hwAddress: hwAddr,
|
||||
}
|
||||
|
||||
if b.ethernetDevice == nil {
|
||||
b.ethernetDevice = dev
|
||||
}
|
||||
|
||||
if b.dbusConn != nil {
|
||||
b.dbusConn.AddMatchSignal(
|
||||
dbus.WithMatchObjectPath(devicePath),
|
||||
dbus.WithMatchInterface(dbusPropsInterface),
|
||||
dbus.WithMatchMember("PropertiesChanged"),
|
||||
)
|
||||
}
|
||||
|
||||
b.updateAllEthernetDevices()
|
||||
b.updateEthernetState()
|
||||
b.listEthernetConnections()
|
||||
b.updatePrimaryConnection()
|
||||
|
||||
case gonetworkmanager.NmDeviceTypeWifi:
|
||||
w, err := gonetworkmanager.NewDeviceWireless(devicePath)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
hwAddr, _ := w.GetPropertyHwAddress()
|
||||
|
||||
b.wifiDevices[iface] = &wifiDeviceInfo{
|
||||
device: dev,
|
||||
wireless: w,
|
||||
name: iface,
|
||||
hwAddress: hwAddr,
|
||||
}
|
||||
|
||||
if b.wifiDevice == nil {
|
||||
b.wifiDevice = dev
|
||||
b.wifiDev = w
|
||||
}
|
||||
|
||||
if b.dbusConn != nil {
|
||||
b.dbusConn.AddMatchSignal(
|
||||
dbus.WithMatchObjectPath(devicePath),
|
||||
dbus.WithMatchInterface(dbusPropsInterface),
|
||||
dbus.WithMatchMember("PropertiesChanged"),
|
||||
)
|
||||
}
|
||||
|
||||
b.updateAllWiFiDevices()
|
||||
b.updateWiFiState()
|
||||
}
|
||||
|
||||
if b.onStateChange != nil {
|
||||
b.onStateChange()
|
||||
}
|
||||
}
|
||||
|
||||
func (b *NetworkManagerBackend) handleDeviceRemoved(devicePath dbus.ObjectPath) {
|
||||
if b.dbusConn != nil {
|
||||
b.dbusConn.RemoveMatchSignal(
|
||||
dbus.WithMatchObjectPath(devicePath),
|
||||
dbus.WithMatchInterface(dbusPropsInterface),
|
||||
dbus.WithMatchMember("PropertiesChanged"),
|
||||
)
|
||||
}
|
||||
|
||||
for iface, info := range b.ethernetDevices {
|
||||
if info.device.GetPath() == devicePath {
|
||||
delete(b.ethernetDevices, iface)
|
||||
|
||||
if b.ethernetDevice != nil {
|
||||
dev := b.ethernetDevice.(gonetworkmanager.Device)
|
||||
if dev.GetPath() == devicePath {
|
||||
b.ethernetDevice = nil
|
||||
for _, remaining := range b.ethernetDevices {
|
||||
b.ethernetDevice = remaining.device
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
b.updateAllEthernetDevices()
|
||||
b.updateEthernetState()
|
||||
b.listEthernetConnections()
|
||||
b.updatePrimaryConnection()
|
||||
|
||||
if b.onStateChange != nil {
|
||||
b.onStateChange()
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
for iface, info := range b.wifiDevices {
|
||||
if info.device.GetPath() == devicePath {
|
||||
delete(b.wifiDevices, iface)
|
||||
|
||||
if b.wifiDevice != nil {
|
||||
dev := b.wifiDevice.(gonetworkmanager.Device)
|
||||
if dev.GetPath() == devicePath {
|
||||
b.wifiDevice = nil
|
||||
b.wifiDev = nil
|
||||
for _, remaining := range b.wifiDevices {
|
||||
b.wifiDevice = remaining.device
|
||||
b.wifiDev = remaining.wireless
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
b.updateAllWiFiDevices()
|
||||
b.updateWiFiState()
|
||||
|
||||
if b.onStateChange != nil {
|
||||
b.onStateChange()
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,33 +72,34 @@ func (b *NetworkManagerBackend) updatePrimaryConnection() error {
|
||||
}
|
||||
|
||||
func (b *NetworkManagerBackend) updateEthernetState() error {
|
||||
if b.ethernetDevice == nil {
|
||||
return nil
|
||||
var connectedDevice string
|
||||
var connectedIP string
|
||||
var anyConnected bool
|
||||
|
||||
for name, info := range b.ethernetDevices {
|
||||
state, err := info.device.GetPropertyState()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if state == gonetworkmanager.NmDeviceStateActivated {
|
||||
anyConnected = true
|
||||
connectedDevice = name
|
||||
connectedIP = b.getDeviceIP(info.device)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
dev := b.ethernetDevice.(gonetworkmanager.Device)
|
||||
|
||||
iface, err := dev.GetPropertyInterface()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
state, err := dev.GetPropertyState()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
connected := state == gonetworkmanager.NmDeviceStateActivated
|
||||
|
||||
var ip string
|
||||
if connected {
|
||||
ip = b.getDeviceIP(dev)
|
||||
if !anyConnected && b.ethernetDevice != nil {
|
||||
dev := b.ethernetDevice.(gonetworkmanager.Device)
|
||||
iface, _ := dev.GetPropertyInterface()
|
||||
connectedDevice = iface
|
||||
}
|
||||
|
||||
b.stateMutex.Lock()
|
||||
b.state.EthernetDevice = iface
|
||||
b.state.EthernetConnected = connected
|
||||
b.state.EthernetIP = ip
|
||||
b.state.EthernetDevice = connectedDevice
|
||||
b.state.EthernetConnected = anyConnected
|
||||
b.state.EthernetIP = connectedIP
|
||||
b.stateMutex.Unlock()
|
||||
|
||||
return nil
|
||||
|
||||
@@ -1,13 +1,19 @@
|
||||
package network
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/log"
|
||||
"github.com/Wifx/gonetworkmanager/v2"
|
||||
"github.com/godbus/dbus/v5"
|
||||
)
|
||||
|
||||
func (b *NetworkManagerBackend) ListVPNProfiles() ([]VPNProfile, error) {
|
||||
@@ -46,11 +52,13 @@ func (b *NetworkManagerBackend) ListVPNProfiles() ([]VPNProfile, error) {
|
||||
|
||||
connID, _ := connMeta["id"].(string)
|
||||
connUUID, _ := connMeta["uuid"].(string)
|
||||
autoconnect, _ := connMeta["autoconnect"].(bool)
|
||||
|
||||
profile := VPNProfile{
|
||||
Name: connID,
|
||||
UUID: connUUID,
|
||||
Type: connType,
|
||||
Name: connID,
|
||||
UUID: connUUID,
|
||||
Type: connType,
|
||||
Autoconnect: autoconnect,
|
||||
}
|
||||
|
||||
if connType == "vpn" {
|
||||
@@ -58,6 +66,16 @@ func (b *NetworkManagerBackend) ListVPNProfiles() ([]VPNProfile, error) {
|
||||
if svcType, ok := vpnSettings["service-type"].(string); ok {
|
||||
profile.ServiceType = svcType
|
||||
}
|
||||
// Get full data map
|
||||
if data, ok := vpnSettings["data"].(map[string]string); ok {
|
||||
profile.Data = data
|
||||
if remote, ok := data["remote"]; ok {
|
||||
profile.RemoteHost = remote
|
||||
}
|
||||
if username, ok := data["username"]; ok {
|
||||
profile.Username = username
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,6 +138,31 @@ func (b *NetworkManagerBackend) ListActiveVPN() ([]VPNActive, error) {
|
||||
Plugin: "",
|
||||
}
|
||||
|
||||
// Get VPN device
|
||||
devices, _ := activeConn.GetPropertyDevices()
|
||||
if len(devices) > 0 {
|
||||
if iface, err := devices[0].GetPropertyInterface(); err == nil {
|
||||
vpnActive.Device = iface
|
||||
}
|
||||
}
|
||||
|
||||
// Get VPN IP from IP4Config
|
||||
if ip4Config, err := activeConn.GetPropertyIP4Config(); err == nil && ip4Config != nil {
|
||||
if addrData, err := ip4Config.GetPropertyAddressData(); err == nil && len(addrData) > 0 {
|
||||
vpnActive.IP = addrData[0].Address
|
||||
}
|
||||
if gw, err := ip4Config.GetPropertyGateway(); err == nil {
|
||||
vpnActive.Gateway = gw
|
||||
}
|
||||
}
|
||||
|
||||
// Get MTU from device
|
||||
if len(devices) > 0 {
|
||||
if mtu, err := devices[0].GetPropertyMtu(); err == nil {
|
||||
vpnActive.MTU = mtu
|
||||
}
|
||||
}
|
||||
|
||||
if connType == "vpn" {
|
||||
conn, _ := activeConn.GetPropertyConnection()
|
||||
if conn != nil {
|
||||
@@ -129,6 +172,16 @@ func (b *NetworkManagerBackend) ListActiveVPN() ([]VPNActive, error) {
|
||||
if svcType, ok := vpnSettings["service-type"].(string); ok {
|
||||
vpnActive.Plugin = svcType
|
||||
}
|
||||
// Get full data map
|
||||
if data, ok := vpnSettings["data"].(map[string]string); ok {
|
||||
vpnActive.Data = data
|
||||
if remote, ok := data["remote"]; ok {
|
||||
vpnActive.RemoteHost = remote
|
||||
}
|
||||
if username, ok := data["username"]; ok {
|
||||
vpnActive.Username = username
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -219,10 +272,122 @@ func (b *NetworkManagerBackend) ConnectVPN(uuidOrName string, singleActive bool)
|
||||
}
|
||||
|
||||
var targetUUID string
|
||||
var connName string
|
||||
if connMeta, ok := targetSettings["connection"]; ok {
|
||||
if uuid, ok := connMeta["uuid"].(string); ok {
|
||||
targetUUID = uuid
|
||||
}
|
||||
if id, ok := connMeta["id"].(string); ok {
|
||||
connName = id
|
||||
}
|
||||
}
|
||||
|
||||
needsUsernamePrePrompt := false
|
||||
var vpnServiceType string
|
||||
if vpnSettings, ok := targetSettings["vpn"]; ok {
|
||||
if svc, ok := vpnSettings["service-type"].(string); ok {
|
||||
vpnServiceType = svc
|
||||
}
|
||||
if data, ok := vpnSettings["data"].(map[string]string); ok {
|
||||
connType := data["connection-type"]
|
||||
username := data["username"]
|
||||
// OpenVPN password auth needs username in vpn.data
|
||||
if strings.Contains(vpnServiceType, "openvpn") &&
|
||||
(connType == "password" || connType == "password-tls") &&
|
||||
username == "" {
|
||||
needsUsernamePrePrompt = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If username is needed but missing, prompt for it before activating
|
||||
if needsUsernamePrePrompt && b.promptBroker != nil {
|
||||
log.Infof("[ConnectVPN] OpenVPN requires username in vpn.data - prompting before activation")
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
|
||||
defer cancel()
|
||||
|
||||
token, err := b.promptBroker.Ask(ctx, PromptRequest{
|
||||
Name: connName,
|
||||
ConnType: "vpn",
|
||||
VpnService: vpnServiceType,
|
||||
SettingName: "vpn",
|
||||
Fields: []string{"username", "password"},
|
||||
FieldsInfo: []FieldInfo{{Name: "username", Label: "Username", IsSecret: false}, {Name: "password", Label: "Password", IsSecret: true}},
|
||||
Reason: "required",
|
||||
ConnectionId: connName,
|
||||
ConnectionUuid: targetUUID,
|
||||
ConnectionPath: string(targetConn.GetPath()),
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to request credentials: %w", err)
|
||||
}
|
||||
|
||||
reply, err := b.promptBroker.Wait(ctx, token)
|
||||
if err != nil {
|
||||
return fmt.Errorf("credentials prompt failed: %w", err)
|
||||
}
|
||||
|
||||
username := reply.Secrets["username"]
|
||||
password := reply.Secrets["password"]
|
||||
if username != "" {
|
||||
connObj := b.dbusConn.Object("org.freedesktop.NetworkManager", targetConn.GetPath())
|
||||
var existingSettings map[string]map[string]dbus.Variant
|
||||
if err := connObj.Call("org.freedesktop.NetworkManager.Settings.Connection.GetSettings", 0).Store(&existingSettings); err != nil {
|
||||
return fmt.Errorf("failed to get settings for username save: %w", err)
|
||||
}
|
||||
|
||||
settings := make(map[string]map[string]dbus.Variant)
|
||||
if connSection, ok := existingSettings["connection"]; ok {
|
||||
settings["connection"] = connSection
|
||||
}
|
||||
vpn := existingSettings["vpn"]
|
||||
var data map[string]string
|
||||
if dataVariant, ok := vpn["data"]; ok {
|
||||
if dm, ok := dataVariant.Value().(map[string]string); ok {
|
||||
data = make(map[string]string)
|
||||
for k, v := range dm {
|
||||
data[k] = v
|
||||
}
|
||||
} else {
|
||||
data = make(map[string]string)
|
||||
}
|
||||
} else {
|
||||
data = make(map[string]string)
|
||||
}
|
||||
data["username"] = username
|
||||
|
||||
if reply.Save && password != "" {
|
||||
data["password-flags"] = "0"
|
||||
secs := make(map[string]string)
|
||||
secs["password"] = password
|
||||
vpn["secrets"] = dbus.MakeVariant(secs)
|
||||
log.Infof("[ConnectVPN] Saving username and password to vpn.data")
|
||||
} else {
|
||||
log.Infof("[ConnectVPN] Saving username to vpn.data (password will be prompted)")
|
||||
}
|
||||
|
||||
vpn["data"] = dbus.MakeVariant(data)
|
||||
settings["vpn"] = vpn
|
||||
|
||||
var result map[string]dbus.Variant
|
||||
if err := connObj.Call("org.freedesktop.NetworkManager.Settings.Connection.Update2", 0,
|
||||
settings, uint32(0x1), map[string]dbus.Variant{}).Store(&result); err != nil {
|
||||
return fmt.Errorf("failed to save username: %w", err)
|
||||
}
|
||||
log.Infof("[ConnectVPN] Username saved to connection, now activating")
|
||||
|
||||
if password != "" && !reply.Save {
|
||||
b.cachedVPNCredsMu.Lock()
|
||||
b.cachedVPNCreds = &cachedVPNCredentials{
|
||||
ConnectionUUID: targetUUID,
|
||||
Password: password,
|
||||
SavePassword: reply.Save,
|
||||
}
|
||||
b.cachedVPNCredsMu.Unlock()
|
||||
log.Infof("[ConnectVPN] Cached password for GetSecrets")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
b.stateMutex.Lock()
|
||||
@@ -470,7 +635,7 @@ func (b *NetworkManagerBackend) updateVPNConnectionState() {
|
||||
continue
|
||||
}
|
||||
|
||||
uuid, err := activeConn.GetPropertyUUID()
|
||||
connUUID, err := activeConn.GetPropertyUUID()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
@@ -478,20 +643,29 @@ func (b *NetworkManagerBackend) updateVPNConnectionState() {
|
||||
state, _ := activeConn.GetPropertyState()
|
||||
stateReason, _ := activeConn.GetPropertyStateFlags()
|
||||
|
||||
if uuid == connectingVPNUUID {
|
||||
if connUUID == connectingVPNUUID {
|
||||
foundConnection = true
|
||||
|
||||
switch state {
|
||||
case 2:
|
||||
log.Infof("[updateVPNConnectionState] VPN connection successful: %s", uuid)
|
||||
log.Infof("[updateVPNConnectionState] VPN connection successful: %s", connUUID)
|
||||
b.stateMutex.Lock()
|
||||
b.state.IsConnectingVPN = false
|
||||
b.state.ConnectingVPNUUID = ""
|
||||
b.state.LastError = ""
|
||||
b.stateMutex.Unlock()
|
||||
|
||||
b.pendingVPNSaveMu.Lock()
|
||||
pending := b.pendingVPNSave
|
||||
b.pendingVPNSave = nil
|
||||
b.pendingVPNSaveMu.Unlock()
|
||||
|
||||
if pending != nil {
|
||||
go b.saveVPNCredentials(pending)
|
||||
}
|
||||
return
|
||||
case 4:
|
||||
log.Warnf("[updateVPNConnectionState] VPN connection failed/deactivated: %s (state=%d, flags=%d)", uuid, state, stateReason)
|
||||
log.Warnf("[updateVPNConnectionState] VPN connection failed/deactivated: %s (state=%d, flags=%d)", connUUID, state, stateReason)
|
||||
b.stateMutex.Lock()
|
||||
b.state.IsConnectingVPN = false
|
||||
b.state.ConnectingVPNUUID = ""
|
||||
@@ -511,3 +685,622 @@ func (b *NetworkManagerBackend) updateVPNConnectionState() {
|
||||
b.stateMutex.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
func (b *NetworkManagerBackend) saveVPNCredentials(creds *pendingVPNCredentials) {
|
||||
log.Infof("[saveVPNCredentials] Saving credentials for %s (username=%v, savePassword=%v)",
|
||||
creds.ConnectionPath, creds.Username != "", creds.SavePassword)
|
||||
|
||||
connObj := b.dbusConn.Object("org.freedesktop.NetworkManager", dbus.ObjectPath(creds.ConnectionPath))
|
||||
var existingSettings map[string]map[string]dbus.Variant
|
||||
if err := connObj.Call("org.freedesktop.NetworkManager.Settings.Connection.GetSettings", 0).Store(&existingSettings); err != nil {
|
||||
log.Warnf("[saveVPNCredentials] GetSettings failed: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
settings := make(map[string]map[string]dbus.Variant)
|
||||
if connSection, ok := existingSettings["connection"]; ok {
|
||||
settings["connection"] = connSection
|
||||
}
|
||||
|
||||
vpn, ok := existingSettings["vpn"]
|
||||
if !ok {
|
||||
vpn = make(map[string]dbus.Variant)
|
||||
}
|
||||
|
||||
// Get existing data map
|
||||
var data map[string]string
|
||||
if dataVariant, ok := vpn["data"]; ok {
|
||||
if dm, ok := dataVariant.Value().(map[string]string); ok {
|
||||
data = make(map[string]string)
|
||||
for k, v := range dm {
|
||||
data[k] = v
|
||||
}
|
||||
} else {
|
||||
data = make(map[string]string)
|
||||
}
|
||||
} else {
|
||||
data = make(map[string]string)
|
||||
}
|
||||
|
||||
// Always save username if provided
|
||||
if creds.Username != "" {
|
||||
data["username"] = creds.Username
|
||||
log.Infof("[saveVPNCredentials] Saving username")
|
||||
}
|
||||
|
||||
// Save password if requested
|
||||
if creds.SavePassword {
|
||||
data["password-flags"] = "0"
|
||||
secs := make(map[string]string)
|
||||
secs["password"] = creds.Password
|
||||
vpn["secrets"] = dbus.MakeVariant(secs)
|
||||
log.Infof("[saveVPNCredentials] Saving password with password-flags=0")
|
||||
}
|
||||
|
||||
vpn["data"] = dbus.MakeVariant(data)
|
||||
settings["vpn"] = vpn
|
||||
|
||||
var result map[string]dbus.Variant
|
||||
if err := connObj.Call("org.freedesktop.NetworkManager.Settings.Connection.Update2", 0,
|
||||
settings, uint32(0x1), map[string]dbus.Variant{}).Store(&result); err != nil {
|
||||
log.Warnf("[saveVPNCredentials] Update2 failed: %v", err)
|
||||
} else {
|
||||
log.Infof("[saveVPNCredentials] Successfully saved credentials")
|
||||
}
|
||||
}
|
||||
|
||||
func (b *NetworkManagerBackend) ListVPNPlugins() ([]VPNPlugin, error) {
|
||||
plugins := []VPNPlugin{}
|
||||
pluginDirs := []string{
|
||||
"/usr/lib/NetworkManager/VPN",
|
||||
"/usr/lib64/NetworkManager/VPN",
|
||||
"/etc/NetworkManager/VPN",
|
||||
}
|
||||
|
||||
seen := make(map[string]bool)
|
||||
|
||||
for _, dir := range pluginDirs {
|
||||
entries, err := os.ReadDir(dir)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, entry := range entries {
|
||||
if !strings.HasSuffix(entry.Name(), ".name") {
|
||||
continue
|
||||
}
|
||||
|
||||
filePath := filepath.Join(dir, entry.Name())
|
||||
plugin, err := parseVPNPluginFile(filePath)
|
||||
if err != nil {
|
||||
log.Debugf("Failed to parse VPN plugin file %s: %v", filePath, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if seen[plugin.ServiceType] {
|
||||
continue
|
||||
}
|
||||
seen[plugin.ServiceType] = true
|
||||
plugins = append(plugins, *plugin)
|
||||
}
|
||||
}
|
||||
|
||||
sort.Slice(plugins, func(i, j int) bool {
|
||||
return strings.ToLower(plugins[i].Name) < strings.ToLower(plugins[j].Name)
|
||||
})
|
||||
|
||||
return plugins, nil
|
||||
}
|
||||
|
||||
func parseVPNPluginFile(path string) (*VPNPlugin, error) {
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
plugin := &VPNPlugin{}
|
||||
scanner := bufio.NewScanner(file)
|
||||
|
||||
for scanner.Scan() {
|
||||
line := strings.TrimSpace(scanner.Text())
|
||||
if line == "" || strings.HasPrefix(line, "#") || strings.HasPrefix(line, "[") {
|
||||
continue
|
||||
}
|
||||
|
||||
parts := strings.SplitN(line, "=", 2)
|
||||
if len(parts) != 2 {
|
||||
continue
|
||||
}
|
||||
|
||||
key := strings.TrimSpace(parts[0])
|
||||
value := strings.TrimSpace(parts[1])
|
||||
|
||||
switch key {
|
||||
case "name":
|
||||
plugin.Name = value
|
||||
case "service":
|
||||
plugin.ServiceType = value
|
||||
case "program":
|
||||
plugin.Program = value
|
||||
case "supports":
|
||||
plugin.Supports = strings.Split(value, ",")
|
||||
for i := range plugin.Supports {
|
||||
plugin.Supports[i] = strings.TrimSpace(plugin.Supports[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if plugin.ServiceType == "" {
|
||||
return nil, fmt.Errorf("plugin file missing service type")
|
||||
}
|
||||
|
||||
plugin.FileExtensions = getVPNFileExtensions(plugin.ServiceType)
|
||||
|
||||
return plugin, nil
|
||||
}
|
||||
|
||||
func getVPNFileExtensions(serviceType string) []string {
|
||||
switch {
|
||||
case strings.Contains(serviceType, "openvpn"):
|
||||
return []string{".ovpn", ".conf"}
|
||||
case strings.Contains(serviceType, "wireguard"):
|
||||
return []string{".conf"}
|
||||
case strings.Contains(serviceType, "vpnc"), strings.Contains(serviceType, "cisco"):
|
||||
return []string{".pcf", ".conf"}
|
||||
case strings.Contains(serviceType, "openconnect"):
|
||||
return []string{".conf"}
|
||||
case strings.Contains(serviceType, "pptp"):
|
||||
return []string{".conf"}
|
||||
case strings.Contains(serviceType, "l2tp"):
|
||||
return []string{".conf"}
|
||||
case strings.Contains(serviceType, "strongswan"), strings.Contains(serviceType, "ipsec"):
|
||||
return []string{".conf", ".sswan"}
|
||||
default:
|
||||
return []string{".conf"}
|
||||
}
|
||||
}
|
||||
|
||||
func (b *NetworkManagerBackend) ImportVPN(filePath string, name string) (*VPNImportResult, error) {
|
||||
if _, err := os.Stat(filePath); os.IsNotExist(err) {
|
||||
return &VPNImportResult{
|
||||
Success: false,
|
||||
Error: fmt.Sprintf("file not found: %s", filePath),
|
||||
}, nil
|
||||
}
|
||||
|
||||
ext := strings.ToLower(filepath.Ext(filePath))
|
||||
|
||||
switch ext {
|
||||
case ".ovpn", ".conf":
|
||||
return b.importVPNWithNmcli(filePath, name)
|
||||
default:
|
||||
return b.importVPNWithNmcli(filePath, name)
|
||||
}
|
||||
}
|
||||
|
||||
func (b *NetworkManagerBackend) importVPNWithNmcli(filePath string, name string) (*VPNImportResult, error) {
|
||||
args := []string{"connection", "import", "type", "openvpn", "file", filePath}
|
||||
cmd := exec.Command("nmcli", args...)
|
||||
output, err := cmd.CombinedOutput()
|
||||
|
||||
if err != nil {
|
||||
outputStr := string(output)
|
||||
if strings.Contains(outputStr, "vpnc") || strings.Contains(outputStr, "unknown connection type") {
|
||||
for _, vpnType := range []string{"vpnc", "pptp", "l2tp", "openconnect", "strongswan", "wireguard"} {
|
||||
args = []string{"connection", "import", "type", vpnType, "file", filePath}
|
||||
cmd = exec.Command("nmcli", args...)
|
||||
output, err = cmd.CombinedOutput()
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return &VPNImportResult{
|
||||
Success: false,
|
||||
Error: fmt.Sprintf("import failed: %s", strings.TrimSpace(string(output))),
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
outputStr := string(output)
|
||||
var connUUID, connName string
|
||||
|
||||
lines := strings.Split(outputStr, "\n")
|
||||
for _, line := range lines {
|
||||
if strings.Contains(line, "successfully added") {
|
||||
parts := strings.Fields(line)
|
||||
for i, part := range parts {
|
||||
if part == "(" && i+1 < len(parts) {
|
||||
connUUID = strings.TrimSuffix(parts[i+1], ")")
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if name != "" && connUUID != "" {
|
||||
renameCmd := exec.Command("nmcli", "connection", "modify", connUUID, "connection.id", name)
|
||||
if err := renameCmd.Run(); err != nil {
|
||||
log.Warnf("Failed to rename imported VPN: %v", err)
|
||||
} else {
|
||||
connName = name
|
||||
}
|
||||
}
|
||||
|
||||
if connUUID == "" {
|
||||
s := b.settings
|
||||
if s == nil {
|
||||
var settingsErr error
|
||||
s, settingsErr = gonetworkmanager.NewSettings()
|
||||
if settingsErr == nil {
|
||||
b.settings = s
|
||||
}
|
||||
}
|
||||
|
||||
if s != nil {
|
||||
settingsMgr := s.(gonetworkmanager.Settings)
|
||||
connections, _ := settingsMgr.ListConnections()
|
||||
baseName := strings.TrimSuffix(filepath.Base(filePath), filepath.Ext(filePath))
|
||||
|
||||
for _, conn := range connections {
|
||||
settings, err := conn.GetSettings()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
connMeta, ok := settings["connection"]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
connType, _ := connMeta["type"].(string)
|
||||
if connType != "vpn" && connType != "wireguard" {
|
||||
continue
|
||||
}
|
||||
connID, _ := connMeta["id"].(string)
|
||||
if strings.Contains(connID, baseName) || (name != "" && connID == name) {
|
||||
connUUID, _ = connMeta["uuid"].(string)
|
||||
connName = connID
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
b.ListVPNProfiles()
|
||||
|
||||
if b.onStateChange != nil {
|
||||
b.onStateChange()
|
||||
}
|
||||
|
||||
return &VPNImportResult{
|
||||
Success: true,
|
||||
UUID: connUUID,
|
||||
Name: connName,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (b *NetworkManagerBackend) GetVPNConfig(uuidOrName string) (*VPNConfig, error) {
|
||||
s := b.settings
|
||||
if s == nil {
|
||||
var err error
|
||||
s, err = gonetworkmanager.NewSettings()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get settings: %w", err)
|
||||
}
|
||||
b.settings = s
|
||||
}
|
||||
|
||||
settingsMgr := s.(gonetworkmanager.Settings)
|
||||
connections, err := settingsMgr.ListConnections()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get connections: %w", err)
|
||||
}
|
||||
|
||||
for _, conn := range connections {
|
||||
settings, err := conn.GetSettings()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
connMeta, ok := settings["connection"]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
connType, _ := connMeta["type"].(string)
|
||||
if connType != "vpn" && connType != "wireguard" {
|
||||
continue
|
||||
}
|
||||
|
||||
connID, _ := connMeta["id"].(string)
|
||||
connUUID, _ := connMeta["uuid"].(string)
|
||||
|
||||
if connUUID != uuidOrName && connID != uuidOrName {
|
||||
continue
|
||||
}
|
||||
|
||||
autoconnect := true
|
||||
if ac, ok := connMeta["autoconnect"].(bool); ok {
|
||||
autoconnect = ac
|
||||
}
|
||||
|
||||
config := &VPNConfig{
|
||||
UUID: connUUID,
|
||||
Name: connID,
|
||||
Type: connType,
|
||||
Autoconnect: autoconnect,
|
||||
Data: make(map[string]string),
|
||||
}
|
||||
|
||||
if connType == "vpn" {
|
||||
if vpnSettings, ok := settings["vpn"]; ok {
|
||||
if svcType, ok := vpnSettings["service-type"].(string); ok {
|
||||
config.ServiceType = svcType
|
||||
}
|
||||
if dataMap, ok := vpnSettings["data"].(map[string]string); ok {
|
||||
for k, v := range dataMap {
|
||||
if !strings.Contains(strings.ToLower(k), "password") &&
|
||||
!strings.Contains(strings.ToLower(k), "secret") &&
|
||||
!strings.Contains(strings.ToLower(k), "key") {
|
||||
config.Data[k] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("VPN connection not found: %s", uuidOrName)
|
||||
}
|
||||
|
||||
func (b *NetworkManagerBackend) UpdateVPNConfig(connUUID string, updates map[string]interface{}) error {
|
||||
s := b.settings
|
||||
if s == nil {
|
||||
var err error
|
||||
s, err = gonetworkmanager.NewSettings()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get settings: %w", err)
|
||||
}
|
||||
b.settings = s
|
||||
}
|
||||
|
||||
settingsMgr := s.(gonetworkmanager.Settings)
|
||||
connections, err := settingsMgr.ListConnections()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get connections: %w", err)
|
||||
}
|
||||
|
||||
for _, conn := range connections {
|
||||
settings, err := conn.GetSettings()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
connMeta, ok := settings["connection"]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
connType, _ := connMeta["type"].(string)
|
||||
if connType != "vpn" && connType != "wireguard" {
|
||||
continue
|
||||
}
|
||||
|
||||
existingUUID, _ := connMeta["uuid"].(string)
|
||||
if existingUUID != connUUID {
|
||||
continue
|
||||
}
|
||||
|
||||
if name, ok := updates["name"].(string); ok && name != "" {
|
||||
connMeta["id"] = name
|
||||
}
|
||||
|
||||
if autoconnect, ok := updates["autoconnect"].(bool); ok {
|
||||
connMeta["autoconnect"] = autoconnect
|
||||
}
|
||||
|
||||
if data, ok := updates["data"].(map[string]interface{}); ok {
|
||||
if vpnSettings, ok := settings["vpn"]; ok {
|
||||
existingData, _ := vpnSettings["data"].(map[string]string)
|
||||
if existingData == nil {
|
||||
existingData = make(map[string]string)
|
||||
}
|
||||
for k, v := range data {
|
||||
if strVal, ok := v.(string); ok {
|
||||
existingData[k] = strVal
|
||||
}
|
||||
}
|
||||
vpnSettings["data"] = existingData
|
||||
}
|
||||
}
|
||||
|
||||
if ipv4, ok := settings["ipv4"]; ok {
|
||||
delete(ipv4, "addresses")
|
||||
delete(ipv4, "routes")
|
||||
delete(ipv4, "dns")
|
||||
}
|
||||
if ipv6, ok := settings["ipv6"]; ok {
|
||||
delete(ipv6, "addresses")
|
||||
delete(ipv6, "routes")
|
||||
delete(ipv6, "dns")
|
||||
}
|
||||
|
||||
if err := conn.Update(settings); err != nil {
|
||||
return fmt.Errorf("failed to update connection: %w", err)
|
||||
}
|
||||
|
||||
b.ListVPNProfiles()
|
||||
|
||||
if b.onStateChange != nil {
|
||||
b.onStateChange()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("VPN connection not found: %s", connUUID)
|
||||
}
|
||||
|
||||
func (b *NetworkManagerBackend) SetVPNCredentials(connUUID string, username string, password string, saveToKeyring bool) error {
|
||||
s := b.settings
|
||||
if s == nil {
|
||||
var err error
|
||||
s, err = gonetworkmanager.NewSettings()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get settings: %w", err)
|
||||
}
|
||||
b.settings = s
|
||||
}
|
||||
|
||||
settingsMgr := s.(gonetworkmanager.Settings)
|
||||
connections, err := settingsMgr.ListConnections()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get connections: %w", err)
|
||||
}
|
||||
|
||||
for _, conn := range connections {
|
||||
settings, err := conn.GetSettings()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
connMeta, ok := settings["connection"]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
connType, _ := connMeta["type"].(string)
|
||||
if connType != "vpn" && connType != "wireguard" {
|
||||
continue
|
||||
}
|
||||
|
||||
existingUUID, _ := connMeta["uuid"].(string)
|
||||
if existingUUID != connUUID {
|
||||
continue
|
||||
}
|
||||
|
||||
vpnSettings, ok := settings["vpn"]
|
||||
if !ok {
|
||||
vpnSettings = make(map[string]interface{})
|
||||
settings["vpn"] = vpnSettings
|
||||
}
|
||||
|
||||
existingData, _ := vpnSettings["data"].(map[string]string)
|
||||
if existingData == nil {
|
||||
existingData = make(map[string]string)
|
||||
}
|
||||
|
||||
if username != "" {
|
||||
existingData["username"] = username
|
||||
}
|
||||
|
||||
if saveToKeyring {
|
||||
existingData["password-flags"] = "0"
|
||||
} else {
|
||||
existingData["password-flags"] = "2"
|
||||
}
|
||||
|
||||
vpnSettings["data"] = existingData
|
||||
|
||||
if password != "" {
|
||||
secrets := make(map[string]string)
|
||||
secrets["password"] = password
|
||||
vpnSettings["secrets"] = secrets
|
||||
}
|
||||
|
||||
if ipv4, ok := settings["ipv4"]; ok {
|
||||
delete(ipv4, "addresses")
|
||||
delete(ipv4, "routes")
|
||||
delete(ipv4, "dns")
|
||||
}
|
||||
if ipv6, ok := settings["ipv6"]; ok {
|
||||
delete(ipv6, "addresses")
|
||||
delete(ipv6, "routes")
|
||||
delete(ipv6, "dns")
|
||||
}
|
||||
|
||||
if err := conn.Update(settings); err != nil {
|
||||
return fmt.Errorf("failed to update connection: %w", err)
|
||||
}
|
||||
|
||||
log.Infof("Updated VPN credentials for %s (save=%v)", connUUID, saveToKeyring)
|
||||
|
||||
if b.onStateChange != nil {
|
||||
b.onStateChange()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("VPN connection not found: %s", connUUID)
|
||||
}
|
||||
|
||||
func (b *NetworkManagerBackend) DeleteVPN(uuidOrName string) error {
|
||||
active, _ := b.ListActiveVPN()
|
||||
for _, vpn := range active {
|
||||
if vpn.UUID == uuidOrName || vpn.Name == uuidOrName {
|
||||
if err := b.DisconnectVPN(uuidOrName); err != nil {
|
||||
log.Warnf("Failed to disconnect VPN before deletion: %v", err)
|
||||
}
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
s := b.settings
|
||||
if s == nil {
|
||||
var err error
|
||||
s, err = gonetworkmanager.NewSettings()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get settings: %w", err)
|
||||
}
|
||||
b.settings = s
|
||||
}
|
||||
|
||||
settingsMgr := s.(gonetworkmanager.Settings)
|
||||
connections, err := settingsMgr.ListConnections()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get connections: %w", err)
|
||||
}
|
||||
|
||||
for _, conn := range connections {
|
||||
settings, err := conn.GetSettings()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
connMeta, ok := settings["connection"]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
connType, _ := connMeta["type"].(string)
|
||||
if connType != "vpn" && connType != "wireguard" {
|
||||
continue
|
||||
}
|
||||
|
||||
connID, _ := connMeta["id"].(string)
|
||||
connUUID, _ := connMeta["uuid"].(string)
|
||||
|
||||
if connUUID == uuidOrName || connID == uuidOrName {
|
||||
if err := conn.Delete(); err != nil {
|
||||
return fmt.Errorf("failed to delete VPN: %w", err)
|
||||
}
|
||||
|
||||
b.ListVPNProfiles()
|
||||
|
||||
if b.onStateChange != nil {
|
||||
b.onStateChange()
|
||||
}
|
||||
|
||||
log.Infof("Deleted VPN connection: %s (%s)", connID, connUUID)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("VPN connection not found: %s", uuidOrName)
|
||||
}
|
||||
|
||||
@@ -579,31 +579,59 @@ func (b *NetworkManagerBackend) createAndConnectWiFiOnDevice(req ConnectionReque
|
||||
"key-mgmt": "wpa-eap",
|
||||
}
|
||||
|
||||
eapMethod := "peap"
|
||||
if req.EAPMethod != "" {
|
||||
eapMethod = req.EAPMethod
|
||||
}
|
||||
|
||||
phase2Auth := "mschapv2"
|
||||
if req.Phase2Auth != "" {
|
||||
phase2Auth = req.Phase2Auth
|
||||
}
|
||||
|
||||
useSystemCACerts := false
|
||||
if req.UseSystemCACerts != nil {
|
||||
useSystemCACerts = *req.UseSystemCACerts
|
||||
}
|
||||
|
||||
x := map[string]interface{}{
|
||||
"eap": []string{"peap"},
|
||||
"phase2-auth": "mschapv2",
|
||||
"system-ca-certs": false,
|
||||
"eap": []string{eapMethod},
|
||||
"system-ca-certs": useSystemCACerts,
|
||||
"password-flags": uint32(0),
|
||||
}
|
||||
|
||||
switch eapMethod {
|
||||
case "peap", "ttls":
|
||||
x["phase2-auth"] = phase2Auth
|
||||
case "tls":
|
||||
if req.ClientCertPath != "" {
|
||||
x["client-cert"] = []byte("file://" + req.ClientCertPath)
|
||||
}
|
||||
if req.PrivateKeyPath != "" {
|
||||
x["private-key"] = []byte("file://" + req.PrivateKeyPath)
|
||||
}
|
||||
}
|
||||
|
||||
if req.Username != "" {
|
||||
x["identity"] = req.Username
|
||||
}
|
||||
if req.Password != "" {
|
||||
x["password"] = req.Password
|
||||
}
|
||||
|
||||
if req.AnonymousIdentity != "" {
|
||||
x["anonymous-identity"] = req.AnonymousIdentity
|
||||
}
|
||||
if req.DomainSuffixMatch != "" {
|
||||
x["domain-suffix-match"] = req.DomainSuffixMatch
|
||||
}
|
||||
if req.CACertPath != "" {
|
||||
x["ca-cert"] = []byte("file://" + req.CACertPath)
|
||||
}
|
||||
|
||||
settings["802-1x"] = x
|
||||
|
||||
log.Infof("[createAndConnectWiFi] WPA-EAP settings: eap=peap, phase2-auth=mschapv2, identity=%s, interactive=%v, system-ca-certs=%v, domain-suffix-match=%q",
|
||||
req.Username, req.Interactive, x["system-ca-certs"], req.DomainSuffixMatch)
|
||||
log.Infof("[createAndConnectWiFi] WPA-EAP settings: eap=%s, phase2-auth=%s, identity=%s, interactive=%v, system-ca-certs=%v, domain-suffix-match=%q",
|
||||
eapMethod, phase2Auth, req.Username, req.Interactive, useSystemCACerts, req.DomainSuffixMatch)
|
||||
|
||||
case isPsk:
|
||||
sec := map[string]interface{}{
|
||||
|
||||
@@ -70,6 +70,18 @@ func HandleRequest(conn net.Conn, req Request, manager *Manager) {
|
||||
handleDisconnectAllVPN(conn, req, manager)
|
||||
case "network.vpn.clearCredentials":
|
||||
handleClearVPNCredentials(conn, req, manager)
|
||||
case "network.vpn.plugins":
|
||||
handleListVPNPlugins(conn, req, manager)
|
||||
case "network.vpn.import":
|
||||
handleImportVPN(conn, req, manager)
|
||||
case "network.vpn.getConfig":
|
||||
handleGetVPNConfig(conn, req, manager)
|
||||
case "network.vpn.updateConfig":
|
||||
handleUpdateVPNConfig(conn, req, manager)
|
||||
case "network.vpn.delete":
|
||||
handleDeleteVPN(conn, req, manager)
|
||||
case "network.vpn.setCredentials":
|
||||
handleSetVPNCredentials(conn, req, manager)
|
||||
case "network.wifi.setAutoconnect":
|
||||
handleSetWiFiAutoconnect(conn, req, manager)
|
||||
default:
|
||||
@@ -200,6 +212,24 @@ func handleConnectWiFi(conn net.Conn, req Request, manager *Manager) {
|
||||
if domainSuffixMatch, ok := req.Params["domainSuffixMatch"].(string); ok {
|
||||
connReq.DomainSuffixMatch = domainSuffixMatch
|
||||
}
|
||||
if eapMethod, ok := req.Params["eapMethod"].(string); ok {
|
||||
connReq.EAPMethod = eapMethod
|
||||
}
|
||||
if phase2Auth, ok := req.Params["phase2Auth"].(string); ok {
|
||||
connReq.Phase2Auth = phase2Auth
|
||||
}
|
||||
if caCertPath, ok := req.Params["caCertPath"].(string); ok {
|
||||
connReq.CACertPath = caCertPath
|
||||
}
|
||||
if clientCertPath, ok := req.Params["clientCertPath"].(string); ok {
|
||||
connReq.ClientCertPath = clientCertPath
|
||||
}
|
||||
if privateKeyPath, ok := req.Params["privateKeyPath"].(string); ok {
|
||||
connReq.PrivateKeyPath = privateKeyPath
|
||||
}
|
||||
if useSystemCACerts, ok := req.Params["useSystemCACerts"].(bool); ok {
|
||||
connReq.UseSystemCACerts = &useSystemCACerts
|
||||
}
|
||||
|
||||
if err := manager.ConnectWiFi(connReq); err != nil {
|
||||
models.RespondError(conn, req.ID, err.Error())
|
||||
@@ -287,7 +317,14 @@ func handleConnectEthernet(conn net.Conn, req Request, manager *Manager) {
|
||||
}
|
||||
|
||||
func handleDisconnectEthernet(conn net.Conn, req Request, manager *Manager) {
|
||||
if err := manager.DisconnectEthernet(); err != nil {
|
||||
device, _ := req.Params["device"].(string)
|
||||
var err error
|
||||
if device != "" {
|
||||
err = manager.DisconnectEthernetDevice(device)
|
||||
} else {
|
||||
err = manager.DisconnectEthernet()
|
||||
}
|
||||
if err != nil {
|
||||
models.RespondError(conn, req.ID, err.Error())
|
||||
return
|
||||
}
|
||||
@@ -502,3 +539,138 @@ func handleSetWiFiAutoconnect(conn net.Conn, req Request, manager *Manager) {
|
||||
|
||||
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "autoconnect updated"})
|
||||
}
|
||||
|
||||
func handleListVPNPlugins(conn net.Conn, req Request, manager *Manager) {
|
||||
plugins, err := manager.ListVPNPlugins()
|
||||
if err != nil {
|
||||
log.Warnf("handleListVPNPlugins: failed to list plugins: %v", err)
|
||||
models.RespondError(conn, req.ID, fmt.Sprintf("failed to list VPN plugins: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
models.Respond(conn, req.ID, plugins)
|
||||
}
|
||||
|
||||
func handleImportVPN(conn net.Conn, req Request, manager *Manager) {
|
||||
filePath, ok := req.Params["file"].(string)
|
||||
if !ok {
|
||||
filePath, ok = req.Params["path"].(string)
|
||||
}
|
||||
if !ok {
|
||||
models.RespondError(conn, req.ID, "missing 'file' or 'path' parameter")
|
||||
return
|
||||
}
|
||||
|
||||
name, _ := req.Params["name"].(string)
|
||||
|
||||
result, err := manager.ImportVPN(filePath, name)
|
||||
if err != nil {
|
||||
log.Warnf("handleImportVPN: failed to import: %v", err)
|
||||
models.RespondError(conn, req.ID, fmt.Sprintf("failed to import VPN: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
models.Respond(conn, req.ID, result)
|
||||
}
|
||||
|
||||
func handleGetVPNConfig(conn net.Conn, req Request, manager *Manager) {
|
||||
uuidOrName, ok := req.Params["uuid"].(string)
|
||||
if !ok {
|
||||
uuidOrName, ok = req.Params["name"].(string)
|
||||
}
|
||||
if !ok {
|
||||
uuidOrName, ok = req.Params["uuidOrName"].(string)
|
||||
}
|
||||
if !ok {
|
||||
models.RespondError(conn, req.ID, "missing 'uuid', 'name', or 'uuidOrName' parameter")
|
||||
return
|
||||
}
|
||||
|
||||
config, err := manager.GetVPNConfig(uuidOrName)
|
||||
if err != nil {
|
||||
log.Warnf("handleGetVPNConfig: failed to get config: %v", err)
|
||||
models.RespondError(conn, req.ID, fmt.Sprintf("failed to get VPN config: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
models.Respond(conn, req.ID, config)
|
||||
}
|
||||
|
||||
func handleUpdateVPNConfig(conn net.Conn, req Request, manager *Manager) {
|
||||
connUUID, ok := req.Params["uuid"].(string)
|
||||
if !ok {
|
||||
models.RespondError(conn, req.ID, "missing 'uuid' parameter")
|
||||
return
|
||||
}
|
||||
|
||||
updates := make(map[string]interface{})
|
||||
|
||||
if name, ok := req.Params["name"].(string); ok {
|
||||
updates["name"] = name
|
||||
}
|
||||
if autoconnect, ok := req.Params["autoconnect"].(bool); ok {
|
||||
updates["autoconnect"] = autoconnect
|
||||
}
|
||||
if data, ok := req.Params["data"].(map[string]interface{}); ok {
|
||||
updates["data"] = data
|
||||
}
|
||||
|
||||
if len(updates) == 0 {
|
||||
models.RespondError(conn, req.ID, "no updates provided")
|
||||
return
|
||||
}
|
||||
|
||||
if err := manager.UpdateVPNConfig(connUUID, updates); err != nil {
|
||||
log.Warnf("handleUpdateVPNConfig: failed to update: %v", err)
|
||||
models.RespondError(conn, req.ID, fmt.Sprintf("failed to update VPN config: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "VPN config updated"})
|
||||
}
|
||||
|
||||
func handleDeleteVPN(conn net.Conn, req Request, manager *Manager) {
|
||||
uuidOrName, ok := req.Params["uuid"].(string)
|
||||
if !ok {
|
||||
uuidOrName, ok = req.Params["name"].(string)
|
||||
}
|
||||
if !ok {
|
||||
uuidOrName, ok = req.Params["uuidOrName"].(string)
|
||||
}
|
||||
if !ok {
|
||||
models.RespondError(conn, req.ID, "missing 'uuid', 'name', or 'uuidOrName' parameter")
|
||||
return
|
||||
}
|
||||
|
||||
if err := manager.DeleteVPN(uuidOrName); err != nil {
|
||||
log.Warnf("handleDeleteVPN: failed to delete: %v", err)
|
||||
models.RespondError(conn, req.ID, fmt.Sprintf("failed to delete VPN: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "VPN deleted"})
|
||||
}
|
||||
|
||||
func handleSetVPNCredentials(conn net.Conn, req Request, manager *Manager) {
|
||||
connUUID, ok := req.Params["uuid"].(string)
|
||||
if !ok {
|
||||
models.RespondError(conn, req.ID, "missing 'uuid' parameter")
|
||||
return
|
||||
}
|
||||
|
||||
username, _ := req.Params["username"].(string)
|
||||
password, _ := req.Params["password"].(string)
|
||||
|
||||
save := true
|
||||
if saveParam, ok := req.Params["save"].(bool); ok {
|
||||
save = saveParam
|
||||
}
|
||||
|
||||
if err := manager.SetVPNCredentials(connUUID, username, password, save); err != nil {
|
||||
log.Warnf("handleSetVPNCredentials: failed to set credentials: %v", err)
|
||||
models.RespondError(conn, req.ID, fmt.Sprintf("failed to set VPN credentials: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "VPN credentials set"})
|
||||
}
|
||||
|
||||
@@ -109,6 +109,7 @@ func (m *Manager) syncStateFromBackend() error {
|
||||
m.state.EthernetDevice = backendState.EthernetDevice
|
||||
m.state.EthernetConnected = backendState.EthernetConnected
|
||||
m.state.EthernetConnectionUuid = backendState.EthernetConnectionUuid
|
||||
m.state.EthernetDevices = backendState.EthernetDevices
|
||||
m.state.WiFiIP = backendState.WiFiIP
|
||||
m.state.WiFiDevice = backendState.WiFiDevice
|
||||
m.state.WiFiConnected = backendState.WiFiConnected
|
||||
@@ -155,6 +156,7 @@ func (m *Manager) snapshotState() NetworkState {
|
||||
s.WiFiNetworks = append([]WiFiNetwork(nil), m.state.WiFiNetworks...)
|
||||
s.WiFiDevices = append([]WiFiDevice(nil), m.state.WiFiDevices...)
|
||||
s.WiredConnections = append([]WiredConnection(nil), m.state.WiredConnections...)
|
||||
s.EthernetDevices = append([]EthernetDevice(nil), m.state.EthernetDevices...)
|
||||
s.VPNProfiles = append([]VPNProfile(nil), m.state.VPNProfiles...)
|
||||
s.VPNActive = append([]VPNActive(nil), m.state.VPNActive...)
|
||||
return s
|
||||
@@ -213,6 +215,9 @@ func stateChangedMeaningfully(old, new *NetworkState) bool {
|
||||
if len(old.WiredConnections) != len(new.WiredConnections) {
|
||||
return true
|
||||
}
|
||||
if len(old.EthernetDevices) != len(new.EthernetDevices) {
|
||||
return true
|
||||
}
|
||||
|
||||
for i := range old.WiFiNetworks {
|
||||
oldNet := &old.WiFiNetworks[i]
|
||||
@@ -242,6 +247,23 @@ func stateChangedMeaningfully(old, new *NetworkState) bool {
|
||||
}
|
||||
}
|
||||
|
||||
for i := range old.EthernetDevices {
|
||||
oldDev := &old.EthernetDevices[i]
|
||||
newDev := &new.EthernetDevices[i]
|
||||
if oldDev.Name != newDev.Name {
|
||||
return true
|
||||
}
|
||||
if oldDev.Connected != newDev.Connected {
|
||||
return true
|
||||
}
|
||||
if oldDev.State != newDev.State {
|
||||
return true
|
||||
}
|
||||
if oldDev.IP != newDev.IP {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// Check VPN profiles count
|
||||
if len(old.VPNProfiles) != len(new.VPNProfiles) {
|
||||
return true
|
||||
@@ -480,6 +502,18 @@ func (m *Manager) DisconnectEthernet() error {
|
||||
return m.backend.DisconnectEthernet()
|
||||
}
|
||||
|
||||
func (m *Manager) DisconnectEthernetDevice(device string) error {
|
||||
return m.backend.DisconnectEthernetDevice(device)
|
||||
}
|
||||
|
||||
func (m *Manager) GetEthernetDevices() []EthernetDevice {
|
||||
m.stateMutex.RLock()
|
||||
defer m.stateMutex.RUnlock()
|
||||
devices := make([]EthernetDevice, len(m.state.EthernetDevices))
|
||||
copy(devices, m.state.EthernetDevices)
|
||||
return devices
|
||||
}
|
||||
|
||||
func (m *Manager) activateConnection(uuid string) error {
|
||||
return m.backend.ActivateWiredConnection(uuid)
|
||||
}
|
||||
@@ -508,6 +542,30 @@ func (m *Manager) ClearVPNCredentials(uuidOrName string) error {
|
||||
return m.backend.ClearVPNCredentials(uuidOrName)
|
||||
}
|
||||
|
||||
func (m *Manager) ListVPNPlugins() ([]VPNPlugin, error) {
|
||||
return m.backend.ListVPNPlugins()
|
||||
}
|
||||
|
||||
func (m *Manager) ImportVPN(filePath string, name string) (*VPNImportResult, error) {
|
||||
return m.backend.ImportVPN(filePath, name)
|
||||
}
|
||||
|
||||
func (m *Manager) GetVPNConfig(uuidOrName string) (*VPNConfig, error) {
|
||||
return m.backend.GetVPNConfig(uuidOrName)
|
||||
}
|
||||
|
||||
func (m *Manager) UpdateVPNConfig(uuid string, updates map[string]interface{}) error {
|
||||
return m.backend.UpdateVPNConfig(uuid, updates)
|
||||
}
|
||||
|
||||
func (m *Manager) DeleteVPN(uuidOrName string) error {
|
||||
return m.backend.DeleteVPN(uuidOrName)
|
||||
}
|
||||
|
||||
func (m *Manager) SetVPNCredentials(uuid, username, password string, save bool) error {
|
||||
return m.backend.SetVPNCredentials(uuid, username, password, save)
|
||||
}
|
||||
|
||||
func (m *Manager) SetWiFiAutoconnect(ssid string, autoconnect bool) error {
|
||||
return m.backend.SetWiFiAutoconnect(ssid, autoconnect)
|
||||
}
|
||||
|
||||
@@ -49,6 +49,7 @@ func (b *SubscriptionBroker) Ask(ctx context.Context, req PromptRequest) (string
|
||||
VpnService: req.VpnService,
|
||||
Setting: req.SettingName,
|
||||
Fields: req.Fields,
|
||||
FieldsInfo: req.FieldsInfo,
|
||||
Hints: req.Hints,
|
||||
Reason: req.Reason,
|
||||
ConnectionId: req.ConnectionId,
|
||||
|
||||
@@ -52,20 +52,40 @@ type WiFiDevice struct {
|
||||
Networks []WiFiNetwork `json:"networks"`
|
||||
}
|
||||
|
||||
type EthernetDevice struct {
|
||||
Name string `json:"name"`
|
||||
HwAddress string `json:"hwAddress"`
|
||||
State string `json:"state"`
|
||||
Connected bool `json:"connected"`
|
||||
IP string `json:"ip,omitempty"`
|
||||
Speed uint32 `json:"speed,omitempty"`
|
||||
Driver string `json:"driver,omitempty"`
|
||||
}
|
||||
|
||||
type VPNProfile struct {
|
||||
Name string `json:"name"`
|
||||
UUID string `json:"uuid"`
|
||||
Type string `json:"type"`
|
||||
ServiceType string `json:"serviceType"`
|
||||
Name string `json:"name"`
|
||||
UUID string `json:"uuid"`
|
||||
Type string `json:"type"`
|
||||
ServiceType string `json:"serviceType"`
|
||||
RemoteHost string `json:"remoteHost,omitempty"`
|
||||
Username string `json:"username,omitempty"`
|
||||
Autoconnect bool `json:"autoconnect"`
|
||||
Data map[string]string `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
type VPNActive struct {
|
||||
Name string `json:"name"`
|
||||
UUID string `json:"uuid"`
|
||||
Device string `json:"device,omitempty"`
|
||||
State string `json:"state,omitempty"`
|
||||
Type string `json:"type"`
|
||||
Plugin string `json:"serviceType"`
|
||||
Name string `json:"name"`
|
||||
UUID string `json:"uuid"`
|
||||
Device string `json:"device,omitempty"`
|
||||
State string `json:"state,omitempty"`
|
||||
Type string `json:"type"`
|
||||
Plugin string `json:"serviceType"`
|
||||
IP string `json:"ip,omitempty"`
|
||||
Gateway string `json:"gateway,omitempty"`
|
||||
RemoteHost string `json:"remoteHost,omitempty"`
|
||||
Username string `json:"username,omitempty"`
|
||||
MTU uint32 `json:"mtu,omitempty"`
|
||||
Data map[string]string `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
type VPNState struct {
|
||||
@@ -81,6 +101,7 @@ type NetworkState struct {
|
||||
EthernetDevice string `json:"ethernetDevice"`
|
||||
EthernetConnected bool `json:"ethernetConnected"`
|
||||
EthernetConnectionUuid string `json:"ethernetConnectionUuid"`
|
||||
EthernetDevices []EthernetDevice `json:"ethernetDevices"`
|
||||
WiFiIP string `json:"wifiIP"`
|
||||
WiFiDevice string `json:"wifiDevice"`
|
||||
WiFiConnected bool `json:"wifiConnected"`
|
||||
@@ -107,6 +128,12 @@ type ConnectionRequest struct {
|
||||
DomainSuffixMatch string `json:"domainSuffixMatch,omitempty"`
|
||||
Interactive bool `json:"interactive,omitempty"`
|
||||
Device string `json:"device,omitempty"`
|
||||
EAPMethod string `json:"eapMethod,omitempty"`
|
||||
Phase2Auth string `json:"phase2Auth,omitempty"`
|
||||
CACertPath string `json:"caCertPath,omitempty"`
|
||||
ClientCertPath string `json:"clientCertPath,omitempty"`
|
||||
PrivateKeyPath string `json:"privateKeyPath,omitempty"`
|
||||
UseSystemCACerts *bool `json:"useSystemCACerts,omitempty"`
|
||||
}
|
||||
|
||||
type WiredConnection struct {
|
||||
@@ -150,17 +177,18 @@ type NetworkEvent struct {
|
||||
}
|
||||
|
||||
type PromptRequest struct {
|
||||
Name string `json:"name"`
|
||||
SSID string `json:"ssid"`
|
||||
ConnType string `json:"connType"`
|
||||
VpnService string `json:"vpnService"`
|
||||
SettingName string `json:"setting"`
|
||||
Fields []string `json:"fields"`
|
||||
Hints []string `json:"hints"`
|
||||
Reason string `json:"reason"`
|
||||
ConnectionId string `json:"connectionId"`
|
||||
ConnectionUuid string `json:"connectionUuid"`
|
||||
ConnectionPath string `json:"connectionPath"`
|
||||
Name string `json:"name"`
|
||||
SSID string `json:"ssid"`
|
||||
ConnType string `json:"connType"`
|
||||
VpnService string `json:"vpnService"`
|
||||
SettingName string `json:"setting"`
|
||||
Fields []string `json:"fields"`
|
||||
FieldsInfo []FieldInfo `json:"fieldsInfo"`
|
||||
Hints []string `json:"hints"`
|
||||
Reason string `json:"reason"`
|
||||
ConnectionId string `json:"connectionId"`
|
||||
ConnectionUuid string `json:"connectionUuid"`
|
||||
ConnectionPath string `json:"connectionPath"`
|
||||
}
|
||||
|
||||
type PromptReply struct {
|
||||
@@ -169,18 +197,25 @@ type PromptReply struct {
|
||||
Cancel bool `json:"cancel"`
|
||||
}
|
||||
|
||||
type FieldInfo struct {
|
||||
Name string `json:"name"`
|
||||
Label string `json:"label"`
|
||||
IsSecret bool `json:"isSecret"`
|
||||
}
|
||||
|
||||
type CredentialPrompt struct {
|
||||
Token string `json:"token"`
|
||||
Name string `json:"name"`
|
||||
SSID string `json:"ssid"`
|
||||
ConnType string `json:"connType"`
|
||||
VpnService string `json:"vpnService"`
|
||||
Setting string `json:"setting"`
|
||||
Fields []string `json:"fields"`
|
||||
Hints []string `json:"hints"`
|
||||
Reason string `json:"reason"`
|
||||
ConnectionId string `json:"connectionId"`
|
||||
ConnectionUuid string `json:"connectionUuid"`
|
||||
Token string `json:"token"`
|
||||
Name string `json:"name"`
|
||||
SSID string `json:"ssid"`
|
||||
ConnType string `json:"connType"`
|
||||
VpnService string `json:"vpnService"`
|
||||
Setting string `json:"setting"`
|
||||
Fields []string `json:"fields"`
|
||||
FieldsInfo []FieldInfo `json:"fieldsInfo"`
|
||||
Hints []string `json:"hints"`
|
||||
Reason string `json:"reason"`
|
||||
ConnectionId string `json:"connectionId"`
|
||||
ConnectionUuid string `json:"connectionUuid"`
|
||||
}
|
||||
|
||||
type NetworkInfoResponse struct {
|
||||
@@ -203,3 +238,28 @@ type WiredIPConfig struct {
|
||||
Gateway string `json:"gateway"`
|
||||
DNS string `json:"dns"`
|
||||
}
|
||||
|
||||
type VPNPlugin struct {
|
||||
Name string `json:"name"`
|
||||
ServiceType string `json:"serviceType"`
|
||||
Program string `json:"program,omitempty"`
|
||||
Supports []string `json:"supports,omitempty"`
|
||||
FileExtensions []string `json:"fileExtensions"`
|
||||
}
|
||||
|
||||
type VPNConfig struct {
|
||||
UUID string `json:"uuid"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
ServiceType string `json:"serviceType,omitempty"`
|
||||
Autoconnect bool `json:"autoconnect"`
|
||||
Data map[string]string `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
type VPNImportResult struct {
|
||||
Success bool `json:"success"`
|
||||
UUID string `json:"uuid,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
ServiceType string `json:"serviceType,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
@@ -21,3 +21,31 @@ func TestManager_GetWiredConfigs(t *testing.T) {
|
||||
assert.Len(t, configs, 1)
|
||||
assert.Equal(t, "Test", configs[0].ID)
|
||||
}
|
||||
|
||||
func TestManager_GetEthernetDevices(t *testing.T) {
|
||||
manager := &Manager{
|
||||
state: &NetworkState{
|
||||
EthernetDevices: []EthernetDevice{
|
||||
{Name: "enp0s3", Connected: true, IP: "192.168.1.100"},
|
||||
{Name: "enp0s8", Connected: false},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
devices := manager.GetEthernetDevices()
|
||||
|
||||
assert.Len(t, devices, 2)
|
||||
assert.Equal(t, "enp0s3", devices[0].Name)
|
||||
assert.True(t, devices[0].Connected)
|
||||
assert.Equal(t, "enp0s8", devices[1].Name)
|
||||
assert.False(t, devices[1].Connected)
|
||||
}
|
||||
|
||||
func TestManager_GetEthernetDevices_Empty(t *testing.T) {
|
||||
manager := &Manager{
|
||||
state: &NetworkState{},
|
||||
}
|
||||
|
||||
devices := manager.GetEthernetDevices()
|
||||
assert.Empty(t, devices)
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ import (
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/pkg/syncmap"
|
||||
)
|
||||
|
||||
const APIVersion = 20
|
||||
const APIVersion = 21
|
||||
|
||||
type Capabilities struct {
|
||||
Capabilities []string `json:"capabilities"`
|
||||
@@ -1073,7 +1073,7 @@ func Start(printDocs bool) error {
|
||||
log.Info(" network.getState - Get current network state")
|
||||
log.Info(" network.wifi.scan - Scan for WiFi networks (params: device?)")
|
||||
log.Info(" network.wifi.networks - Get WiFi network list")
|
||||
log.Info(" network.wifi.connect - Connect to WiFi (params: ssid, password?, username?, device?)")
|
||||
log.Info(" network.wifi.connect - Connect to WiFi (params: ssid, password?, username?, device?, eapMethod?, phase2Auth?, caCertPath?, clientCertPath?, privateKeyPath?, useSystemCACerts?)")
|
||||
log.Info(" network.wifi.disconnect - Disconnect WiFi (params: device?)")
|
||||
log.Info(" network.wifi.forget - Forget network (params: ssid)")
|
||||
log.Info(" network.wifi.toggle - Toggle WiFi radio")
|
||||
@@ -1089,6 +1089,11 @@ func Start(printDocs bool) error {
|
||||
log.Info(" network.vpn.disconnect - Disconnect VPN (params: uuidOrName|name|uuid)")
|
||||
log.Info(" network.vpn.disconnectAll - Disconnect all VPNs")
|
||||
log.Info(" network.vpn.clearCredentials - Clear saved VPN credentials (params: uuidOrName|name|uuid)")
|
||||
log.Info(" network.vpn.plugins - List available VPN plugins")
|
||||
log.Info(" network.vpn.import - Import VPN from file (params: file|path, name?)")
|
||||
log.Info(" network.vpn.getConfig - Get VPN configuration (params: uuid|name|uuidOrName)")
|
||||
log.Info(" network.vpn.updateConfig - Update VPN configuration (params: uuid, name?, autoconnect?, data?)")
|
||||
log.Info(" network.vpn.delete - Delete VPN connection (params: uuid|name|uuidOrName)")
|
||||
log.Info(" network.preference.set - Set preference (params: preference [auto|wifi|ethernet])")
|
||||
log.Info(" network.info - Get network info (params: ssid)")
|
||||
log.Info(" network.credentials.submit - Submit credentials for prompt (params: token, secrets, save?)")
|
||||
|
||||
Reference in New Issue
Block a user