From 1f673f30802b88a426a83683f950eab8376f0ccf Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Mon, 27 Mar 2023 16:58:12 -0400 Subject: [PATCH] refactor(server): use a fixed repos path & drop AccessMethod --- server/backend/access.go | 8 -------- server/backend/file/file.go | 38 ++++++++++++++----------------------- server/backend/noop/noop.go | 7 ------- server/backend/repo.go | 4 ++-- server/cmd/cmd.go | 10 +++++----- server/cmd/list.go | 14 +++++++------- server/cmd/show.go | 7 ++----- server/config/config.go | 22 ++++++--------------- server/daemon.go | 7 ++++--- server/daemon_test.go | 8 +++++++- server/http.go | 13 +++++++------ server/server.go | 13 +++++++++++-- server/session.go | 2 +- server/session_test.go | 9 ++++++++- server/ssh.go | 16 +++++----------- server/utils/utils.go | 14 ++++++++++++++ 16 files changed, 93 insertions(+), 99 deletions(-) create mode 100644 server/utils/utils.go diff --git a/server/backend/access.go b/server/backend/access.go index 52cc16eff8357c46fa84d18bed37336ba81ded82..e13e20fcc5e175ca68f633339a36b293ce39adb9 100644 --- a/server/backend/access.go +++ b/server/backend/access.go @@ -1,7 +1,5 @@ package backend -import "golang.org/x/crypto/ssh" - // AccessLevel is the level of access allowed to a repo. type AccessLevel int @@ -34,9 +32,3 @@ func (a AccessLevel) String() string { return "unknown" } } - -// AccessMethod is an interface that handles repository authorization. -type AccessMethod interface { - // AccessLevel returns the access level for the given repository and key. - AccessLevel(repo string, pk ssh.PublicKey) AccessLevel -} diff --git a/server/backend/file/file.go b/server/backend/file/file.go index 66d19ac4fb08559eaeb7cd0095c6dd55b1db9f01..68e5d92ff701dcadc50ba3939b66e26f560cda51 100644 --- a/server/backend/file/file.go +++ b/server/backend/file/file.go @@ -31,6 +31,7 @@ import ( "github.com/charmbracelet/log" "github.com/charmbracelet/soft-serve/git" "github.com/charmbracelet/soft-serve/server/backend" + "github.com/charmbracelet/soft-serve/server/utils" "github.com/charmbracelet/ssh" gossh "golang.org/x/crypto/ssh" ) @@ -59,8 +60,6 @@ var ( var _ backend.Backend = &FileBackend{} -var _ backend.AccessMethod = &FileBackend{} - // FileBackend is a backend that uses the filesystem. type FileBackend struct { // nolint:revive // path is the path to the directory containing the repositories and config @@ -78,11 +77,6 @@ func (fb *FileBackend) reposPath() string { return filepath.Join(fb.path, repos) } -// RepositoryStorePath returns the path to the repository store. -func (fb *FileBackend) RepositoryStorePath() string { - return fb.reposPath() -} - func (fb *FileBackend) settingsPath() string { return filepath.Join(fb.path, settings) } @@ -95,10 +89,6 @@ func (fb *FileBackend) collabsPath(repo string) string { return filepath.Join(fb.path, collabs, repo, collabs) } -func sanatizeRepo(repo string) string { - return strings.TrimSuffix(repo, ".git") -} - func readOneLine(path string) (string, error) { f, err := os.Open(path) if err != nil { @@ -221,7 +211,7 @@ func (fb *FileBackend) AddAdmin(pk gossh.PublicKey, memo string) error { // // It implements backend.Backend. func (fb *FileBackend) AddCollaborator(pk gossh.PublicKey, memo string, repo string) error { - name := sanatizeRepo(repo) + name := utils.SanitizeRepo(repo) repo = name + ".git" // Check if repo exists if !exists(filepath.Join(fb.reposPath(), repo)) { @@ -278,7 +268,7 @@ func (fb *FileBackend) Admins() ([]string, error) { // // It implements backend.Backend. func (fb *FileBackend) Collaborators(repo string) ([]string, error) { - name := sanatizeRepo(repo) + name := utils.SanitizeRepo(repo) repo = name + ".git" // Check if repo exists if !exists(filepath.Join(fb.reposPath(), repo)) { @@ -359,7 +349,7 @@ func (fb *FileBackend) RemoveAdmin(pk gossh.PublicKey) error { // // It implements backend.Backend. func (fb *FileBackend) RemoveCollaborator(pk gossh.PublicKey, repo string) error { - name := sanatizeRepo(repo) + name := utils.SanitizeRepo(repo) repo = name + ".git" // Check if repo exists if !exists(filepath.Join(fb.reposPath(), repo)) { @@ -458,7 +448,7 @@ func (fb *FileBackend) AnonAccess() backend.AccessLevel { // // It implements backend.Backend. func (fb *FileBackend) Description(repo string) string { - repo = sanatizeRepo(repo) + ".git" + repo = utils.SanitizeRepo(repo) + ".git" r := &Repo{path: filepath.Join(fb.reposPath(), repo), root: fb.reposPath()} return r.Description() } @@ -501,7 +491,7 @@ func (fb *FileBackend) IsAdmin(pk gossh.PublicKey) bool { // // It implements backend.Backend. func (fb *FileBackend) IsCollaborator(pk gossh.PublicKey, repo string) bool { - repo = sanatizeRepo(repo) + ".git" + repo = utils.SanitizeRepo(repo) + ".git" _, err := os.Stat(fb.collabsPath(repo)) if err != nil { return false @@ -532,7 +522,7 @@ func (fb *FileBackend) IsCollaborator(pk gossh.PublicKey, repo string) bool { // // It implements backend.Backend. func (fb *FileBackend) IsPrivate(repo string) bool { - repo = sanatizeRepo(repo) + ".git" + repo = utils.SanitizeRepo(repo) + ".git" r := &Repo{path: filepath.Join(fb.reposPath(), repo), root: fb.reposPath()} return r.IsPrivate() } @@ -569,7 +559,7 @@ func (fb *FileBackend) SetAnonAccess(level backend.AccessLevel) error { // // It implements backend.Backend. func (fb *FileBackend) SetDescription(repo string, desc string) error { - repo = sanatizeRepo(repo) + ".git" + repo = utils.SanitizeRepo(repo) + ".git" f, err := os.OpenFile(filepath.Join(fb.reposPath(), repo, description), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) if err != nil { return fmt.Errorf("failed to open description file: %w", err) @@ -584,7 +574,7 @@ func (fb *FileBackend) SetDescription(repo string, desc string) error { // // It implements backend.Backend. func (fb *FileBackend) SetPrivate(repo string, priv bool) error { - repo = sanatizeRepo(repo) + ".git" + repo = utils.SanitizeRepo(repo) + ".git" daemonExport := filepath.Join(fb.reposPath(), repo, exportOk) if priv { _ = os.Remove(daemonExport) @@ -612,7 +602,7 @@ func (fb *FileBackend) SetPrivate(repo string, priv bool) error { // // It implements backend.Backend. func (fb *FileBackend) CreateRepository(repo string, private bool) (backend.Repository, error) { - name := sanatizeRepo(repo) + name := utils.SanitizeRepo(repo) repo = name + ".git" rp := filepath.Join(fb.reposPath(), repo) if _, err := os.Stat(rp); err == nil { @@ -637,7 +627,7 @@ func (fb *FileBackend) CreateRepository(repo string, private bool) (backend.Repo // // It implements backend.Backend. func (fb *FileBackend) DeleteRepository(repo string) error { - name := sanatizeRepo(repo) + name := utils.SanitizeRepo(repo) delete(fb.repos, name) repo = name + ".git" return os.RemoveAll(filepath.Join(fb.reposPath(), repo)) @@ -647,8 +637,8 @@ func (fb *FileBackend) DeleteRepository(repo string) error { // // It implements backend.Backend. func (fb *FileBackend) RenameRepository(oldName string, newName string) error { - oldName = filepath.Join(fb.reposPath(), sanatizeRepo(oldName)+".git") - newName = filepath.Join(fb.reposPath(), sanatizeRepo(newName)+".git") + oldName = filepath.Join(fb.reposPath(), utils.SanitizeRepo(oldName)+".git") + newName = filepath.Join(fb.reposPath(), utils.SanitizeRepo(newName)+".git") if _, err := os.Stat(oldName); errors.Is(err, os.ErrNotExist) { return fmt.Errorf("repository %q does not exist", strings.TrimSuffix(filepath.Base(oldName), ".git")) } @@ -663,7 +653,7 @@ func (fb *FileBackend) RenameRepository(oldName string, newName string) error { // // It implements backend.Backend. func (fb *FileBackend) Repository(repo string) (backend.Repository, error) { - name := sanatizeRepo(repo) + name := utils.SanitizeRepo(repo) if r, ok := fb.repos[name]; ok { return r, nil } diff --git a/server/backend/noop/noop.go b/server/backend/noop/noop.go index b91ffbee395e9daca5a465a61175d33a90c119e4..db7324bc0b60fe7fa006b70d1d530e94358446af 100644 --- a/server/backend/noop/noop.go +++ b/server/backend/noop/noop.go @@ -14,18 +14,11 @@ var ErrNotImpl = fmt.Errorf("not implemented") var _ backend.Backend = (*Noop)(nil) -var _ backend.AccessMethod = (*Noop)(nil) - // Noop is a backend that does nothing. It's used for testing. type Noop struct { Port string } -// RepositoryStorePath implements backend.Backend -func (*Noop) RepositoryStorePath() string { - return "" -} - // Admins implements backend.Backend func (*Noop) Admins() ([]string, error) { return nil, nil diff --git a/server/backend/repo.go b/server/backend/repo.go index 6cf46c807e7ca42bb0a0deeb32e1a786715a0e8a..05d2de240656cca7749dcec0afb5bdd5d4bfd8e8 100644 --- a/server/backend/repo.go +++ b/server/backend/repo.go @@ -11,8 +11,6 @@ type RepositoryStore interface { Repository(repo string) (Repository, error) // Repositories returns a list of all repositories. Repositories() ([]Repository, error) - // RepositoryStorePath returns the path to the repository store. - RepositoryStorePath() string // CreateRepository creates a new repository. CreateRepository(name string, private bool) (Repository, error) // DeleteRepository deletes a repository. @@ -35,6 +33,8 @@ type RepositoryMetadata interface { // RepositoryAccess is an interface for managing repository access. type RepositoryAccess interface { + // AccessLevel returns the access level for the given repository and key. + AccessLevel(repo string, pk ssh.PublicKey) AccessLevel // IsCollaborator returns true if the authorized key is a collaborator on the repository. IsCollaborator(pk ssh.PublicKey, repo string) bool // AddCollaborator adds the authorized key as a collaborator on the repository. diff --git a/server/cmd/cmd.go b/server/cmd/cmd.go index 44dd4b67153b085ff990dcf6eba4c9f9ec36329f..e32836abb31be39b40829fddcb9f8082aaa8b60e 100644 --- a/server/cmd/cmd.go +++ b/server/cmd/cmd.go @@ -3,10 +3,10 @@ package cmd import ( "context" "fmt" - "strings" "github.com/charmbracelet/soft-serve/server/backend" "github.com/charmbracelet/soft-serve/server/config" + "github.com/charmbracelet/soft-serve/server/utils" "github.com/charmbracelet/ssh" "github.com/charmbracelet/wish" "github.com/spf13/cobra" @@ -75,8 +75,8 @@ func checkIfReadable(cmd *cobra.Command, args []string) error { repo = args[0] } cfg, s := fromContext(cmd) - rn := strings.TrimSuffix(repo, ".git") - auth := cfg.Access.AccessLevel(rn, s.PublicKey()) + rn := utils.SanitizeRepo(repo) + auth := cfg.Backend.AccessLevel(rn, s.PublicKey()) if auth < backend.ReadOnlyAccess { return ErrUnauthorized } @@ -97,8 +97,8 @@ func checkIfCollab(cmd *cobra.Command, args []string) error { repo = args[0] } cfg, s := fromContext(cmd) - rn := strings.TrimSuffix(repo, ".git") - auth := cfg.Access.AccessLevel(rn, s.PublicKey()) + rn := utils.SanitizeRepo(repo) + auth := cfg.Backend.AccessLevel(rn, s.PublicKey()) if auth < backend.ReadWriteAccess { return ErrUnauthorized } diff --git a/server/cmd/list.go b/server/cmd/list.go index 0bec7fd198aeca000b5ed3a02c82d7e31b134f1e..86f95d7770087a404814c5c23313a5ec03ac61bc 100644 --- a/server/cmd/list.go +++ b/server/cmd/list.go @@ -13,21 +13,21 @@ import ( // listCommand returns a command that list file or directory at path. func listCommand() *cobra.Command { listCmd := &cobra.Command{ - Use: "list PATH", - Aliases: []string{"ls"}, - Short: "List files at repository.", - Args: cobra.RangeArgs(0, 1), - PersistentPreRunE: checkIfReadable, + Use: "list PATH", + Aliases: []string{"ls"}, + Short: "List files at repository.", + Args: cobra.RangeArgs(0, 1), RunE: func(cmd *cobra.Command, args []string) error { cfg, s := fromContext(cmd) rn := "" path := "" ps := []string{} if len(args) > 0 { + // FIXME: nested repos are not supported. path = filepath.Clean(args[0]) ps = strings.Split(path, "/") rn = strings.TrimSuffix(ps[0], ".git") - auth := cfg.Access.AccessLevel(rn, s.PublicKey()) + auth := cfg.Backend.AccessLevel(rn, s.PublicKey()) if auth < backend.ReadOnlyAccess { return ErrUnauthorized } @@ -38,7 +38,7 @@ func listCommand() *cobra.Command { return err } for _, r := range repos { - if cfg.Access.AccessLevel(r.Name(), s.PublicKey()) >= backend.ReadOnlyAccess { + if cfg.Backend.AccessLevel(r.Name(), s.PublicKey()) >= backend.ReadOnlyAccess { cmd.Println(r.Name()) } } diff --git a/server/cmd/show.go b/server/cmd/show.go index cab515e4f78b2d2c7a8a0a922a2da2ae10302293..14981dc27d95c1ecaa97ec189c3b615e57dbf5b8 100644 --- a/server/cmd/show.go +++ b/server/cmd/show.go @@ -33,14 +33,11 @@ func showCommand() *cobra.Command { Args: cobra.ExactArgs(1), PersistentPreRunE: checkIfReadable, RunE: func(cmd *cobra.Command, args []string) error { - cfg, s := fromContext(cmd) + cfg, _ := fromContext(cmd) + // FIXME: nested repos are not supported. ps := strings.Split(args[0], "/") rn := strings.TrimSuffix(ps[0], ".git") fp := strings.Join(ps[1:], "/") - auth := cfg.Access.AccessLevel(rn, s.PublicKey()) - if auth < backend.ReadOnlyAccess { - return ErrUnauthorized - } var repo backend.Repository repoExists := false repos, err := cfg.Backend.Repositories() diff --git a/server/config/config.go b/server/config/config.go index e005cdea7a879bbe6711a0f98d785f48465dad8a..8595fe48a599007d4425bfcfcd5ea1e75519309a 100644 --- a/server/config/config.go +++ b/server/config/config.go @@ -6,7 +6,6 @@ import ( "github.com/caarlos0/env/v6" "github.com/charmbracelet/log" "github.com/charmbracelet/soft-serve/server/backend" - "github.com/charmbracelet/soft-serve/server/backend/file" ) // SSHConfig is the configuration for the SSH server. @@ -20,6 +19,9 @@ type SSHConfig struct { // KeyPath is the path to the SSH server's private key. KeyPath string `env:"KEY_PATH"` + // InternalKeyPath is the path to the SSH server's internal private key. + InternalKeyPath string `env:"INTERNAL_KEY_PATH"` + // MaxTimeout is the maximum number of seconds a connection can take. MaxTimeout int `env:"MAX_TIMEOUT" envDefault:"0"` @@ -73,9 +75,6 @@ type Config struct { // Backend is the Git backend to use. Backend backend.Backend - - // Access is the access control backend to use. - Access backend.AccessMethod } // DefaultConfig returns a Config with the values populated with the defaults @@ -90,13 +89,10 @@ func DefaultConfig() *Config { if cfg.SSH.KeyPath == "" { cfg.SSH.KeyPath = filepath.Join(cfg.DataPath, "ssh", "soft_serve") } - fb, err := file.NewFileBackend(cfg.DataPath) - if err != nil { - log.Fatal(err) + if cfg.SSH.InternalKeyPath == "" { + cfg.SSH.InternalKeyPath = filepath.Join(cfg.DataPath, "ssh", "soft_serve_internal") } - // Add the initial admin keys to the list of admins. - fb.AdditionalAdmins = cfg.InitialAdminKeys - return cfg.WithBackend(fb).WithAccessMethod(fb) + return cfg } // WithBackend sets the backend for the configuration. @@ -104,9 +100,3 @@ func (c *Config) WithBackend(backend backend.Backend) *Config { c.Backend = backend return c } - -// WithAccessMethod sets the access control method for the configuration. -func (c *Config) WithAccessMethod(access backend.AccessMethod) *Config { - c.Access = access - return c -} diff --git a/server/daemon.go b/server/daemon.go index 789f1e9bd39b2adc7fd4ce7152d3b1844e2f4195..86ad93e4167f908198a177fd176b9c79079db47d 100644 --- a/server/daemon.go +++ b/server/daemon.go @@ -12,6 +12,7 @@ import ( "github.com/charmbracelet/soft-serve/server/backend" "github.com/charmbracelet/soft-serve/server/config" + "github.com/charmbracelet/soft-serve/server/utils" "github.com/go-git/go-git/v5/plumbing/format/pktline" ) @@ -201,19 +202,19 @@ func (d *GitDaemon) handleClient(conn net.Conn) { return } - name := sanitizeRepoName(string(opts[0])) + name := utils.SanitizeRepo(string(opts[0])) logger.Debugf("git: connect %s %s %s", c.RemoteAddr(), cmd, name) defer logger.Debugf("git: disconnect %s %s %s", c.RemoteAddr(), cmd, name) // git bare repositories should end in ".git" // https://git-scm.com/docs/gitrepository-layout repo := name + ".git" - reposDir := d.cfg.Backend.RepositoryStorePath() + reposDir := filepath.Join(d.cfg.DataPath, "repos") if err := ensureWithin(reposDir, repo); err != nil { fatal(c, err) return } - auth := d.cfg.Access.AccessLevel(name, nil) + auth := d.cfg.Backend.AccessLevel(name, nil) if auth < backend.ReadOnlyAccess { fatal(c, ErrNotAuthed) return diff --git a/server/daemon_test.go b/server/daemon_test.go index e2e059e42cd6553cf5b707a386061204a1852b98..220624000d8cd733a1686f0bcb38214376059db3 100644 --- a/server/daemon_test.go +++ b/server/daemon_test.go @@ -8,10 +8,12 @@ import ( "log" "net" "os" + "path/filepath" "strings" "testing" "time" + "github.com/charmbracelet/soft-serve/server/backend/file" "github.com/charmbracelet/soft-serve/server/config" "github.com/go-git/go-git/v5/plumbing/format/pktline" ) @@ -29,7 +31,11 @@ func TestMain(m *testing.M) { os.Setenv("SOFT_SERVE_GIT_MAX_TIMEOUT", "100") os.Setenv("SOFT_SERVE_GIT_IDLE_TIMEOUT", "1") os.Setenv("SOFT_SERVE_GIT_LISTEN_ADDR", fmt.Sprintf(":%d", randomPort())) - cfg := config.DefaultConfig() + fb, err := file.NewFileBackend(filepath.Join(tmp, "repos")) + if err != nil { + log.Fatal(err) + } + cfg := config.DefaultConfig().WithBackend(fb).WithAccessMethod(fb) d, err := NewGitDaemon(cfg) if err != nil { log.Fatal(err) diff --git a/server/http.go b/server/http.go index fdc58c551f394e920852a1fe47cb337501b956ef..5c7d168840031de3122b15441728c77e4fd11562 100644 --- a/server/http.go +++ b/server/http.go @@ -13,6 +13,7 @@ import ( "github.com/charmbracelet/soft-serve/server/backend" "github.com/charmbracelet/soft-serve/server/config" + "github.com/charmbracelet/soft-serve/server/utils" "github.com/dustin/go-humanize" "goji.io" "goji.io/pat" @@ -68,7 +69,7 @@ func NewHTTPServer(cfg *config.Config) (*HTTPServer, error) { mux := goji.NewMux() s := &HTTPServer{ cfg: cfg, - dirHandler: http.FileServer(http.Dir(cfg.Backend.RepositoryStorePath())), + dirHandler: http.FileServer(http.Dir(filepath.Join(cfg.DataPath, "repos"))), server: &http.Server{ Addr: cfg.HTTP.ListenAddr, Handler: mux, @@ -114,7 +115,7 @@ Redirecting to docs at god func (s *HTTPServer) repoIndexHandler(w http.ResponseWriter, r *http.Request) { repo := pat.Param(r, "repo") - repo = sanitizeRepoName(repo) + repo = utils.SanitizeRepo(repo) // Only respond to go-get requests if r.URL.Query().Get("go-get") != "1" { @@ -122,7 +123,7 @@ func (s *HTTPServer) repoIndexHandler(w http.ResponseWriter, r *http.Request) { return } - access := s.cfg.Access.AccessLevel(repo, nil) + access := s.cfg.Backend.AccessLevel(repo, nil) if access < backend.ReadOnlyAccess { http.NotFound(w, r) return @@ -149,16 +150,16 @@ func (s *HTTPServer) repoIndexHandler(w http.ResponseWriter, r *http.Request) { func (s *HTTPServer) dumbGitHandler(w http.ResponseWriter, r *http.Request) { repo := pat.Param(r, "repo") - repo = sanitizeRepoName(repo) + ".git" + repo = utils.SanitizeRepo(repo) + ".git" - access := s.cfg.Access.AccessLevel(repo, nil) + access := s.cfg.Backend.AccessLevel(repo, nil) if access < backend.ReadOnlyAccess || !s.cfg.Backend.AllowKeyless() { httpStatusError(w, http.StatusUnauthorized) return } path := pattern.Path(r.Context()) - stat, err := os.Stat(filepath.Join(s.cfg.Backend.RepositoryStorePath(), repo, path)) + stat, err := os.Stat(filepath.Join(s.cfg.DataPath, "repos", repo, path)) // Restrict access to files if err != nil || stat.IsDir() { http.NotFound(w, r) diff --git a/server/server.go b/server/server.go index f1c703af2a1e195c530a13a87db7d369e052b22a..5cfa6192a967f08123e8334ce80a4b98743df35e 100644 --- a/server/server.go +++ b/server/server.go @@ -7,6 +7,7 @@ import ( "github.com/charmbracelet/log" "github.com/charmbracelet/soft-serve/server/backend" + "github.com/charmbracelet/soft-serve/server/backend/file" "github.com/charmbracelet/soft-serve/server/config" "github.com/charmbracelet/ssh" "golang.org/x/sync/errgroup" @@ -23,7 +24,6 @@ type Server struct { HTTPServer *HTTPServer Config *config.Config Backend backend.Backend - Access backend.AccessMethod } // NewServer returns a new *ssh.Server configured to serve Soft Serve. The SSH @@ -33,10 +33,19 @@ type Server struct { // publicly writable until configured otherwise by cloning the `config` repo. func NewServer(cfg *config.Config) (*Server, error) { var err error + if cfg.Backend == nil { + fb, err := file.NewFileBackend(cfg.DataPath) + if err != nil { + logger.Fatal(err) + } + // Add the initial admin keys to the list of admins. + fb.AdditionalAdmins = cfg.InitialAdminKeys + cfg = cfg.WithBackend(fb) + } + srv := &Server{ Config: cfg, Backend: cfg.Backend, - Access: cfg.Access, } srv.SSHServer, err = NewSSHServer(cfg) if err != nil { diff --git a/server/session.go b/server/session.go index 56bc02ed88ef87d9aa1474dcc99435627e0f4b20..03df433ddb530e753e083af6c55f4c4f96d9c044 100644 --- a/server/session.go +++ b/server/session.go @@ -26,7 +26,7 @@ func SessionHandler(cfg *config.Config) bm.ProgramHandler { initialRepo := "" if len(cmd) == 1 { initialRepo = cmd[0] - auth := cfg.Access.AccessLevel(initialRepo, s.PublicKey()) + auth := cfg.Backend.AccessLevel(initialRepo, s.PublicKey()) if auth < backend.ReadOnlyAccess { wish.Fatalln(s, cm.ErrUnauthorized) return nil diff --git a/server/session_test.go b/server/session_test.go index bd81a2320ba8fa14c2f5712b4154a8e6aa98ed76..a120321a266005fdf6d98f6504dde1523e5ffc4b 100644 --- a/server/session_test.go +++ b/server/session_test.go @@ -3,10 +3,13 @@ package server import ( "errors" "fmt" + "log" "os" + "path/filepath" "testing" "time" + "github.com/charmbracelet/soft-serve/server/backend/file" "github.com/charmbracelet/soft-serve/server/config" "github.com/charmbracelet/ssh" bm "github.com/charmbracelet/wish/bubbletea" @@ -49,7 +52,11 @@ func setup(tb testing.TB) *gossh.Session { is.NoErr(os.Unsetenv("SOFT_SERVE_SSH_LISTEN_ADDR")) is.NoErr(os.RemoveAll(dp)) }) - cfg := config.DefaultConfig() + fb, err := file.NewFileBackend(filepath.Join(dp, "repos")) + if err != nil { + log.Fatal(err) + } + cfg := config.DefaultConfig().WithBackend(fb).WithAccessMethod(fb) return testsession.New(tb, &ssh.Server{ Handler: bm.MiddlewareWithProgramHandler(SessionHandler(cfg), termenv.ANSI256)(func(s ssh.Session) { _, _, active := s.Pty() diff --git a/server/ssh.go b/server/ssh.go index 25dcdaed86d01b6e9c3d18263f27bab7037aeafd..61e8f7f54c7072866f7927b583375b6cfb11802b 100644 --- a/server/ssh.go +++ b/server/ssh.go @@ -12,6 +12,7 @@ import ( "github.com/charmbracelet/soft-serve/server/backend" cm "github.com/charmbracelet/soft-serve/server/cmd" "github.com/charmbracelet/soft-serve/server/config" + "github.com/charmbracelet/soft-serve/server/utils" "github.com/charmbracelet/ssh" "github.com/charmbracelet/wish" bm "github.com/charmbracelet/wish/bubbletea" @@ -88,7 +89,7 @@ func (s *SSHServer) Shutdown(ctx context.Context) error { // PublicKeyAuthHandler handles public key authentication. func (s *SSHServer) PublicKeyHandler(ctx ssh.Context, pk ssh.PublicKey) bool { - return s.cfg.Access.AccessLevel("", pk) > backend.NoAccess + return s.cfg.Backend.AccessLevel("", pk) > backend.NoAccess } // KeyboardInteractiveHandler handles keyboard interactive authentication. @@ -109,14 +110,14 @@ func (s *SSHServer) Middleware(cfg *config.Config) wish.Middleware { if len(cmd) >= 2 && strings.HasPrefix(cmd[0], "git") { gc := cmd[0] // repo should be in the form of "repo.git" - name := sanitizeRepoName(cmd[1]) + name := utils.SanitizeRepo(cmd[1]) pk := s.PublicKey() - access := cfg.Access.AccessLevel(name, pk) + access := cfg.Backend.AccessLevel(name, pk) // git bare repositories should end in ".git" // https://git-scm.com/docs/gitrepository-layout repo := name + ".git" - reposDir := cfg.Backend.RepositoryStorePath() + reposDir := filepath.Join(cfg.DataPath, "repos") if err := ensureWithin(reposDir, repo); err != nil { sshFatal(s, err) return @@ -168,10 +169,3 @@ func sshFatal(s ssh.Session, v ...interface{}) { WritePktline(s, v...) s.Exit(1) // nolint: errcheck } - -func sanitizeRepoName(repo string) string { - repo = strings.TrimPrefix(repo, "/") - repo = filepath.Clean(repo) - repo = strings.TrimSuffix(repo, ".git") - return repo -} diff --git a/server/utils/utils.go b/server/utils/utils.go new file mode 100644 index 0000000000000000000000000000000000000000..3f2fe5dabe16b0daa646a56d1bf3e01c0b5572cb --- /dev/null +++ b/server/utils/utils.go @@ -0,0 +1,14 @@ +package utils + +import ( + "path/filepath" + "strings" +) + +// SanitizeRepo returns a sanitized version of the given repository name. +func SanitizeRepo(repo string) string { + repo = strings.TrimPrefix(repo, "/") + repo = filepath.Clean(repo) + repo = strings.TrimSuffix(repo, ".git") + return repo +}