user.go

  1package backend
  2
  3import (
  4	"context"
  5	"strings"
  6
  7	"github.com/charmbracelet/soft-serve/server/access"
  8	"github.com/charmbracelet/soft-serve/server/db"
  9	"github.com/charmbracelet/soft-serve/server/db/models"
 10	"github.com/charmbracelet/soft-serve/server/proto"
 11	"github.com/charmbracelet/soft-serve/server/sshutils"
 12	"github.com/charmbracelet/soft-serve/server/utils"
 13	"golang.org/x/crypto/ssh"
 14)
 15
 16// AccessLevel returns the access level of a user for a repository.
 17//
 18// It implements backend.Backend.
 19func (d *Backend) AccessLevel(ctx context.Context, repo string, username string) access.AccessLevel {
 20	user, _ := d.User(ctx, username)
 21	return d.AccessLevelForUser(ctx, repo, user)
 22}
 23
 24// AccessLevelByPublicKey returns the access level of a user's public key for a repository.
 25//
 26// It implements backend.Backend.
 27func (d *Backend) AccessLevelByPublicKey(ctx context.Context, repo string, pk ssh.PublicKey) access.AccessLevel {
 28	for _, k := range d.cfg.AdminKeys() {
 29		if sshutils.KeysEqual(pk, k) {
 30			return access.AdminAccess
 31		}
 32	}
 33
 34	user, _ := d.UserByPublicKey(ctx, pk)
 35	if user != nil {
 36		return d.AccessLevel(ctx, repo, user.Username())
 37	}
 38
 39	return d.AccessLevel(ctx, repo, "")
 40}
 41
 42// AccessLevelForUser returns the access level of a user for a repository.
 43func (d *Backend) AccessLevelForUser(ctx context.Context, repo string, user proto.User) access.AccessLevel {
 44	var username string
 45	anon := d.AnonAccess(ctx)
 46	if user != nil {
 47		username = user.Username()
 48	}
 49
 50	// If the user is an admin, they have admin access.
 51	if user != nil && user.IsAdmin() {
 52		return access.AdminAccess
 53	}
 54
 55	// If the repository exists, check if the user is a collaborator.
 56	r, _ := d.Repository(ctx, repo)
 57	if r != nil {
 58		// If the user is a collaborator, they have read/write access.
 59		isCollab, _ := d.IsCollaborator(ctx, repo, username)
 60		if isCollab {
 61			if anon > access.ReadWriteAccess {
 62				return anon
 63			}
 64			return access.ReadWriteAccess
 65		}
 66
 67		// If the repository is private, the user has no access.
 68		if r.IsPrivate() {
 69			return access.NoAccess
 70		}
 71
 72		// Otherwise, the user has read-only access.
 73		return access.ReadOnlyAccess
 74	}
 75
 76	if user != nil {
 77		// If the repository doesn't exist, the user has read/write access.
 78		if anon > access.ReadWriteAccess {
 79			return anon
 80		}
 81
 82		return access.ReadWriteAccess
 83	}
 84
 85	// If the user doesn't exist, give them the anonymous access level.
 86	return anon
 87}
 88
 89// User finds a user by username.
 90//
 91// It implements backend.Backend.
 92func (d *Backend) User(ctx context.Context, username string) (proto.User, error) {
 93	username = strings.ToLower(username)
 94	if err := utils.ValidateUsername(username); err != nil {
 95		return nil, err
 96	}
 97
 98	var m models.User
 99	var pks []ssh.PublicKey
100	if err := d.db.TransactionContext(ctx, func(tx *db.Tx) error {
101		var err error
102		m, err = d.store.FindUserByUsername(ctx, tx, username)
103		if err != nil {
104			return err
105		}
106
107		pks, err = d.store.ListPublicKeysByUserID(ctx, tx, m.ID)
108		return err
109	}); err != nil {
110		return nil, db.WrapError(err)
111	}
112
113	return &user{
114		user:       m,
115		publicKeys: pks,
116	}, nil
117}
118
119// UserByPublicKey finds a user by public key.
120//
121// It implements backend.Backend.
122func (d *Backend) UserByPublicKey(ctx context.Context, pk ssh.PublicKey) (proto.User, error) {
123	var m models.User
124	var pks []ssh.PublicKey
125	if err := d.db.TransactionContext(ctx, func(tx *db.Tx) error {
126		var err error
127		m, err = d.store.FindUserByPublicKey(ctx, tx, pk)
128		if err != nil {
129			return err
130		}
131
132		pks, err = d.store.ListPublicKeysByUserID(ctx, tx, m.ID)
133		return err
134	}); err != nil {
135		return nil, db.WrapError(err)
136	}
137
138	return &user{
139		user:       m,
140		publicKeys: pks,
141	}, nil
142}
143
144// Users returns all users.
145//
146// It implements backend.Backend.
147func (d *Backend) Users(ctx context.Context) ([]string, error) {
148	var users []string
149	if err := d.db.TransactionContext(ctx, func(tx *db.Tx) error {
150		ms, err := d.store.GetAllUsers(ctx, tx)
151		if err != nil {
152			return err
153		}
154
155		for _, m := range ms {
156			users = append(users, m.Username)
157		}
158
159		return nil
160	}); err != nil {
161		return nil, db.WrapError(err)
162	}
163
164	return users, nil
165}
166
167// AddPublicKey adds a public key to a user.
168//
169// It implements backend.Backend.
170func (d *Backend) AddPublicKey(ctx context.Context, username string, pk ssh.PublicKey) error {
171	username = strings.ToLower(username)
172	if err := utils.ValidateUsername(username); err != nil {
173		return err
174	}
175
176	return db.WrapError(
177		d.db.TransactionContext(ctx, func(tx *db.Tx) error {
178			return d.store.AddPublicKeyByUsername(ctx, tx, username, pk)
179		}),
180	)
181}
182
183// CreateUser creates a new user.
184//
185// It implements backend.Backend.
186func (d *Backend) CreateUser(ctx context.Context, username string, opts proto.UserOptions) (proto.User, error) {
187	username = strings.ToLower(username)
188	if err := utils.ValidateUsername(username); err != nil {
189		return nil, err
190	}
191
192	if err := d.db.TransactionContext(ctx, func(tx *db.Tx) error {
193		return d.store.CreateUser(ctx, tx, username, opts.Admin, opts.PublicKeys)
194	}); err != nil {
195		return nil, db.WrapError(err)
196	}
197
198	return d.User(ctx, username)
199}
200
201// DeleteUser deletes a user.
202//
203// It implements backend.Backend.
204func (d *Backend) DeleteUser(ctx context.Context, username string) error {
205	username = strings.ToLower(username)
206	if err := utils.ValidateUsername(username); err != nil {
207		return err
208	}
209
210	return db.WrapError(
211		d.db.TransactionContext(ctx, func(tx *db.Tx) error {
212			return d.store.DeleteUserByUsername(ctx, tx, username)
213		}),
214	)
215}
216
217// RemovePublicKey removes a public key from a user.
218//
219// It implements backend.Backend.
220func (d *Backend) RemovePublicKey(ctx context.Context, username string, pk ssh.PublicKey) error {
221	return db.WrapError(
222		d.db.TransactionContext(ctx, func(tx *db.Tx) error {
223			return d.store.RemovePublicKeyByUsername(ctx, tx, username, pk)
224		}),
225	)
226}
227
228// ListPublicKeys lists the public keys of a user.
229func (d *Backend) ListPublicKeys(ctx context.Context, username string) ([]ssh.PublicKey, error) {
230	username = strings.ToLower(username)
231	if err := utils.ValidateUsername(username); err != nil {
232		return nil, err
233	}
234
235	var keys []ssh.PublicKey
236	if err := d.db.TransactionContext(ctx, func(tx *db.Tx) error {
237		var err error
238		keys, err = d.store.ListPublicKeysByUsername(ctx, tx, username)
239		return err
240	}); err != nil {
241		return nil, db.WrapError(err)
242	}
243
244	return keys, nil
245}
246
247// SetUsername sets the username of a user.
248//
249// It implements backend.Backend.
250func (d *Backend) SetUsername(ctx context.Context, username string, newUsername string) error {
251	username = strings.ToLower(username)
252	if err := utils.ValidateUsername(username); err != nil {
253		return err
254	}
255
256	return db.WrapError(
257		d.db.TransactionContext(ctx, func(tx *db.Tx) error {
258			return d.store.SetUsernameByUsername(ctx, tx, username, newUsername)
259		}),
260	)
261}
262
263// SetAdmin sets the admin flag of a user.
264//
265// It implements backend.Backend.
266func (d *Backend) SetAdmin(ctx context.Context, username string, admin bool) error {
267	username = strings.ToLower(username)
268	if err := utils.ValidateUsername(username); err != nil {
269		return err
270	}
271
272	return db.WrapError(
273		d.db.TransactionContext(ctx, func(tx *db.Tx) error {
274			return d.store.SetAdminByUsername(ctx, tx, username, admin)
275		}),
276	)
277}
278
279type user struct {
280	user       models.User
281	publicKeys []ssh.PublicKey
282}
283
284var _ proto.User = (*user)(nil)
285
286// IsAdmin implements proto.User
287func (u *user) IsAdmin() bool {
288	return u.user.Admin
289}
290
291// PublicKeys implements proto.User
292func (u *user) PublicKeys() []ssh.PublicKey {
293	return u.publicKeys
294}
295
296// Username implements proto.User
297func (u *user) Username() string {
298	return u.user.Username
299}
300
301// ID implements proto.User.
302func (u *user) ID() int64 {
303	return u.user.ID
304}