chore: use interfaces for all other services

This commit is contained in:
Christoph Haas
2025-03-23 23:09:47 +01:00
parent 02ed7b19df
commit 7d0da4e7ad
40 changed files with 1337 additions and 406 deletions

View File

@@ -1,11 +1,5 @@
package domain
import (
"context"
"golang.org/x/oauth2"
)
type LoginProvider string
type LoginProviderInfo struct {
@@ -24,28 +18,3 @@ type AuthenticatorUserInfo struct {
Department string
IsAdmin bool
}
type AuthenticatorType string
const (
AuthenticatorTypeOAuth AuthenticatorType = "oauth"
AuthenticatorTypeOidc AuthenticatorType = "oidc"
)
type OauthAuthenticator interface {
GetName() string
GetType() AuthenticatorType
AuthCodeURL(state string, opts ...oauth2.AuthCodeOption) string
Exchange(ctx context.Context, code string, opts ...oauth2.AuthCodeOption) (*oauth2.Token, error)
GetUserInfo(ctx context.Context, token *oauth2.Token, nonce string) (map[string]any, error)
ParseUserInfo(raw map[string]any) (*AuthenticatorUserInfo, error)
RegistrationEnabled() bool
}
type LdapAuthenticator interface {
GetName() string
PlaintextAuthentication(userId UserIdentifier, plainPassword string) error
GetUserInfo(ctx context.Context, username UserIdentifier) (map[string]any, error)
ParseUserInfo(raw map[string]any) (*AuthenticatorUserInfo, error)
RegistrationEnabled() bool
}

View File

@@ -33,6 +33,7 @@ func (p KeyPair) GetPublicKey() wgtypes.Key {
type PreSharedKey string
// NewFreshKeypair generates a new key pair.
func NewFreshKeypair() (KeyPair, error) {
privateKey, err := wgtypes.GeneratePrivateKey()
if err != nil {
@@ -45,6 +46,7 @@ func NewFreshKeypair() (KeyPair, error) {
}, nil
}
// NewPreSharedKey generates a new pre-shared key.
func NewPreSharedKey() (PreSharedKey, error) {
preSharedKey, err := wgtypes.GenerateKey()
if err != nil {
@@ -54,6 +56,8 @@ func NewPreSharedKey() (PreSharedKey, error) {
return PreSharedKey(preSharedKey.String()), nil
}
// PublicKeyFromPrivateKey returns the public key for a given private key.
// If the private key is invalid, an empty string is returned.
func PublicKeyFromPrivateKey(key string) string {
privKey, err := wgtypes.ParseKey(key)
if err != nil {

View File

@@ -0,0 +1,56 @@
package domain
import (
"encoding/base64"
"testing"
"github.com/stretchr/testify/assert"
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
)
func TestKeyPair_GetPrivateKeyBytesReturnsCorrectBytes(t *testing.T) {
keyPair := KeyPair{PrivateKey: base64.StdEncoding.EncodeToString([]byte("privateKey"))}
expected := []byte("privateKey")
assert.Equal(t, expected, keyPair.GetPrivateKeyBytes())
}
func TestKeyPair_GetPublicKeyBytesReturnsCorrectBytes(t *testing.T) {
keyPair := KeyPair{PublicKey: base64.StdEncoding.EncodeToString([]byte("publicKey"))}
expected := []byte("publicKey")
assert.Equal(t, expected, keyPair.GetPublicKeyBytes())
}
func TestKeyPair_GetPrivateKeyReturnsCorrectKey(t *testing.T) {
privateKey, _ := wgtypes.GeneratePrivateKey()
keyPair := KeyPair{PrivateKey: privateKey.String()}
assert.Equal(t, privateKey, keyPair.GetPrivateKey())
}
func TestKeyPair_GetPublicKeyReturnsCorrectKey(t *testing.T) {
privateKey, _ := wgtypes.GeneratePrivateKey()
keyPair := KeyPair{PublicKey: privateKey.PublicKey().String()}
assert.Equal(t, privateKey.PublicKey(), keyPair.GetPublicKey())
}
func TestNewFreshKeypairGeneratesValidKeypair(t *testing.T) {
keyPair, err := NewFreshKeypair()
assert.NoError(t, err)
assert.NotEmpty(t, keyPair.PrivateKey)
assert.NotEmpty(t, keyPair.PublicKey)
}
func TestNewPreSharedKeyGeneratesValidKey(t *testing.T) {
preSharedKey, err := NewPreSharedKey()
assert.NoError(t, err)
assert.NotEmpty(t, preSharedKey)
}
func TestPublicKeyFromPrivateKeyReturnsCorrectPublicKey(t *testing.T) {
privateKey, _ := wgtypes.GeneratePrivateKey()
expected := privateKey.PublicKey().String()
assert.Equal(t, expected, PublicKeyFromPrivateKey(privateKey.String()))
}
func TestPublicKeyFromPrivateKeyReturnsEmptyStringOnInvalidKey(t *testing.T) {
assert.Equal(t, "", PublicKeyFromPrivateKey("invalidKey"))
}

View File

@@ -0,0 +1,83 @@
package domain
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestInterface_IsDisabledReturnsTrueWhenDisabled(t *testing.T) {
iface := &Interface{}
assert.False(t, iface.IsDisabled())
now := time.Now()
iface.Disabled = &now
assert.True(t, iface.IsDisabled())
}
func TestInterface_AddressStrReturnsCorrectString(t *testing.T) {
iface := &Interface{
Addresses: []Cidr{
{Cidr: "192.168.1.1/24", Addr: "192.168.1.1", NetLength: 24},
{Cidr: "10.0.0.1/24", Addr: "10.0.0.1", NetLength: 24},
},
}
expected := "192.168.1.1/24,10.0.0.1/24"
assert.Equal(t, expected, iface.AddressStr())
}
func TestInterface_GetConfigFileNameReturnsCorrectFileName(t *testing.T) {
iface := &Interface{Identifier: "wg0"}
expected := "wg0.conf"
assert.Equal(t, expected, iface.GetConfigFileName())
iface.Identifier = "wg0@123"
expected = "wg0123.conf"
assert.Equal(t, expected, iface.GetConfigFileName())
}
func TestInterface_GetAllowedIPsReturnsCorrectCidrs(t *testing.T) {
peer1 := Peer{
Interface: PeerInterfaceConfig{
Addresses: []Cidr{
{Cidr: "192.168.1.2/32", Addr: "192.168.1.2", NetLength: 32},
},
},
}
peer2 := Peer{
Interface: PeerInterfaceConfig{
Addresses: []Cidr{
{Cidr: "10.0.0.2/32", Addr: "10.0.0.2", NetLength: 32},
},
},
}
iface := &Interface{}
expected := []Cidr{
{Cidr: "192.168.1.2/32", Addr: "192.168.1.2", NetLength: 32},
{Cidr: "10.0.0.2/32", Addr: "10.0.0.2", NetLength: 32},
}
assert.Equal(t, expected, iface.GetAllowedIPs([]Peer{peer1, peer2}))
}
func TestInterface_ManageRoutingTableReturnsCorrectValue(t *testing.T) {
iface := &Interface{RoutingTable: "off"}
assert.False(t, iface.ManageRoutingTable())
iface.RoutingTable = "100"
assert.True(t, iface.ManageRoutingTable())
}
func TestInterface_GetRoutingTableReturnsCorrectValue(t *testing.T) {
iface := &Interface{RoutingTable: ""}
assert.Equal(t, 0, iface.GetRoutingTable())
iface.RoutingTable = "off"
assert.Equal(t, -1, iface.GetRoutingTable())
iface.RoutingTable = "0x64"
assert.Equal(t, 100, iface.GetRoutingTable())
iface.RoutingTable = "200"
assert.Equal(t, 200, iface.GetRoutingTable())
}

View File

@@ -0,0 +1,42 @@
package domain
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestConfigOption_GetValueReturnsCorrectValue(t *testing.T) {
option := ConfigOption[int]{Value: 42}
assert.Equal(t, 42, option.GetValue())
}
func TestConfigOption_SetValueUpdatesValue(t *testing.T) {
option := ConfigOption[int]{Value: 42}
option.SetValue(100)
assert.Equal(t, 100, option.GetValue())
}
func TestConfigOption_TrySetValueUpdatesValueWhenOverridable(t *testing.T) {
option := ConfigOption[int]{Value: 42, Overridable: true}
result := option.TrySetValue(100)
assert.True(t, result)
assert.Equal(t, 100, option.GetValue())
}
func TestConfigOption_TrySetValueDoesNotUpdateValueWhenNotOverridable(t *testing.T) {
option := ConfigOption[int]{Value: 42, Overridable: false}
result := option.TrySetValue(100)
assert.False(t, result)
assert.Equal(t, 42, option.GetValue())
}
func TestNewConfigOptionCreatesCorrectOption(t *testing.T) {
option := NewConfigOption(42, true)
assert.Equal(t, 42, option.GetValue())
assert.True(t, option.Overridable)
option2 := NewConfigOption("str", false)
assert.Equal(t, "str", option2.GetValue())
assert.False(t, option2.Overridable)
}

View File

@@ -0,0 +1,165 @@
package domain
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestPeer_IsDisabled(t *testing.T) {
peer := &Peer{}
assert.False(t, peer.IsDisabled())
now := time.Now()
peer.Disabled = &now
assert.True(t, peer.IsDisabled())
}
func TestPeer_IsExpired(t *testing.T) {
peer := &Peer{}
assert.False(t, peer.IsExpired())
expiredTime := time.Now().Add(-time.Hour)
peer.ExpiresAt = &expiredTime
assert.True(t, peer.IsExpired())
futureTime := time.Now().Add(time.Hour)
peer.ExpiresAt = &futureTime
assert.False(t, peer.IsExpired())
}
func TestPeer_CheckAliveAddress(t *testing.T) {
peer := &Peer{}
assert.Equal(t, "", peer.CheckAliveAddress())
peer.Interface.CheckAliveAddress = "192.168.1.1"
assert.Equal(t, "192.168.1.1", peer.CheckAliveAddress())
peer.Interface.CheckAliveAddress = ""
peer.Interface.Addresses = []Cidr{{Addr: "10.0.0.1"}}
assert.Equal(t, "10.0.0.1", peer.CheckAliveAddress())
}
func TestPeer_GetConfigFileName(t *testing.T) {
peer := &Peer{DisplayName: "Test Peer"}
expected := "Test_Peer.conf"
assert.Equal(t, expected, peer.GetConfigFileName())
peer.DisplayName = ""
peer.Identifier = "12345678"
expected = "wg_12345678.conf"
assert.Equal(t, expected, peer.GetConfigFileName())
}
func TestPeer_ApplyInterfaceDefaults(t *testing.T) {
peer := &Peer{
Endpoint: ConfigOption[string]{
Value: "",
Overridable: true,
},
EndpointPublicKey: ConfigOption[string]{
Value: "",
Overridable: true,
},
AllowedIPsStr: ConfigOption[string]{
Value: "1.1.1.1/32",
Overridable: false,
},
}
iface := &Interface{
PeerDefEndpoint: "192.168.1.1",
KeyPair: KeyPair{
PublicKey: "publicKey",
},
PeerDefAllowedIPsStr: "8.8.8.8/32",
}
peer.ApplyInterfaceDefaults(iface)
assert.Equal(t, "192.168.1.1", peer.Endpoint.GetValue())
assert.Equal(t, "publicKey", peer.EndpointPublicKey.GetValue())
assert.Equal(t, "1.1.1.1/32", peer.AllowedIPsStr.GetValue())
}
func TestPeer_GenerateDisplayName(t *testing.T) {
peer := &Peer{Identifier: "12345678"}
peer.GenerateDisplayName("Prefix")
expected := "Prefix Peer 12345678"
assert.Equal(t, expected, peer.DisplayName)
peer.GenerateDisplayName("")
expected = "Peer 12345678"
assert.Equal(t, expected, peer.DisplayName)
}
func TestPeer_OverwriteUserEditableFields(t *testing.T) {
peer := &Peer{}
userPeer := &Peer{
DisplayName: "New DisplayName",
}
peer.OverwriteUserEditableFields(userPeer)
assert.Equal(t, "New DisplayName", peer.DisplayName)
}
func TestPeer_GetPresharedKey(t *testing.T) {
physicalPeer := PhysicalPeer{}
assert.Nil(t, physicalPeer.GetPresharedKey())
physicalPeer.PresharedKey = "Q0evIJTOjhyy2o5J7whvrsvQC+FRL8A74vrw44YHUAk="
key := physicalPeer.GetPresharedKey()
assert.NotNil(t, key)
}
func TestPeer_GetEndpointAddress(t *testing.T) {
physicalPeer := PhysicalPeer{}
assert.Nil(t, physicalPeer.GetEndpointAddress())
physicalPeer.Endpoint = "192.168.1.1:51820"
addr := physicalPeer.GetEndpointAddress()
assert.NotNil(t, addr)
assert.Equal(t, "192.168.1.1:51820", addr.String())
}
func TestPeer_GetPersistentKeepaliveTime(t *testing.T) {
physicalPeer := PhysicalPeer{}
assert.Nil(t, physicalPeer.GetPersistentKeepaliveTime())
physicalPeer.PersistentKeepalive = 25
duration := physicalPeer.GetPersistentKeepaliveTime()
assert.NotNil(t, duration)
assert.Equal(t, 25*time.Second, *duration)
}
func TestPeer_GetAllowedIPs(t *testing.T) {
physicalPeer := PhysicalPeer{}
assert.Empty(t, physicalPeer.GetAllowedIPs())
physicalPeer.AllowedIPs = []Cidr{
{
Cidr: "192.168.1.0/24",
Addr: "192.168.1.0",
NetLength: 24,
},
}
ips := physicalPeer.GetAllowedIPs()
assert.Len(t, ips, 1)
assert.Equal(t, "192.168.1.0/24", ips[0].String())
physicalPeer.AllowedIPs = []Cidr{
{
Cidr: "192.168.1.0/24",
Addr: "192.168.1.0",
NetLength: 24,
},
{
Cidr: "fe80::/64",
Addr: "fe80::",
NetLength: 64,
},
}
ips2 := physicalPeer.GetAllowedIPs()
assert.Len(t, ips2, 2)
assert.Equal(t, "192.168.1.0/24", ips2[0].String())
assert.Equal(t, "fe80::/64", ips2[1].String())
}

View File

@@ -0,0 +1,74 @@
package domain
import (
"testing"
"time"
)
func TestPeerStatus_IsConnected(t *testing.T) {
now := time.Now()
past := now.Add(-3 * time.Minute)
recent := now.Add(-1 * time.Minute)
tests := []struct {
name string
status PeerStatus
want bool
}{
{
name: "Pingable and recent handshake",
status: PeerStatus{
IsPingable: true,
LastHandshake: &recent,
},
want: true,
},
{
name: "Not pingable but recent handshake",
status: PeerStatus{
IsPingable: false,
LastHandshake: &recent,
},
want: true,
},
{
name: "Pingable but old handshake",
status: PeerStatus{
IsPingable: true,
LastHandshake: &past,
},
want: true,
},
{
name: "Not pingable and old handshake",
status: PeerStatus{
IsPingable: false,
LastHandshake: &past,
},
want: false,
},
{
name: "Pingable and no handshake",
status: PeerStatus{
IsPingable: true,
LastHandshake: nil,
},
want: true,
},
{
name: "Not pingable and no handshake",
status: PeerStatus{
IsPingable: false,
LastHandshake: nil,
},
want: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := tt.status.IsConnected(); got != tt.want {
t.Errorf("IsConnected() = %v, want %v", got, tt.want)
}
})
}
}

View File

@@ -0,0 +1,125 @@
package domain
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
"golang.org/x/crypto/bcrypt"
)
func TestUser_IsDisabled(t *testing.T) {
user := &User{}
assert.False(t, user.IsDisabled())
now := time.Now()
user.Disabled = &now
assert.True(t, user.IsDisabled())
}
func TestUser_IsLocked(t *testing.T) {
user := &User{}
assert.False(t, user.IsLocked())
now := time.Now()
user.Locked = &now
assert.True(t, user.IsLocked())
}
func TestUser_IsApiEnabled(t *testing.T) {
user := &User{}
assert.False(t, user.IsApiEnabled())
user.ApiToken = "token"
assert.True(t, user.IsApiEnabled())
}
func TestUser_CanChangePassword(t *testing.T) {
user := &User{Source: UserSourceDatabase}
assert.NoError(t, user.CanChangePassword())
user.Source = UserSourceLdap
assert.Error(t, user.CanChangePassword())
user.Source = UserSourceOauth
assert.Error(t, user.CanChangePassword())
}
func TestUser_EditAllowed(t *testing.T) {
user := &User{Source: UserSourceDatabase}
newUser := &User{Source: UserSourceDatabase}
assert.NoError(t, user.EditAllowed(newUser))
newUser.Notes = "notes can be changed"
assert.NoError(t, user.EditAllowed(newUser))
newUser.Disabled = &time.Time{}
assert.NoError(t, user.EditAllowed(newUser))
newUser.Lastname = "lastname or other fields can be changed"
assert.NoError(t, user.EditAllowed(newUser))
user.Source = UserSourceLdap
newUser.Source = UserSourceLdap
newUser.Disabled = nil
newUser.Lastname = ""
newUser.Notes = "notes can be changed"
assert.NoError(t, user.EditAllowed(newUser))
newUser.Disabled = &time.Time{}
assert.NoError(t, user.EditAllowed(newUser))
newUser.Lastname = "lastname or other fields can not be changed"
assert.Error(t, user.EditAllowed(newUser))
user.Source = UserSourceOauth
newUser.Source = UserSourceOauth
newUser.Disabled = nil
newUser.Lastname = ""
newUser.Notes = "notes can be changed"
assert.NoError(t, user.EditAllowed(newUser))
newUser.Disabled = &time.Time{}
assert.NoError(t, user.EditAllowed(newUser))
newUser.Lastname = "lastname or other fields can not be changed"
assert.Error(t, user.EditAllowed(newUser))
}
func TestUser_DeleteAllowed(t *testing.T) {
user := &User{}
assert.NoError(t, user.DeleteAllowed())
}
func TestUser_CheckPassword(t *testing.T) {
password := "password"
hashedPassword, _ := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
user := &User{Source: UserSourceDatabase, Password: PrivateString(hashedPassword)}
assert.NoError(t, user.CheckPassword(password))
user.Password = ""
assert.Error(t, user.CheckPassword(password))
user.Source = UserSourceLdap
assert.Error(t, user.CheckPassword(password))
}
func TestUser_CheckApiToken(t *testing.T) {
user := &User{}
assert.Error(t, user.CheckApiToken("token"))
user.ApiToken = "token"
assert.NoError(t, user.CheckApiToken("token"))
assert.Error(t, user.CheckApiToken("wrong_token"))
}
func TestUser_HashPassword(t *testing.T) {
user := &User{Password: "password"}
assert.NoError(t, user.HashPassword())
assert.NotEmpty(t, user.Password)
user.Password = ""
assert.NoError(t, user.HashPassword())
}