mirror of
https://github.com/UNITRONIX/BetterDesk.git
synced 2026-09-10 17:45:42 +00:00
1b240543bd
- Added functionality for safely consolidating legacy auth.db into the selected SQLite database, with options for dry runs and rollbacks. - Introduced command-line flags for SQLite auth consolidation, including backup directory and rollback options. - Enhanced admin interface security by requiring a password when the admin port is enabled, preventing unauthorized access. - Updated related tests to ensure proper handling of admin password requirements and relay authorization logic.
164 lines
4.0 KiB
Go
164 lines
4.0 KiB
Go
package admin
|
|
|
|
import (
|
|
"bufio"
|
|
"context"
|
|
"fmt"
|
|
"net"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/unitronix/betterdesk-server/config"
|
|
"github.com/unitronix/betterdesk-server/db"
|
|
"github.com/unitronix/betterdesk-server/peer"
|
|
"github.com/unitronix/betterdesk-server/security"
|
|
)
|
|
|
|
const testAdminPassword = "test-admin-password"
|
|
|
|
func setupTestAdmin(t *testing.T) (*Server, int) {
|
|
t.Helper()
|
|
|
|
cfg := config.DefaultConfig()
|
|
// Find a free port
|
|
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
|
if err != nil {
|
|
t.Fatalf("failed to find free port: %v", err)
|
|
}
|
|
port := ln.Addr().(*net.TCPAddr).Port
|
|
ln.Close()
|
|
|
|
cfg.AdminPort = port
|
|
cfg.AdminPassword = testAdminPassword
|
|
|
|
database, err := db.Open(":memory:")
|
|
if err != nil {
|
|
t.Fatalf("failed to open db: %v", err)
|
|
}
|
|
database.Migrate()
|
|
t.Cleanup(func() { database.Close() })
|
|
|
|
peerMap := peer.NewMap()
|
|
srv := New(cfg, database, peerMap, "test")
|
|
srv.SetAdminPassword(testAdminPassword)
|
|
srv.SetBlocklist(security.NewBlocklist())
|
|
|
|
ctx := context.Background()
|
|
if err := srv.Start(ctx); err != nil {
|
|
t.Fatalf("failed to start admin: %v", err)
|
|
}
|
|
t.Cleanup(srv.Stop)
|
|
|
|
return srv, port
|
|
}
|
|
|
|
func connectAdmin(t *testing.T, port int) net.Conn {
|
|
t.Helper()
|
|
conn, err := net.DialTimeout("tcp", fmt.Sprintf("127.0.0.1:%d", port), 2*time.Second)
|
|
if err != nil {
|
|
t.Fatalf("failed to connect to admin: %v", err)
|
|
}
|
|
t.Cleanup(func() { conn.Close() })
|
|
|
|
// Read banner
|
|
conn.SetReadDeadline(time.Now().Add(2 * time.Second))
|
|
reader := bufio.NewReader(conn)
|
|
if prompt, err := reader.ReadString(' '); err != nil || prompt != "Password: " {
|
|
t.Fatalf("expected password prompt, got %q (err: %v)", prompt, err)
|
|
}
|
|
fmt.Fprintf(conn, "%s\r\n", testAdminPassword)
|
|
reader.ReadString('\n') // Authenticated.
|
|
reader.ReadString('\n') // BetterDesk Admin Console
|
|
reader.ReadString('\n') // Type 'help'...
|
|
reader.ReadString('\n') // blank line
|
|
|
|
return conn
|
|
}
|
|
|
|
func sendCommand(conn net.Conn, cmd string) string {
|
|
// Read the prompt first
|
|
buf := make([]byte, 4096)
|
|
conn.SetReadDeadline(time.Now().Add(500 * time.Millisecond))
|
|
conn.Read(buf) // "> " prompt
|
|
|
|
// Send command
|
|
fmt.Fprintf(conn, "%s\r\n", cmd)
|
|
|
|
// Read response
|
|
conn.SetReadDeadline(time.Now().Add(2 * time.Second))
|
|
var response strings.Builder
|
|
for {
|
|
n, err := conn.Read(buf)
|
|
if n > 0 {
|
|
response.Write(buf[:n])
|
|
}
|
|
if err != nil || n == 0 {
|
|
break
|
|
}
|
|
// Check if we've received the next prompt
|
|
if strings.Contains(response.String(), "> ") && response.Len() > 2 {
|
|
break
|
|
}
|
|
}
|
|
return response.String()
|
|
}
|
|
|
|
func TestAdminStatusCommand(t *testing.T) {
|
|
_, port := setupTestAdmin(t)
|
|
conn := connectAdmin(t, port)
|
|
|
|
resp := sendCommand(conn, "status")
|
|
if !strings.Contains(resp, "BetterDesk") {
|
|
t.Errorf("status should contain server name, got: %s", resp)
|
|
}
|
|
if !strings.Contains(resp, "DB Peers") {
|
|
t.Errorf("status should contain peer info, got: %s", resp)
|
|
}
|
|
}
|
|
|
|
func TestAdminHelpCommand(t *testing.T) {
|
|
_, port := setupTestAdmin(t)
|
|
conn := connectAdmin(t, port)
|
|
|
|
resp := sendCommand(conn, "help")
|
|
if !strings.Contains(resp, "Available commands") {
|
|
t.Errorf("help should list commands, got: %s", resp)
|
|
}
|
|
}
|
|
|
|
func TestAdminPeersCount(t *testing.T) {
|
|
_, port := setupTestAdmin(t)
|
|
conn := connectAdmin(t, port)
|
|
|
|
resp := sendCommand(conn, "peers count")
|
|
if !strings.Contains(resp, "Total:") {
|
|
t.Errorf("peers count should show total, got: %s", resp)
|
|
}
|
|
}
|
|
|
|
func TestAdminDisabledByDefault(t *testing.T) {
|
|
cfg := config.DefaultConfig()
|
|
// AdminPort defaults to 0 (disabled)
|
|
database, _ := db.Open(":memory:")
|
|
database.Migrate()
|
|
defer database.Close()
|
|
|
|
srv := New(cfg, database, peer.NewMap(), "test")
|
|
err := srv.Start(context.Background())
|
|
if err != nil {
|
|
t.Errorf("should not error when disabled, got: %v", err)
|
|
}
|
|
srv.Stop()
|
|
}
|
|
|
|
func TestAdminRejectsEnabledPortWithoutPassword(t *testing.T) {
|
|
cfg := config.DefaultConfig()
|
|
cfg.AdminPort = 21115
|
|
|
|
srv := New(cfg, nil, peer.NewMap(), "test")
|
|
if err := srv.Start(context.Background()); err == nil {
|
|
t.Fatal("expected enabled admin interface without password to be rejected")
|
|
}
|
|
}
|