user.go

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