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