go fix changes

This commit is contained in:
Christoph
2026-09-10 23:10:35 +02:00
parent 1ba611c20f
commit fc965fcb73
40 changed files with 229 additions and 341 deletions
+16 -24
View File
@@ -279,12 +279,10 @@ func (r *SqlRepo) migrate() error {
now := time.Now()
for _, user := range users {
auth := domain.UserAuthentication{
BaseModel: domain.BaseModel{
CreatedBy: domain.CtxSystemDBMigrator,
UpdatedBy: domain.CtxSystemDBMigrator,
CreatedAt: now,
UpdatedAt: now,
},
CreatedBy: domain.CtxSystemDBMigrator,
UpdatedBy: domain.CtxSystemDBMigrator,
CreatedAt: now,
UpdatedAt: now,
UserIdentifier: user.Identifier,
Source: user.Source,
ProviderName: user.ProviderName,
@@ -493,12 +491,10 @@ func (r *SqlRepo) getOrCreateInterface(
// interfaceDefaults will be applied to newly created interface records
interfaceDefaults := domain.Interface{
BaseModel: domain.BaseModel{
CreatedBy: ui.UserId(),
UpdatedBy: ui.UserId(),
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
},
CreatedBy: ui.UserId(),
UpdatedBy: ui.UserId(),
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
Identifier: id,
}
@@ -702,12 +698,10 @@ func (r *SqlRepo) getOrCreatePeer(ui *domain.ContextUserInfo, tx *gorm.DB, id do
// interfaceDefaults will be applied to newly created interface records
interfaceDefaults := domain.Peer{
BaseModel: domain.BaseModel{
CreatedBy: ui.UserId(),
UpdatedBy: ui.UserId(),
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
},
CreatedBy: ui.UserId(),
UpdatedBy: ui.UserId(),
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
Identifier: id,
}
@@ -995,12 +989,10 @@ func (r *SqlRepo) getOrCreateUser(ui *domain.ContextUserInfo, tx *gorm.DB, id do
// create a new user record if no user record exists yet
userDefaults := domain.User{
BaseModel: domain.BaseModel{
CreatedBy: ui.UserId(),
UpdatedBy: ui.UserId(),
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
},
CreatedBy: ui.UserId(),
UpdatedBy: ui.UserId(),
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
Identifier: id,
IsAdmin: false,
}
@@ -54,10 +54,8 @@ func TestUpsertUser_PreservesExistingCreatedAt(t *testing.T) {
user := &domain.User{
Identifier: "test-user",
Email: "test@example.com",
BaseModel: domain.BaseModel{
CreatedAt: originalTime,
CreatedBy: "original-creator",
},
CreatedAt: originalTime,
CreatedBy: "original-creator",
}
err := repo.upsertUser(ui, db, user)
+10 -23
View File
@@ -127,11 +127,9 @@ func (c LocalController) convertWireGuardInterface(device *wgtypes.Device) (doma
// read data from wgctrl interface
iface := domain.PhysicalInterface{
Identifier: domain.InterfaceIdentifier(device.Name),
KeyPair: domain.KeyPair{
PrivateKey: device.PrivateKey.String(),
PublicKey: device.PublicKey.String(),
},
Identifier: domain.InterfaceIdentifier(device.Name),
PrivateKey: device.PrivateKey.String(),
PublicKey: device.PublicKey.String(),
ListenPort: device.ListenPort,
Addresses: nil,
Mtu: 0,
@@ -190,12 +188,10 @@ func (c LocalController) GetPeers(_ context.Context, deviceId domain.InterfaceId
func (c LocalController) convertWireGuardPeer(peer *wgtypes.Peer) (domain.PhysicalPeer, error) {
peerModel := domain.PhysicalPeer{
Identifier: domain.PeerIdentifier(peer.PublicKey.String()),
Endpoint: "",
AllowedIPs: nil,
KeyPair: domain.KeyPair{
PublicKey: peer.PublicKey.String(),
},
Identifier: domain.PeerIdentifier(peer.PublicKey.String()),
Endpoint: "",
AllowedIPs: nil,
PublicKey: peer.PublicKey.String(),
PresharedKey: "",
PersistentKeepalive: int(peer.PersistentKeepaliveInterval.Seconds()),
LastHandshake: peer.LastHandshakeTime,
@@ -280,9 +276,7 @@ func (c LocalController) getInterface(id domain.InterfaceIdentifier) (*domain.Ph
func (c LocalController) createLowLevelInterface(id domain.InterfaceIdentifier) error {
link := &netlink.GenericLink{
LinkAttrs: netlink.LinkAttrs{
Name: string(id),
},
Name: string(id),
LinkType: "wireguard",
}
err := c.nl.LinkAdd(link)
@@ -318,13 +312,7 @@ func (c LocalController) updateLowLevelInterface(pi *domain.PhysicalInterface) e
}
for _, rawAddr := range rawAddresses {
netlinkAddr := domain.CidrFromNetlinkAddr(rawAddr)
remove := true
for _, addr := range pi.Addresses {
if addr == netlinkAddr {
remove = false
break
}
}
remove := !slices.Contains(pi.Addresses, netlinkAddr)
if !remove {
continue
@@ -385,8 +373,7 @@ func (c LocalController) DeleteInterface(_ context.Context, id domain.InterfaceI
func (c LocalController) deleteLowLevelInterface(id domain.InterfaceIdentifier) error {
link, err := c.nl.LinkByName(string(id))
if err != nil {
var linkNotFoundError netlink.LinkNotFoundError
if errors.As(err, &linkNotFoundError) {
if _, ok := errors.AsType[netlink.LinkNotFoundError](err); ok {
return nil // ignore not found error
}
return fmt.Errorf("unable to find low level interface: %w", err)
+10 -20
View File
@@ -263,11 +263,9 @@ func (c *MikrotikController) convertWireGuardInterface(
error,
) {
pi := domain.PhysicalInterface{
Identifier: domain.InterfaceIdentifier(wg.GetString("name")),
KeyPair: domain.KeyPair{
PrivateKey: wg.GetString("private-key"),
PublicKey: wg.GetString("public-key"),
},
Identifier: domain.InterfaceIdentifier(wg.GetString("name")),
PrivateKey: wg.GetString("private-key"),
PublicKey: wg.GetString("public-key"),
ListenPort: wg.GetInt("listen-port"),
Addresses: addresses,
Mtu: wg.GetInt("mtu"),
@@ -354,13 +352,11 @@ func (c *MikrotikController) convertWireGuardPeer(peer lowlevel.GenericJsonObjec
}
peerModel := domain.PhysicalPeer{
Identifier: domain.PeerIdentifier(peer.GetString("public-key")),
Endpoint: currentEndpoint,
AllowedIPs: allowedAddresses,
KeyPair: domain.KeyPair{
PublicKey: peer.GetString("public-key"),
PrivateKey: peer.GetString("private-key"),
},
Identifier: domain.PeerIdentifier(peer.GetString("public-key")),
Endpoint: currentEndpoint,
AllowedIPs: allowedAddresses,
PublicKey: peer.GetString("public-key"),
PrivateKey: peer.GetString("private-key"),
PresharedKey: domain.PreSharedKey(peer.GetString("preshared-key")),
PersistentKeepalive: keepAliveSeconds,
LastHandshake: lastHandshakeTime,
@@ -792,7 +788,7 @@ func (c *MikrotikController) ExecuteInterfaceHook(
}
scriptName := fmt.Sprintf("wg-portal-hook-%s-%d", id, time.Now().UnixNano())
// Replace %i with the interface ID to mimic wg-quick behavior
scriptSource := strings.ReplaceAll(hookCmd, "%i", string(id))
// Inject the interface ID as a local variable for convenience in RouterOS scripts
@@ -1076,13 +1072,7 @@ func (c *MikrotikController) setRoutesForFamily(
continue
}
valid := false
for _, cidr := range cidrs {
if existingRoute.EqualPrefix(cidr) {
valid = true
break
}
}
valid := slices.ContainsFunc(cidrs, existingRoute.EqualPrefix)
if valid {
continue // route is still valid, nothing to do
}
+12 -16
View File
@@ -168,7 +168,7 @@ func parseCidrsTolerant(value, what, owner string) []domain.Cidr {
}
parsed := make([]domain.Cidr, 0, 1)
for _, part := range strings.Split(value, ",") {
for part := range strings.SplitSeq(value, ",") {
part = strings.TrimSpace(part)
if part == "" {
continue
@@ -424,11 +424,9 @@ func (c *OpnsenseController) convertServer(
stats := interfaceStats[string(identifier)]
physicalInterface := &domain.PhysicalInterface{
Identifier: identifier,
KeyPair: domain.KeyPair{
PrivateKey: row.GetString("privkey"),
PublicKey: row.GetString("pubkey"),
},
Identifier: identifier,
PrivateKey: row.GetString("privkey"),
PublicKey: row.GetString("pubkey"),
ListenPort: row.GetInt("port"),
Addresses: addresses,
Mtu: row.GetInt("mtu"),
@@ -494,7 +492,7 @@ func clientBelongsToServer(row lowlevel.GenericJsonObject, serverUuid string) bo
if serverUuid == "" {
return false
}
for _, uuid := range strings.Split(row.GetString("servers"), ",") {
for uuid := range strings.SplitSeq(row.GetString("servers"), ",") {
if strings.TrimSpace(uuid) == serverUuid {
return true
}
@@ -522,12 +520,10 @@ func (c *OpnsenseController) convertClient(
Identifier: domain.PeerIdentifier(publicKey),
Endpoint: endpoint,
AllowedIPs: allowedIPs,
KeyPair: domain.KeyPair{
PublicKey: publicKey,
// OPNsense stores only the public key for a peer; the private key
// stays with the client device.
PrivateKey: "",
},
PublicKey: publicKey,
// OPNsense stores only the public key for a peer; the private key
// stays with the client device.
PrivateKey: "",
PresharedKey: domain.PreSharedKey(row.GetString("psk")),
PersistentKeepalive: row.GetInt("keepalive"),
LastHandshake: stats.lastHandshake,
@@ -726,7 +722,7 @@ func (c *OpnsenseController) SavePeer(
} else {
physicalPeer = &domain.PhysicalPeer{
Identifier: id,
KeyPair: domain.KeyPair{PublicKey: string(id)},
PublicKey: string(id),
ImportSource: domain.ControllerTypeOpnsense,
}
physicalPeer.SetExtras(domain.OpnsensePeerExtras{})
@@ -781,7 +777,7 @@ func (c *OpnsenseController) createOrUpdatePeer(
// from them.
servers := map[string]struct{}{serverUuid: {}}
if existingRow != nil {
for _, uuid := range strings.Split(existingRow.GetString("servers"), ",") {
for uuid := range strings.SplitSeq(existingRow.GetString("servers"), ",") {
if uuid = strings.TrimSpace(uuid); uuid != "" {
servers[uuid] = struct{}{}
}
@@ -912,7 +908,7 @@ func (c *OpnsenseController) DeletePeer(
remaining := make([]string, 0)
if serverRow != nil {
serverUuid := serverRow.GetString("uuid")
for _, attached := range strings.Split(row.GetString("servers"), ",") {
for attached := range strings.SplitSeq(row.GetString("servers"), ",") {
if attached = strings.TrimSpace(attached); attached != "" && attached != serverUuid {
remaining = append(remaining, attached)
}
+9 -13
View File
@@ -340,11 +340,9 @@ func (c *PfsenseController) convertWireGuardInterface(
var rxBytes, txBytes uint64
pi := domain.PhysicalInterface{
Identifier: domain.InterfaceIdentifier(wg.GetString("name")),
KeyPair: domain.KeyPair{
PrivateKey: privateKey,
PublicKey: publicKey,
},
Identifier: domain.InterfaceIdentifier(wg.GetString("name")),
PrivateKey: privateKey,
PublicKey: publicKey,
ListenPort: listenPort,
Addresses: addresses,
Mtu: mtu,
@@ -559,13 +557,11 @@ func (c *PfsenseController) convertWireGuardPeer(peer lowlevel.GenericJsonObject
txBytes := uint64(0)
peerModel := domain.PhysicalPeer{
Identifier: domain.PeerIdentifier(publicKey),
Endpoint: endpoint,
AllowedIPs: allowedAddresses,
KeyPair: domain.KeyPair{
PublicKey: publicKey,
PrivateKey: privateKey,
},
Identifier: domain.PeerIdentifier(publicKey),
Endpoint: endpoint,
AllowedIPs: allowedAddresses,
PublicKey: publicKey,
PrivateKey: privateKey,
PresharedKey: domain.PreSharedKey(presharedKey),
PersistentKeepalive: keepAliveSeconds,
LastHandshake: lastHandshakeTime,
@@ -794,7 +790,7 @@ func (c *PfsenseController) SavePeer(
if physicalPeer == nil {
physicalPeer = &domain.PhysicalPeer{
Identifier: id,
KeyPair: domain.KeyPair{PublicKey: string(id)},
PublicKey: string(id),
ImportSource: domain.ControllerTypePfsense,
}
physicalPeer.SetExtras(domain.PfsensePeerExtras{})
+11 -23
View File
@@ -6,6 +6,7 @@ import (
"fmt"
"log/slog"
"os"
"slices"
"github.com/vishvananda/netlink"
"golang.zx2c4.com/wireguard/wgctrl"
@@ -106,11 +107,9 @@ func (r *WgRepo) convertWireGuardInterface(device *wgtypes.Device) (domain.Physi
// read data from wgctrl interface
iface := domain.PhysicalInterface{
Identifier: domain.InterfaceIdentifier(device.Name),
KeyPair: domain.KeyPair{
PrivateKey: device.PrivateKey.String(),
PublicKey: device.PublicKey.String(),
},
Identifier: domain.InterfaceIdentifier(device.Name),
PrivateKey: device.PrivateKey.String(),
PublicKey: device.PublicKey.String(),
ListenPort: device.ListenPort,
Addresses: nil,
Mtu: 0,
@@ -148,12 +147,10 @@ func (r *WgRepo) convertWireGuardInterface(device *wgtypes.Device) (domain.Physi
func (r *WgRepo) convertWireGuardPeer(peer *wgtypes.Peer) (domain.PhysicalPeer, error) {
peerModel := domain.PhysicalPeer{
Identifier: domain.PeerIdentifier(peer.PublicKey.String()),
Endpoint: "",
AllowedIPs: nil,
KeyPair: domain.KeyPair{
PublicKey: peer.PublicKey.String(),
},
Identifier: domain.PeerIdentifier(peer.PublicKey.String()),
Endpoint: "",
AllowedIPs: nil,
PublicKey: peer.PublicKey.String(),
PresharedKey: "",
PersistentKeepalive: int(peer.PersistentKeepaliveInterval.Seconds()),
LastHandshake: peer.LastHandshakeTime,
@@ -241,9 +238,7 @@ func (r *WgRepo) getInterface(id domain.InterfaceIdentifier) (*domain.PhysicalIn
func (r *WgRepo) createLowLevelInterface(id domain.InterfaceIdentifier) error {
link := &netlink.GenericLink{
LinkAttrs: netlink.LinkAttrs{
Name: string(id),
},
Name: string(id),
LinkType: "wireguard",
}
err := r.nl.LinkAdd(link)
@@ -279,13 +274,7 @@ func (r *WgRepo) updateLowLevelInterface(pi *domain.PhysicalInterface) error {
}
for _, rawAddr := range rawAddresses {
netlinkAddr := domain.CidrFromNetlinkAddr(rawAddr)
remove := true
for _, addr := range pi.Addresses {
if addr == netlinkAddr {
remove = false
break
}
}
remove := !slices.Contains(pi.Addresses, netlinkAddr)
if !remove {
continue
@@ -351,8 +340,7 @@ func (r *WgRepo) DeleteInterface(_ context.Context, id domain.InterfaceIdentifie
func (r *WgRepo) deleteLowLevelInterface(id domain.InterfaceIdentifier) error {
link, err := r.nl.LinkByName(string(id))
if err != nil {
var linkNotFoundError netlink.LinkNotFoundError
if errors.As(err, &linkNotFoundError) {
if _, ok := errors.AsType[netlink.LinkNotFoundError](err); ok {
return nil // ignore not found error
}
return fmt.Errorf("unable to find low level interface: %w", err)
@@ -18,10 +18,10 @@ func (w wildcard) match(s string) bool {
}
func newWildcard(s string) wildcard {
if i := strings.IndexByte(s, '*'); i >= 0 {
if before, after, ok := strings.Cut(s, "*"); ok {
return wildcard{
prefix: s[:i],
suffix: s[i+1:],
prefix: before,
suffix: after,
}
}
@@ -52,7 +52,7 @@ func maskToken(token, key []byte) []byte {
// masked contains the key in the first half and the XOR masked token in the second half
tokenLength := len(token)
masked := make([]byte, tokenLength*2)
for i := 0; i < len(token); i++ {
for i := range token {
masked[i] = key[i]
masked[i+tokenLength] = token[i] ^ key[i] // XOR mask
}
@@ -65,7 +65,7 @@ func maskToken(token, key []byte) []byte {
func unmaskToken(masked []byte) []byte {
tokenLength := len(masked) / 2
token := make([]byte, tokenLength)
for i := 0; i < tokenLength; i++ {
for i := range tokenLength {
token[i] = masked[i] ^ masked[i+tokenLength] // XOR unmask
}
@@ -85,7 +85,7 @@ func addPrefix(o options, message string) string {
// enabled, the stack trace is included in the response.
func getDefaultErrCallback(o options) func(err error, stack []byte, w http.ResponseWriter, r *http.Request) {
return func(err error, stack []byte, w http.ResponseWriter, r *http.Request) {
responseBody := map[string]interface{}{
responseBody := map[string]any{
"error": "Internal Server Error",
}
if o.exposeStackTrace && len(stack) > 0 {
@@ -120,8 +120,7 @@ func getDefaultLogCallback(o options) func(error, []byte, bool) {
}
func isBrokenPipeError(err error) bool {
var syscallErr *os.SyscallError
if errors.As(err, &syscallErr) {
if syscallErr, ok := errors.AsType[*os.SyscallError](err); ok {
errMsg := strings.ToLower(syscallErr.Err.Error())
if strings.Contains(errMsg, "broken pipe") ||
strings.Contains(errMsg, "connection reset by peer") {
+1 -1
View File
@@ -114,7 +114,7 @@ type Session interface {
type Validator interface {
// Struct validates the given struct.
Struct(s interface{}) error
Struct(s any) error
}
// endregion handler-interfaces
@@ -3,6 +3,7 @@ package handlers
import (
"context"
"net/http"
"slices"
"github.com/h44z/wg-portal/internal/app/api/core/request"
"github.com/h44z/wg-portal/internal/app/api/core/respond"
@@ -137,10 +138,8 @@ func UserHasScopes(session SessionData, scopes ...Scope) bool {
}
// Check if admin scope is required
for _, scope := range scopes {
if scope == ScopeAdmin {
return false
}
if slices.Contains(scopes, ScopeAdmin) {
return false
}
// For all other scopes, a logged-in user is sufficient (for now)
@@ -137,12 +137,10 @@ func NewDomainInterface(src *Interface) *domain.Interface {
cidrs, _ := domain.CidrsFromArray(src.Addresses)
res := &domain.Interface{
BaseModel: domain.BaseModel{},
Identifier: domain.InterfaceIdentifier(src.Identifier),
KeyPair: domain.KeyPair{
PrivateKey: src.PrivateKey,
PublicKey: src.PublicKey,
},
BaseModel: domain.BaseModel{},
Identifier: domain.InterfaceIdentifier(src.Identifier),
PrivateKey: src.PrivateKey,
PublicKey: src.PublicKey,
ListenPort: src.ListenPort,
Addresses: cidrs,
DnsStr: internal.SliceToString(src.Dns),
+1 -1
View File
@@ -47,7 +47,7 @@ type Peer struct {
InterfaceIdentifier string `json:"InterfaceIdentifier"` // the interface id
Disabled bool `json:"Disabled"` // flag that specifies if the peer is enabled (up) or not (down)
DisabledReason string `json:"DisabledReason"` // the reason why the peer has been disabled
ExpiresAt ExpiryDate `json:"ExpiresAt,omitempty"` // expiry dates for peers
ExpiresAt ExpiryDate `json:"ExpiresAt"` // expiry dates for peers
Notes string `json:"Notes"` // a note field for peers
IsDynamic bool `json:"IsDynamic"` // specifies if the peer is dynamically created by mikrotik
+1 -1
View File
@@ -88,7 +88,7 @@ type Authenticator interface {
type Validator interface {
// Struct validates the given struct.
Struct(s interface{}) error
Struct(s any) error
}
// endregion handler-interfaces
@@ -3,6 +3,7 @@ package handlers
import (
"context"
"net/http"
"slices"
"github.com/h44z/wg-portal/internal/app/api/core/respond"
"github.com/h44z/wg-portal/internal/app/api/v0/model"
@@ -104,11 +105,5 @@ func UserHasScopes(user *domain.User, scopes ...Scope) bool {
}
// Check if admin scope is required
for _, scope := range scopes {
if scope == ScopeAdmin {
return false
}
}
return true
return !slices.Contains(scopes, ScopeAdmin)
}
@@ -159,12 +159,10 @@ func NewDomainInterface(src *Interface) *domain.Interface {
cidrs, _ := domain.CidrsFromArray(src.Addresses)
res := &domain.Interface{
BaseModel: domain.BaseModel{},
Identifier: domain.InterfaceIdentifier(src.Identifier),
KeyPair: domain.KeyPair{
PrivateKey: src.PrivateKey,
PublicKey: src.PublicKey,
},
BaseModel: domain.BaseModel{},
Identifier: domain.InterfaceIdentifier(src.Identifier),
PrivateKey: src.PrivateKey,
PublicKey: src.PublicKey,
ListenPort: src.ListenPort,
Addresses: cidrs,
DnsStr: internal.SliceToString(src.Dns),
+4 -6
View File
@@ -122,12 +122,10 @@ func (a *App) createDefaultUser(ctx context.Context) error {
now := time.Now()
defaultAdmin := &domain.User{
BaseModel: domain.BaseModel{
CreatedBy: domain.CtxSystemAdminId,
UpdatedBy: domain.CtxSystemAdminId,
CreatedAt: now,
UpdatedAt: now,
},
CreatedBy: domain.CtxSystemAdminId,
UpdatedBy: domain.CtxSystemAdminId,
CreatedAt: now,
UpdatedAt: now,
Identifier: adminUserId,
Email: "admin@wgportal.local",
IsAdmin: true,
+1 -1
View File
@@ -20,7 +20,7 @@ type DatabaseRepo interface {
type EventBus interface {
// Subscribe subscribes to a topic
Subscribe(topic string, fn interface{}) error
Subscribe(topic string, fn any) error
}
// endregion dependencies
+8 -12
View File
@@ -673,12 +673,10 @@ func (a *Authenticator) registerNewUser(
Department: userInfo.Department,
Authentications: []domain.UserAuthentication{
{
BaseModel: domain.BaseModel{
CreatedBy: ctxUserInfo.UserId(),
UpdatedBy: ctxUserInfo.UserId(),
CreatedAt: now,
UpdatedAt: now,
},
CreatedBy: ctxUserInfo.UserId(),
UpdatedBy: ctxUserInfo.UserId(),
CreatedAt: now,
UpdatedAt: now,
UserIdentifier: userInfo.Identifier,
Source: source,
ProviderName: provider,
@@ -742,12 +740,10 @@ func (a *Authenticator) updateExternalUser(
ctxUserInfo := domain.GetUserInfo(ctx)
now := time.Now()
existingUser.Authentications = append(existingUser.Authentications, domain.UserAuthentication{
BaseModel: domain.BaseModel{
CreatedBy: ctxUserInfo.UserId(),
UpdatedBy: ctxUserInfo.UserId(),
CreatedAt: now,
UpdatedAt: now,
},
CreatedBy: ctxUserInfo.UserId(),
UpdatedBy: ctxUserInfo.UserId(),
CreatedAt: now,
UpdatedAt: now,
UserIdentifier: existingUser.Identifier,
Source: source,
ProviderName: provider,
+11 -15
View File
@@ -1,6 +1,7 @@
package auth
import (
"slices"
"strings"
"github.com/h44z/wg-portal/internal"
@@ -49,11 +50,8 @@ func parseOauthUserInfo(
if !isAdmin && mapping.UserGroups != "" && adminMapping.AdminGroupRegex != "" {
adminInfoAvailable = true
re := adminMapping.GetAdminGroupRegex()
for _, group := range userInfo.UserGroups {
if re.MatchString(group) {
isAdmin = true
break
}
if slices.ContainsFunc(userInfo.UserGroups, re.MatchString) {
isAdmin = true
}
userInfo.IsAdmin = isAdmin
userInfo.AdminInfoAvailable = adminInfoAvailable
@@ -65,16 +63,14 @@ func parseOauthUserInfo(
// getOauthFieldMapping returns the default field mapping for the oauth provider
func getOauthFieldMapping(f config.OauthFields) config.OauthFields {
defaultMap := config.OauthFields{
BaseFields: config.BaseFields{
UserIdentifier: "sub",
Email: "email",
Firstname: "given_name",
Lastname: "family_name",
Phone: "phone",
Department: "department",
},
IsAdmin: "admin_flag",
UserGroups: "", // by default, do not use user groups
UserIdentifier: "sub",
Email: "email",
Firstname: "given_name",
Lastname: "family_name",
Phone: "phone",
Department: "department",
IsAdmin: "admin_flag",
UserGroups: "", // by default, do not use user groups
}
if f.UserIdentifier != "" {
defaultMap.UserIdentifier = f.UserIdentifier
@@ -16,14 +16,12 @@ import (
// makeOauthFieldMapping returns a minimal OauthFields mapping for testing.
func makeOauthFieldMapping() config.OauthFields {
return config.OauthFields{
BaseFields: config.BaseFields{
UserIdentifier: "sub",
Email: "email",
Firstname: "given_name",
Lastname: "family_name",
Phone: "phone",
Department: "department",
},
UserIdentifier: "sub",
Email: "email",
Firstname: "given_name",
Lastname: "family_name",
Phone: "phone",
Department: "department",
}
}
+21 -29
View File
@@ -32,14 +32,12 @@ func Test_parseOauthUserInfo_no_admin(t *testing.T) {
require.NoError(t, err)
fieldMapping := getOauthFieldMapping(config.OauthFields{
BaseFields: config.BaseFields{
UserIdentifier: "email",
Email: "email",
Firstname: "name",
Lastname: "family_name",
},
IsAdmin: "is_admin",
UserGroups: "groups",
UserIdentifier: "email",
Email: "email",
Firstname: "name",
Lastname: "family_name",
IsAdmin: "is_admin",
UserGroups: "groups",
})
adminMapping := &config.OauthAdminMapping{}
@@ -78,13 +76,11 @@ func Test_parseOauthUserInfo_admin_group(t *testing.T) {
require.NoError(t, err)
fieldMapping := getOauthFieldMapping(config.OauthFields{
BaseFields: config.BaseFields{
UserIdentifier: "email",
Email: "email",
Firstname: "name",
Lastname: "family_name",
},
UserGroups: "groups",
UserIdentifier: "email",
Email: "email",
Firstname: "name",
Lastname: "family_name",
UserGroups: "groups",
})
adminMapping := &config.OauthAdminMapping{
AdminGroupRegex: "^wgportal-admins@mydomain.net$",
@@ -122,13 +118,11 @@ func Test_parseOauthUserInfo_admin_value(t *testing.T) {
require.NoError(t, err)
fieldMapping := getOauthFieldMapping(config.OauthFields{
BaseFields: config.BaseFields{
UserIdentifier: "email",
Email: "email",
Firstname: "name",
Lastname: "family_name",
},
IsAdmin: "is_admin",
UserIdentifier: "email",
Email: "email",
Firstname: "name",
Lastname: "family_name",
IsAdmin: "is_admin",
})
adminMapping := &config.OauthAdminMapping{} // test with default regex
@@ -163,13 +157,11 @@ func Test_parseOauthUserInfo_admin_value_custom(t *testing.T) {
require.NoError(t, err)
fieldMapping := getOauthFieldMapping(config.OauthFields{
BaseFields: config.BaseFields{
UserIdentifier: "email",
Email: "email",
Firstname: "name",
Lastname: "family_name",
},
IsAdmin: "is_admin",
UserIdentifier: "email",
Email: "email",
Firstname: "name",
Lastname: "family_name",
IsAdmin: "is_admin",
})
adminMapping := &config.OauthAdminMapping{
AdminValueRegex: "^1$",
+27 -41
View File
@@ -136,12 +136,10 @@ func migrateV1Users(oldDb, newDb *gorm.DB) error {
deletionReason = "disabled prior to migration"
}
newUser := domain.User{
BaseModel: domain.BaseModel{
CreatedBy: domain.CtxSystemV1Migrator,
UpdatedBy: domain.CtxSystemV1Migrator,
CreatedAt: oldUser.CreatedAt,
UpdatedAt: oldUser.UpdatedAt,
},
CreatedBy: domain.CtxSystemV1Migrator,
UpdatedBy: domain.CtxSystemV1Migrator,
CreatedAt: oldUser.CreatedAt,
UpdatedAt: oldUser.UpdatedAt,
Identifier: domain.UserIdentifier(oldUser.Email),
Email: oldUser.Email,
IsAdmin: oldUser.IsAdmin,
@@ -162,12 +160,10 @@ func migrateV1Users(oldDb, newDb *gorm.DB) error {
}
authentication := domain.UserAuthentication{
BaseModel: domain.BaseModel{
CreatedBy: domain.CtxSystemV1Migrator,
UpdatedBy: domain.CtxSystemV1Migrator,
CreatedAt: oldUser.CreatedAt,
UpdatedAt: oldUser.UpdatedAt,
},
CreatedBy: domain.CtxSystemV1Migrator,
UpdatedBy: domain.CtxSystemV1Migrator,
CreatedAt: oldUser.CreatedAt,
UpdatedAt: oldUser.UpdatedAt,
UserIdentifier: domain.UserIdentifier(oldUser.Email),
Source: domain.UserSource(oldUser.Source),
ProviderName: "", // unknown
@@ -223,17 +219,13 @@ func migrateV1Interfaces(oldDb, newDb *gorm.DB) error {
networks[i] = domain.CidrFromIpNet(*ip.IpNet())
}
newInterface := domain.Interface{
BaseModel: domain.BaseModel{
CreatedBy: domain.CtxSystemV1Migrator,
UpdatedBy: domain.CtxSystemV1Migrator,
CreatedAt: oldDevice.CreatedAt,
UpdatedAt: oldDevice.UpdatedAt,
},
Identifier: domain.InterfaceIdentifier(oldDevice.DeviceName),
KeyPair: domain.KeyPair{
PrivateKey: oldDevice.PrivateKey,
PublicKey: oldDevice.PublicKey,
},
CreatedBy: domain.CtxSystemV1Migrator,
UpdatedBy: domain.CtxSystemV1Migrator,
CreatedAt: oldDevice.CreatedAt,
UpdatedAt: oldDevice.UpdatedAt,
Identifier: domain.InterfaceIdentifier(oldDevice.DeviceName),
PrivateKey: oldDevice.PrivateKey,
PublicKey: oldDevice.PublicKey,
ListenPort: oldDevice.ListenPort,
Addresses: ips,
DnsStr: "",
@@ -350,12 +342,10 @@ func migrateV1Peers(oldDb, newDb *gorm.DB) error {
// create dummy user
now := time.Now()
user = domain.User{
BaseModel: domain.BaseModel{
CreatedBy: domain.CtxSystemV1Migrator,
UpdatedBy: domain.CtxSystemV1Migrator,
CreatedAt: now,
UpdatedAt: now,
},
CreatedBy: domain.CtxSystemV1Migrator,
UpdatedBy: domain.CtxSystemV1Migrator,
CreatedAt: now,
UpdatedAt: now,
Identifier: domain.UserIdentifier(oldPeer.Email),
Email: oldPeer.Email,
IsAdmin: false,
@@ -369,12 +359,10 @@ func migrateV1Peers(oldDb, newDb *gorm.DB) error {
}
authentication := domain.UserAuthentication{
BaseModel: domain.BaseModel{
CreatedBy: domain.CtxSystemV1Migrator,
UpdatedBy: domain.CtxSystemV1Migrator,
CreatedAt: now,
UpdatedAt: now,
},
CreatedBy: domain.CtxSystemV1Migrator,
UpdatedBy: domain.CtxSystemV1Migrator,
CreatedAt: now,
UpdatedAt: now,
UserIdentifier: domain.UserIdentifier(oldPeer.Email),
Source: domain.UserSourceDatabase,
ProviderName: "", // unknown
@@ -386,12 +374,10 @@ func migrateV1Peers(oldDb, newDb *gorm.DB) error {
slog.Debug("dummy user migrated successfully", "identifier", user.Identifier)
}
newPeer := domain.Peer{
BaseModel: domain.BaseModel{
CreatedBy: domain.CtxSystemV1Migrator,
UpdatedBy: domain.CtxSystemV1Migrator,
CreatedAt: oldPeer.CreatedAt,
UpdatedAt: oldPeer.UpdatedAt,
},
CreatedBy: domain.CtxSystemV1Migrator,
UpdatedBy: domain.CtxSystemV1Migrator,
CreatedAt: oldPeer.CreatedAt,
UpdatedAt: oldPeer.UpdatedAt,
Endpoint: domain.NewConfigOption(oldPeer.Endpoint, !oldPeer.IgnoreGlobalSettings),
EndpointPublicKey: domain.NewConfigOption(iface.PublicKey, !oldPeer.IgnoreGlobalSettings),
AllowedIPsStr: domain.NewConfigOption(oldPeer.AllowedIPsStr, !oldPeer.IgnoreGlobalSettings),
+1 -1
View File
@@ -25,7 +25,7 @@ type InterfaceAndPeerDatabaseRepo interface {
type EventBus interface {
// Subscribe subscribes to a topic
Subscribe(topic string, fn interface{}) error
Subscribe(topic string, fn any) error
}
type RoutesController interface {
+4 -6
View File
@@ -29,12 +29,10 @@ func convertRawLdapUser(
uid := domain.UserIdentifier(internal.MapDefaultString(rawUser, fields.UserIdentifier, ""))
user := &domain.User{
BaseModel: domain.BaseModel{
CreatedBy: domain.CtxSystemLdapSyncer,
UpdatedBy: domain.CtxSystemLdapSyncer,
CreatedAt: now,
UpdatedAt: now,
},
CreatedBy: domain.CtxSystemLdapSyncer,
UpdatedBy: domain.CtxSystemLdapSyncer,
CreatedAt: now,
UpdatedAt: now,
Identifier: uid,
Email: strings.ToLower(internal.MapDefaultString(rawUser, fields.Email, "")),
IsAdmin: isAdmin,
@@ -16,14 +16,12 @@ import (
// makeTestLdapFields returns a minimal LdapFields config for testing.
func makeTestLdapFields() *config.LdapFields {
return &config.LdapFields{
BaseFields: config.BaseFields{
UserIdentifier: "uid",
Email: "mail",
Firstname: "givenName",
Lastname: "sn",
Phone: "telephoneNumber",
Department: "department",
},
UserIdentifier: "uid",
Email: "mail",
Firstname: "givenName",
Lastname: "sn",
Phone: "telephoneNumber",
Department: "department",
GroupMembership: "memberOf",
}
}
+5 -7
View File
@@ -156,7 +156,7 @@ func (m Manager) GetAllUsers(ctx context.Context) ([]domain.User, error) {
wg := sync.WaitGroup{}
workers := int(math.Min(float64(len(users)), 10))
wg.Add(workers)
for i := 0; i < workers; i++ {
for range workers {
go func() {
defer wg.Done()
for user := range ch {
@@ -500,12 +500,10 @@ func (m Manager) create(ctx context.Context, user *domain.User) (*domain.User, e
now := time.Now()
user.Authentications = []domain.UserAuthentication{
{
BaseModel: domain.BaseModel{
CreatedBy: ctxUserInfo.UserId(),
UpdatedBy: ctxUserInfo.UserId(),
CreatedAt: now,
UpdatedAt: now,
},
CreatedBy: ctxUserInfo.UserId(),
UpdatedBy: ctxUserInfo.UserId(),
CreatedAt: now,
UpdatedAt: now,
UserIdentifier: user.Identifier,
Source: domain.UserSourceDatabase,
ProviderName: "",
+1 -1
View File
@@ -19,7 +19,7 @@ type EventBus interface {
// Publish sends a message to the message bus.
Publish(topic string, args ...any)
// Subscribe subscribes to a topic
Subscribe(topic string, fn interface{}) error
Subscribe(topic string, fn any) error
}
// endregion dependencies
@@ -23,7 +23,8 @@ func TestRegisterOpnsenseControllers(t *testing.T) {
cfg := &config.Config{}
cfg.Backend.Opnsense = []config.BackendOpnsense{
{
BackendBase: config.BackendBase{Id: "opn1", DisplayName: "Edge firewall"},
Id: "opn1",
DisplayName: "Edge firewall",
ApiUrl: "https://127.0.0.1",
ApiKey: "key",
ApiSecret: "secret",
@@ -51,10 +52,10 @@ func TestRegisterOpnsenseControllersSkipsReservedId(t *testing.T) {
cfg := &config.Config{}
cfg.Backend.Opnsense = []config.BackendOpnsense{
{
BackendBase: config.BackendBase{Id: config.LocalBackendName},
ApiUrl: "https://127.0.0.1",
ApiKey: "key",
ApiSecret: "secret",
Id: config.LocalBackendName,
ApiUrl: "https://127.0.0.1",
ApiKey: "key",
ApiSecret: "secret",
},
}
+1 -1
View File
@@ -36,7 +36,7 @@ type StatisticsMetricsServer interface {
type StatisticsEventBus interface {
// Subscribe subscribes to a topic
Subscribe(topic string, fn interface{}) error
Subscribe(topic string, fn any) error
// Publish sends a message to the message bus.
Publish(topic string, args ...any)
}
+1 -1
View File
@@ -49,7 +49,7 @@ type EventBus interface {
// Publish sends a message to the message bus.
Publish(topic string, args ...any)
// Subscribe subscribes to a topic
Subscribe(topic string, fn interface{}) error
Subscribe(topic string, fn any) error
}
// endregion dependencies
@@ -375,12 +375,10 @@ func (m Manager) PrepareInterface(ctx context.Context) (*domain.Interface, error
}
freshInterface := &domain.Interface{
BaseModel: domain.BaseModel{
CreatedBy: string(currentUser.Id),
UpdatedBy: string(currentUser.Id),
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
},
CreatedBy: string(currentUser.Id),
UpdatedBy: string(currentUser.Id),
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
Identifier: id,
KeyPair: kp,
ListenPort: port,
@@ -114,7 +114,7 @@ func TestImportPeer_AddressMapping(t *testing.T) {
p := &domain.PhysicalPeer{
Identifier: "peer1",
KeyPair: domain.KeyPair{PublicKey: "peer1-public-key-is-long-enough"},
PublicKey: "peer1-public-key-is-long-enough",
AllowedIPs: allowedIPs,
}
+4 -6
View File
@@ -166,12 +166,10 @@ func (m Manager) PreparePeer(ctx context.Context, id domain.InterfaceIdentifier)
peerId := domain.PeerIdentifier(kp.PublicKey)
freshPeer := &domain.Peer{
BaseModel: domain.BaseModel{
CreatedBy: string(currentUser.Id),
UpdatedBy: string(currentUser.Id),
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
},
CreatedBy: string(currentUser.Id),
UpdatedBy: string(currentUser.Id),
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
Endpoint: domain.NewConfigOption(iface.PeerDefEndpoint, true),
EndpointPublicKey: domain.NewConfigOption(iface.PublicKey, true),
AllowedIPsStr: domain.NewConfigOption(iface.PeerDefAllowedIPsStr, true),
@@ -12,8 +12,8 @@ import (
type mockBus struct{}
func (f *mockBus) Publish(topic string, args ...any) {}
func (f *mockBus) Subscribe(topic string, fn interface{}) error { return nil }
func (f *mockBus) Publish(topic string, args ...any) {}
func (f *mockBus) Subscribe(topic string, fn any) error { return nil }
type mockController struct{}
+6 -8
View File
@@ -130,14 +130,12 @@ type LdapFields struct {
// If specific fields are not set, the default values are used.
func (f LdapFields) getMappingWithDefaults() LdapFields {
defaultMap := LdapFields{
BaseFields: BaseFields{
UserIdentifier: "mail",
Email: "mail",
Firstname: "givenName",
Lastname: "sn",
Phone: "telephoneNumber",
Department: "department",
},
UserIdentifier: "mail",
Email: "mail",
Firstname: "givenName",
Lastname: "sn",
Phone: "telephoneNumber",
Department: "department",
GroupMembership: "memberOf",
}
if f.UserIdentifier != "" {
+8 -8
View File
@@ -16,9 +16,9 @@ func TestBackendValidate_DefaultsToLocal(t *testing.T) {
func TestBackendValidate_RejectsReservedId(t *testing.T) {
tests := map[string]Backend{
"mikrotik": {Mikrotik: []BackendMikrotik{{BackendBase: BackendBase{Id: LocalBackendName}}}},
"pfsense": {Pfsense: []BackendPfsense{{BackendBase: BackendBase{Id: LocalBackendName}}}},
"opnsense": {Opnsense: []BackendOpnsense{{BackendBase: BackendBase{Id: LocalBackendName}}}},
"mikrotik": {Mikrotik: []BackendMikrotik{{Id: LocalBackendName}}},
"pfsense": {Pfsense: []BackendPfsense{{Id: LocalBackendName}}},
"opnsense": {Opnsense: []BackendOpnsense{{Id: LocalBackendName}}},
}
for name, backend := range tests {
@@ -35,8 +35,8 @@ func TestBackendValidate_RejectsReservedId(t *testing.T) {
// controller map would silently keep only one of them.
func TestBackendValidate_RejectsDuplicateIdAcrossTypes(t *testing.T) {
backend := Backend{
Pfsense: []BackendPfsense{{BackendBase: BackendBase{Id: "fw1"}}},
Opnsense: []BackendOpnsense{{BackendBase: BackendBase{Id: "fw1"}}},
Pfsense: []BackendPfsense{{Id: "fw1"}},
Opnsense: []BackendOpnsense{{Id: "fw1"}},
}
err := backend.Validate()
@@ -47,7 +47,7 @@ func TestBackendValidate_RejectsDuplicateIdAcrossTypes(t *testing.T) {
func TestBackendValidate_RejectsUnknownDefault(t *testing.T) {
backend := Backend{
Default: "does-not-exist",
Opnsense: []BackendOpnsense{{BackendBase: BackendBase{Id: "fw1"}}},
Opnsense: []BackendOpnsense{{Id: "fw1"}},
}
err := backend.Validate()
@@ -58,7 +58,7 @@ func TestBackendValidate_RejectsUnknownDefault(t *testing.T) {
func TestBackendValidate_AcceptsOpnsenseAsDefault(t *testing.T) {
backend := Backend{
Default: "fw1",
Opnsense: []BackendOpnsense{{BackendBase: BackendBase{Id: "fw1"}}},
Opnsense: []BackendOpnsense{{Id: "fw1"}},
}
assert.NoError(t, backend.Validate())
@@ -77,7 +77,7 @@ func TestBackendOpnsenseDefaults(t *testing.T) {
}
func TestBackendOpnsenseDisplayName(t *testing.T) {
backend := BackendOpnsense{BackendBase: BackendBase{Id: "fw1"}}
backend := BackendOpnsense{Id: "fw1"}
assert.Equal(t, "fw1", backend.GetDisplayName(), "display name falls back to the id")
backend.DisplayName = "Edge firewall"
+5 -6
View File
@@ -6,6 +6,7 @@ import (
"math"
"net"
"regexp"
"slices"
"strconv"
"strings"
"time"
@@ -60,8 +61,8 @@ type Interface struct {
DriverType string // the interface driver type (linux, software, ...)
IsDynamic bool `gorm:"column:is_dynamic;default:false"` // specifies if the interface is a dynamic object
Disabled *time.Time `gorm:"index"` // flag that specifies if the interface is enabled (up) or not (down)
DisabledReason string // the reason why the interface has been disabled
Disabled *time.Time `gorm:"index"` // flag that specifies if the interface is enabled (up) or not (down)
DisabledReason string // the reason why the interface has been disabled
// Default settings for the peer, used for new peers, those settings will be published to ConfigOption options of
// the peer config
@@ -100,10 +101,8 @@ func (i *Interface) IsUserAllowed(userId UserIdentifier, cfg *config.Config) boo
}
for _, allowedUsers := range i.LdapAllowedUsers {
for _, uid := range allowedUsers {
if uid == userId {
return true
}
if slices.Contains(allowedUsers, userId) {
return true
}
}
return false
+3 -5
View File
@@ -4,8 +4,8 @@ import (
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/h44z/wg-portal/internal/config"
"github.com/stretchr/testify/assert"
)
func TestPeer_IsDisabled(t *testing.T) {
@@ -69,10 +69,8 @@ func TestPeer_ApplyInterfaceDefaults(t *testing.T) {
},
}
iface := &Interface{
PeerDefEndpoint: "192.168.1.1",
KeyPair: KeyPair{
PublicKey: "publicKey",
},
PeerDefEndpoint: "192.168.1.1",
PublicKey: "publicKey",
PeerDefAllowedIPsStr: "8.8.8.8/32",
}