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}