18d598959dbf8a192efab935b989a6a8bb9790d0

Author
Ayman Bagabas <ayman.bagabas@gmail.com>
Committer
Ayman Bagabas <ayman.bagabas@gmail.com>
Date

Message

fix(session): unauthorized access to private repos

Prevent unauthorized ui access to private repos.

Fixes: https://github.com/charmbracelet/soft-serve/issues/189

Diff

  1diff --git a/server/server_test.go b/server/server_test.go
  2index d7dcf9b8b21cedaba940b88c709c172b94bd9bfc..6f126ce9bbd81b60f608d936f05996d57914339f 100644
  3--- a/server/server_test.go
  4+++ b/server/server_test.go
  5@@ -99,7 +99,9 @@ func setupServer(t *testing.T) *Server {
  6 	go func() {
  7 		s.Start()
  8 	}()
  9-	defer s.Close()
 10+	t.Cleanup(func() {
 11+		s.Close()
 12+	})
 13 	return s
 14 }
 15 
 16diff --git a/server/session.go b/server/session.go
 17index 96aeba4a919ecf6afa290219cb4efdd74b2da904..a645c09c97948ba19ddcd231e427b241f2b44875 100644
 18--- a/server/session.go
 19+++ b/server/session.go
 20@@ -6,11 +6,14 @@ import (
 21 	"github.com/aymanbagabas/go-osc52"
 22 	tea "github.com/charmbracelet/bubbletea"
 23 	appCfg "github.com/charmbracelet/soft-serve/config"
 24+	cm "github.com/charmbracelet/soft-serve/server/cmd"
 25 	"github.com/charmbracelet/soft-serve/ui"
 26 	"github.com/charmbracelet/soft-serve/ui/common"
 27 	"github.com/charmbracelet/soft-serve/ui/keymap"
 28 	"github.com/charmbracelet/soft-serve/ui/styles"
 29+	"github.com/charmbracelet/wish"
 30 	bm "github.com/charmbracelet/wish/bubbletea"
 31+	gm "github.com/charmbracelet/wish/git"
 32 	"github.com/gliderlabs/ssh"
 33 	zone "github.com/lrstanley/bubblezone"
 34 )
 35@@ -26,6 +29,11 @@ func SessionHandler(ac *appCfg.Config) bm.ProgramHandler {
 36 		initialRepo := ""
 37 		if len(cmd) == 1 {
 38 			initialRepo = cmd[0]
 39+			auth := ac.AuthRepo(initialRepo, s.PublicKey())
 40+			if auth < gm.ReadOnlyAccess {
 41+				wish.Fatalln(s, cm.ErrUnauthorized)
 42+				return nil
 43+			}
 44 		}
 45 		if ac.Cfg.Callbacks != nil {
 46 			ac.Cfg.Callbacks.Tui("new session")
 47diff --git a/server/session_test.go b/server/session_test.go
 48new file mode 100644
 49index 0000000000000000000000000000000000000000..3257be89c852af1612b7b8a8349fed8e2c4a0001
 50--- /dev/null
 51+++ b/server/session_test.go
 52@@ -0,0 +1,77 @@
 53+package server
 54+
 55+import (
 56+	"bytes"
 57+	"errors"
 58+	"os"
 59+	"strings"
 60+	"testing"
 61+	"time"
 62+
 63+	appCfg "github.com/charmbracelet/soft-serve/config"
 64+	cm "github.com/charmbracelet/soft-serve/server/cmd"
 65+	"github.com/charmbracelet/soft-serve/server/config"
 66+	bm "github.com/charmbracelet/wish/bubbletea"
 67+	"github.com/charmbracelet/wish/testsession"
 68+	"github.com/gliderlabs/ssh"
 69+	"github.com/matryer/is"
 70+	"github.com/muesli/termenv"
 71+	gossh "golang.org/x/crypto/ssh"
 72+)
 73+
 74+func TestSession(t *testing.T) {
 75+	is := is.New(t)
 76+	t.Run("unauthorized repo access", func(t *testing.T) {
 77+		var out bytes.Buffer
 78+		s := setup(t)
 79+		s.Stderr = &out
 80+		defer s.Close()
 81+		err := s.RequestPty("xterm", 80, 40, nil)
 82+		is.NoErr(err)
 83+		err = s.Run("config")
 84+		// Session writes error and exits
 85+		is.True(strings.Contains(out.String(), cm.ErrUnauthorized.Error()))
 86+		var ee *gossh.ExitError
 87+		is.True(errors.As(err, &ee) && ee.ExitStatus() == 1)
 88+	})
 89+	t.Run("authorized repo access", func(t *testing.T) {
 90+		s := setup(t)
 91+		s.Stderr = os.Stderr
 92+		defer s.Close()
 93+		err := s.RequestPty("xterm", 80, 40, nil)
 94+		is.NoErr(err)
 95+		go func() {
 96+			time.Sleep(1 * time.Second)
 97+			s.Signal(gossh.SIGTERM)
 98+			// FIXME: exit with code 0 instead of forcibly closing the session
 99+			s.Close()
100+		}()
101+		err = s.Run("test")
102+		var ee *gossh.ExitMissingError
103+		is.True(errors.As(err, &ee))
104+	})
105+}
106+
107+func setup(tb testing.TB) *gossh.Session {
108+	is := is.New(tb)
109+	tb.Helper()
110+	cfg.RepoPath = tb.TempDir()
111+	ac, err := appCfg.NewConfig(&config.Config{
112+		Port:     22226,
113+		KeyPath:  tb.TempDir(),
114+		RepoPath: tb.TempDir(),
115+		InitialAdminKeys: []string{
116+			"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMJlb/qf2B2kMNdBxfpCQqI2ctPcsOkdZGVh5zTRhKtH",
117+		},
118+	})
119+	ac.AnonAccess = "read-only"
120+	is.NoErr(err)
121+	return testsession.New(tb, &ssh.Server{
122+		Handler: bm.MiddlewareWithProgramHandler(SessionHandler(ac), termenv.ANSI256)(func(s ssh.Session) {
123+			_, _, active := s.Pty()
124+			tb.Logf("PTY active %v", active)
125+			tb.Log(s.Command())
126+			s.Exit(0)
127+		}),
128+	}, nil)
129+}