4f07abf55c5324ab16a7ee2c3bfec428dfbd1573

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

Message

feat(backend): collabs & admins interface

Diff

This diff is truncated to protect this page.

  1diff --git a/server/backend/backend.go b/server/backend/backend.go
  2index e43b839411d11615faeadd4aac39394c2391c6c3..5d771e35720b41012ad29c2e7c684ff62335fac2 100644
  3--- a/server/backend/backend.go
  4+++ b/server/backend/backend.go
  5@@ -9,56 +9,10 @@ import (
  6 // Backend is an interface that handles repositories management and any
  7 // non-Git related operations.
  8 type Backend interface {
  9-	// ServerName returns the server's name.
 10-	ServerName() string
 11-	// SetServerName sets the server's name.
 12-	SetServerName(name string) error
 13-	// ServerHost returns the server's host.
 14-	ServerHost() string
 15-	// SetServerHost sets the server's host.
 16-	SetServerHost(host string) error
 17-	// ServerPort returns the server's port.
 18-	ServerPort() string
 19-	// SetServerPort sets the server's port.
 20-	SetServerPort(port string) error
 21-
 22-	// AnonAccess returns the access level for anonymous users.
 23-	AnonAccess() AccessLevel
 24-	// SetAnonAccess sets the access level for anonymous users.
 25-	SetAnonAccess(level AccessLevel) error
 26-	// AllowKeyless returns true if keyless access is allowed.
 27-	AllowKeyless() bool
 28-	// SetAllowKeyless sets whether or not keyless access is allowed.
 29-	SetAllowKeyless(allow bool) error
 30-
 31-	// Repository finds the given repository.
 32-	Repository(repo string) (Repository, error)
 33-	// Repositories returns a list of all repositories.
 34-	Repositories() ([]Repository, error)
 35-	// CreateRepository creates a new repository.
 36-	CreateRepository(name string, private bool) (Repository, error)
 37-	// DeleteRepository deletes a repository.
 38-	DeleteRepository(name string) error
 39-	// RenameRepository renames a repository.
 40-	RenameRepository(oldName, newName string) error
 41-
 42-	// Description returns the repo's description.
 43-	Description(repo string) string
 44-	// SetDescription sets the repo's description.
 45-	SetDescription(repo, desc string) error
 46-	// IsPrivate returns true if the repository is private.
 47-	IsPrivate(repo string) bool
 48-	// SetPrivate sets the repository's private status.
 49-	SetPrivate(repo string, priv bool) error
 50-
 51-	// IsCollaborator returns true if the authorized key is a collaborator on the repository.
 52-	IsCollaborator(pk ssh.PublicKey, repo string) bool
 53-	// AddCollaborator adds the authorized key as a collaborator on the repository.
 54-	AddCollaborator(pk ssh.PublicKey, repo string) error
 55-	// IsAdmin returns true if the authorized key is an admin.
 56-	IsAdmin(pk ssh.PublicKey) bool
 57-	// AddAdmin adds the authorized key as an admin.
 58-	AddAdmin(pk ssh.PublicKey) error
 59+	ServerBackend
 60+	RepositoryStore
 61+	RepositoryMetadata
 62+	RepositoryAccess
 63 }
 64 
 65 // ParseAuthorizedKey parses an authorized key string into a public key.
 66diff --git a/server/backend/file/file.go b/server/backend/file/file.go
 67index da8196d63d0688a97ed267aa9ae7d130a90867c0..05949556bb186e35b05fe5e7e144b37fb46a5eb0 100644
 68--- a/server/backend/file/file.go
 69+++ b/server/backend/file/file.go
 70@@ -89,7 +89,7 @@ func (fb *FileBackend) adminsPath() string {
 71 }
 72 
 73 func (fb *FileBackend) collabsPath(repo string) string {
 74-	return filepath.Join(fb.reposPath(), repo, collabs)
 75+	return filepath.Join(fb.path, collabs, repo)
 76 }
 77 
 78 func sanatizeRepo(repo string) string {
 79@@ -117,10 +117,16 @@ func readAll(path string) (string, error) {
 80 	return string(bts), err
 81 }
 82 
 83+// exists returns true if the given path exists.
 84+func exists(path string) bool {
 85+	_, err := os.Stat(path)
 86+	return err == nil
 87+}
 88+
 89 // NewFileBackend creates a new FileBackend.
 90 func NewFileBackend(path string) (*FileBackend, error) {
 91 	fb := &FileBackend{path: path}
 92-	for _, dir := range []string{repos, settings} {
 93+	for _, dir := range []string{repos, settings, collabs} {
 94 		if err := os.MkdirAll(filepath.Join(path, dir), 0755); err != nil {
 95 			return nil, err
 96 		}
 97@@ -181,10 +187,10 @@ func (fb *FileBackend) AccessLevel(repo string, pk gossh.PublicKey) backend.Acce
 98 // AddAdmin adds a public key to the list of server admins.
 99 //
100 // It implements backend.Backend.
101-func (fb *FileBackend) AddAdmin(pk gossh.PublicKey) error {
102+func (fb *FileBackend) AddAdmin(pk gossh.PublicKey, memo string) error {
103 	// Skip if the key already exists.
104 	if fb.IsAdmin(pk) {
105-		return nil
106+		return fmt.Errorf("key already exists")
107 	}
108 
109 	ak := backend.MarshalAuthorizedKey(pk)
110@@ -195,32 +201,206 @@ func (fb *FileBackend) AddAdmin(pk gossh.PublicKey) error {
111 	}
112 
113 	defer f.Close() //nolint:errcheck
114-	_, err = fmt.Fprintln(f, ak)
115+	if memo != "" {
116+		memo = " " + memo
117+	}
118+	_, err = fmt.Fprintf(f, "%s%s\n", ak, memo)
119 	return err
120 }
121 
122 // AddCollaborator adds a public key to the list of collaborators for the given repo.
123 //
124 // It implements backend.Backend.
125-func (fb *FileBackend) AddCollaborator(pk gossh.PublicKey, repo string) error {
126+func (fb *FileBackend) AddCollaborator(pk gossh.PublicKey, memo string, name string) error {
127+	// Check if repo exists
128+	if !exists(filepath.Join(fb.reposPath(), sanatizeRepo(name)+".git")) {
129+		return fmt.Errorf("repository %s does not exist", name)
130+	}
131+
132 	// Skip if the key already exists.
133-	if fb.IsCollaborator(pk, repo) {
134-		return nil
135+	if fb.IsCollaborator(pk, name) {
136+		return fmt.Errorf("key already exists")
137 	}
138 
139 	ak := backend.MarshalAuthorizedKey(pk)
140-	repo = sanatizeRepo(repo) + ".git"
141-	f, err := os.OpenFile(fb.collabsPath(repo), os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0644)
142+	name = sanatizeRepo(name)
143+	if err := os.MkdirAll(filepath.Dir(fb.collabsPath(name)), 0755); err != nil {
144+		logger.Debug("failed to create collaborators directory",
145+			"err", err, "path", filepath.Dir(fb.collabsPath(name)))
146+		return err
147+	}
148+
149+	f, err := os.OpenFile(fb.collabsPath(name), os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0644)
150 	if err != nil {
151-		logger.Debug("failed to open collaborators file", "err", err, "path", fb.collabsPath(repo))
152+		logger.Debug("failed to open collaborators file", "err", err, "path", fb.collabsPath(name))
153 		return err
154 	}
155 
156 	defer f.Close() //nolint:errcheck
157-	_, err = fmt.Fprintln(f, ak)
158+	if memo != "" {
159+		memo = " " + memo
160+	}
161+	_, err = fmt.Fprintf(f, "%s%s\n", ak, memo)
162 	return err
163 }
164 
165+// Admins returns a list of public keys that are admins.
166+//
167+// It implements backend.Backend.
168+func (fb *FileBackend) Admins() ([]string, error) {
169+	admins := make([]string, 0)
170diff --git a/server/backend/noop/noop.go b/server/backend/noop/noop.go
171index 72c863b9503fedbba5215300b7cc222edd235ee1..45cf143964aeb2e7f1ba098c0f061b064f06fde5 100644
172--- a/server/backend/noop/noop.go
173+++ b/server/backend/noop/noop.go
174@@ -21,18 +21,38 @@ type Noop struct {
175 	Port string
176 }
177 
178+// Admins implements backend.Backend
179+func (*Noop) Admins() ([]string, error) {
180+	return nil, nil
181+}
182+
183+// Collaborators implements backend.Backend
184+func (*Noop) Collaborators(repo string) ([]string, error) {
185+	return nil, nil
186+}
187+
188+// RemoveAdmin implements backend.Backend
189+func (*Noop) RemoveAdmin(pk ssh.PublicKey) error {
190+	return nil
191+}
192+
193+// RemoveCollaborator implements backend.Backend
194+func (*Noop) RemoveCollaborator(pk ssh.PublicKey, repo string) error {
195+	return nil
196+}
197+
198 // AccessLevel implements backend.AccessMethod
199 func (*Noop) AccessLevel(repo string, pk ssh.PublicKey) backend.AccessLevel {
200 	return backend.AdminAccess
201 }
202 
203 // AddAdmin implements backend.Backend
204-func (*Noop) AddAdmin(pk ssh.PublicKey) error {
205+func (*Noop) AddAdmin(pk ssh.PublicKey, memo string) error {
206 	return ErrNotImpl
207 }
208 
209 // AddCollaborator implements backend.Backend
210-func (*Noop) AddCollaborator(pk ssh.PublicKey, repo string) error {
211+func (*Noop) AddCollaborator(pk ssh.PublicKey, memo string, repo string) error {
212 	return ErrNotImpl
213 }
214 
215diff --git a/server/backend/repo.go b/server/backend/repo.go
216index 61bd6c1ea14c7378d84f03ec9bf23002900cd58f..d9238ae762438130e9260e421112b086a0bc3b8e 100644
217--- a/server/backend/repo.go
218+++ b/server/backend/repo.go
219@@ -1,6 +1,55 @@
220 package backend
221 
222-import "github.com/charmbracelet/soft-serve/git"
223+import (
224+	"github.com/charmbracelet/soft-serve/git"
225+	"golang.org/x/crypto/ssh"
226+)
227+
228+// RepositoryStore is an interface for managing repositories.
229+type RepositoryStore interface {
230+	// Repository finds the given repository.
231+	Repository(repo string) (Repository, error)
232+	// Repositories returns a list of all repositories.
233+	Repositories() ([]Repository, error)
234+	// CreateRepository creates a new repository.
235+	CreateRepository(name string, private bool) (Repository, error)
236+	// DeleteRepository deletes a repository.
237+	DeleteRepository(name string) error
238+	// RenameRepository renames a repository.
239+	RenameRepository(oldName, newName string) error
240+}
241+
242+// RepositoryMetadata is an interface for managing repository metadata.
243+type RepositoryMetadata interface {
244+	// Description returns the repository's description.
245+	Description(repo string) string
246+	// SetDescription sets the repository's description.
247+	SetDescription(repo, desc string) error
248+	// IsPrivate returns whether the repository is private.
249+	IsPrivate(repo string) bool
250+	// SetPrivate sets whether the repository is private.
251+	SetPrivate(repo string, private bool) error
252+}
253+
254+// RepositoryAccess is an interface for managing repository access.
255+type RepositoryAccess interface {
256+	// IsCollaborator returns true if the authorized key is a collaborator on the repository.
257+	IsCollaborator(pk ssh.PublicKey, repo string) bool
258+	// AddCollaborator adds the authorized key as a collaborator on the repository.
259+	AddCollaborator(pk ssh.PublicKey, memo string, repo string) error
260+	// RemoveCollaborator removes the authorized key as a collaborator on the repository.
261+	RemoveCollaborator(pk ssh.PublicKey, repo string) error
262+	// Collaborators returns a list of all collaborators on the repository.
263+	Collaborators(repo string) ([]string, error)
264+	// IsAdmin returns true if the authorized key is an admin.
265+	IsAdmin(pk ssh.PublicKey) bool
266+	// AddAdmin adds the authorized key as an admin.
267+	AddAdmin(pk ssh.PublicKey, memo string) error
268+	// RemoveAdmin removes the authorized key as an admin.
269+	RemoveAdmin(pk ssh.PublicKey) error
270+	// Admins returns a list of all admins.
271+	Admins() ([]string, error)
272+}
273 
274 // Repository is a Git repository interface.
275 type Repository interface {
276diff --git a/server/backend/server.go b/server/backend/server.go
277new file mode 100644
278index 0000000000000000000000000000000000000000..97c02b0966e631ac09139960bd3f09d94e06a560
279--- /dev/null
280+++ b/server/backend/server.go
281@@ -0,0 +1,26 @@
282+package backend
283+
284+// ServerBackend is an interface that handles server configuration.
285+type ServerBackend interface {
286+	// ServerName returns the server's name.
287+	ServerName() string
288+	// SetServerName sets the server's name.
289+	SetServerName(name string) error
290+	// ServerHost returns the server's host.
291+	ServerHost() string
292+	// SetServerHost sets the server's host.
293+	SetServerHost(host string) error
294+	// ServerPort returns the server's port.
295+	ServerPort() string
296+	// SetServerPort sets the server's port.
297+	SetServerPort(port string) error
298+
299+	// AnonAccess returns the access level for anonymous users.
300+	AnonAccess() AccessLevel
301+	// SetAnonAccess sets the access level for anonymous users.
302+	SetAnonAccess(level AccessLevel) error
303+	// AllowKeyless returns true if keyless access is allowed.
304+	AllowKeyless() bool
305+	// SetAllowKeyless sets whether or not keyless access is allowed.
306+	SetAllowKeyless(allow bool) error
307+}