Skip to content

Commit

Permalink
fix(backend): update backend to use handles table
Browse files Browse the repository at this point in the history
  • Loading branch information
aymanbagabas committed Nov 8, 2023
1 parent a222f24 commit 84cb588
Show file tree
Hide file tree
Showing 17 changed files with 395 additions and 126 deletions.
95 changes: 95 additions & 0 deletions pkg/backend/access.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package backend

import (
"context"

"github.com/charmbracelet/soft-serve/pkg/access"
"github.com/charmbracelet/soft-serve/pkg/proto"
"github.com/charmbracelet/soft-serve/pkg/sshutils"
"golang.org/x/crypto/ssh"
)

// AccessLevel returns the access level of a user for a repository.
//
// It implements backend.Backend.
func (d *Backend) AccessLevel(ctx context.Context, repo string, username string) access.AccessLevel {
user, _ := d.User(ctx, username)
return d.AccessLevelForUser(ctx, repo, user)
}

// AccessLevelByPublicKey returns the access level of a user's public key for a repository.
//
// It implements backend.Backend.
func (d *Backend) AccessLevelByPublicKey(ctx context.Context, repo string, pk ssh.PublicKey) access.AccessLevel {
for _, k := range d.cfg.AdminKeys() {
if sshutils.KeysEqual(pk, k) {
return access.AdminAccess
}
}

user, _ := d.UserByPublicKey(ctx, pk)
if user != nil {
return d.AccessLevel(ctx, repo, user.Username())
}

return d.AccessLevel(ctx, repo, "")
}

// AccessLevelForUser returns the access level of a user for a repository.
// TODO: user repository ownership
func (d *Backend) AccessLevelForUser(ctx context.Context, repo string, user proto.User) access.AccessLevel {
var username string
anon := d.AnonAccess(ctx)
if user != nil {
username = user.Username()
}

// If the user is an admin, they have admin access.
if user != nil && user.IsAdmin() {
return access.AdminAccess
}

// If the repository exists, check if the user is a collaborator.
r := proto.RepositoryFromContext(ctx)
if r == nil {
r, _ = d.Repository(ctx, repo)
}

if r != nil {
if user != nil {
// If the user is the owner, they have admin access.
if r.UserID() == user.ID() {
return access.AdminAccess
}
}

// If the user is a collaborator, they have return their access level.
collabAccess, isCollab, _ := d.IsCollaborator(ctx, repo, username)
if isCollab {
if anon > collabAccess {
return anon
}
return collabAccess
}

// If the repository is private, the user has no access.
if r.IsPrivate() {
return access.NoAccess
}

// Otherwise, the user has read-only access.
return access.ReadOnlyAccess
}

if user != nil {
// If the repository doesn't exist, the user has read/write access.
if anon > access.ReadWriteAccess {
return anon
}

return access.ReadWriteAccess
}

// If the user doesn't exist, give them the anonymous access level.
return anon
}
26 changes: 20 additions & 6 deletions pkg/backend/collab.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,19 +50,33 @@ func (d *Backend) AddCollaborator(ctx context.Context, repo string, username str
func (d *Backend) Collaborators(ctx context.Context, repo string) ([]string, error) {
repo = utils.SanitizeRepo(repo)
var users []models.User
var usernames []string
if err := d.db.TransactionContext(ctx, func(tx *db.Tx) error {
var err error
users, err = d.store.ListCollabsByRepoAsUsers(ctx, tx, repo)
return err
if err != nil {
return err
}

ids := make([]int64, len(users))
for i, u := range users {
ids[i] = u.ID
}

handles, err := d.store.ListHandlesForIDs(ctx, tx, ids)
if err != nil {
return err
}

for _, h := range handles {
usernames = append(usernames, h.Handle)
}

return nil
}); err != nil {
return nil, db.WrapError(err)
}

var usernames []string
for _, u := range users {
usernames = append(usernames, u.Username)
}

return usernames, nil
}

Expand Down
131 changes: 42 additions & 89 deletions pkg/backend/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import (
"strings"
"time"

"github.com/charmbracelet/soft-serve/pkg/access"
"github.com/charmbracelet/soft-serve/pkg/db"
"github.com/charmbracelet/soft-serve/pkg/db/models"
"github.com/charmbracelet/soft-serve/pkg/proto"
Expand All @@ -15,91 +14,6 @@ import (
"golang.org/x/crypto/ssh"
)

// AccessLevel returns the access level of a user for a repository.
//
// It implements backend.Backend.
func (d *Backend) AccessLevel(ctx context.Context, repo string, username string) access.AccessLevel {
user, _ := d.User(ctx, username)
return d.AccessLevelForUser(ctx, repo, user)
}

// AccessLevelByPublicKey returns the access level of a user's public key for a repository.
//
// It implements backend.Backend.
func (d *Backend) AccessLevelByPublicKey(ctx context.Context, repo string, pk ssh.PublicKey) access.AccessLevel {
for _, k := range d.cfg.AdminKeys() {
if sshutils.KeysEqual(pk, k) {
return access.AdminAccess
}
}

user, _ := d.UserByPublicKey(ctx, pk)
if user != nil {
return d.AccessLevel(ctx, repo, user.Username())
}

return d.AccessLevel(ctx, repo, "")
}

// AccessLevelForUser returns the access level of a user for a repository.
// TODO: user repository ownership
func (d *Backend) AccessLevelForUser(ctx context.Context, repo string, user proto.User) access.AccessLevel {
var username string
anon := d.AnonAccess(ctx)
if user != nil {
username = user.Username()
}

// If the user is an admin, they have admin access.
if user != nil && user.IsAdmin() {
return access.AdminAccess
}

// If the repository exists, check if the user is a collaborator.
r := proto.RepositoryFromContext(ctx)
if r == nil {
r, _ = d.Repository(ctx, repo)
}

if r != nil {
if user != nil {
// If the user is the owner, they have admin access.
if r.UserID() == user.ID() {
return access.AdminAccess
}
}

// If the user is a collaborator, they have return their access level.
collabAccess, isCollab, _ := d.IsCollaborator(ctx, repo, username)
if isCollab {
if anon > collabAccess {
return anon
}
return collabAccess
}

// If the repository is private, the user has no access.
if r.IsPrivate() {
return access.NoAccess
}

// Otherwise, the user has read-only access.
return access.ReadOnlyAccess
}

if user != nil {
// If the repository doesn't exist, the user has read/write access.
if anon > access.ReadWriteAccess {
return anon
}

return access.ReadWriteAccess
}

// If the user doesn't exist, give them the anonymous access level.
return anon
}

// User finds a user by username.
//
// It implements backend.Backend.
Expand All @@ -111,6 +25,7 @@ func (d *Backend) User(ctx context.Context, username string) (proto.User, error)

var m models.User
var pks []ssh.PublicKey
var hl models.Handle
if err := d.db.TransactionContext(ctx, func(tx *db.Tx) error {
var err error
m, err = d.store.FindUserByUsername(ctx, tx, username)
Expand All @@ -119,6 +34,11 @@ func (d *Backend) User(ctx context.Context, username string) (proto.User, error)
}

pks, err = d.store.ListPublicKeysByUserID(ctx, tx, m.ID)
if err != nil {
return err
}

hl, err = d.store.GetHandleByUserID(ctx, tx, m.ID)
return err
}); err != nil {
err = db.WrapError(err)
Expand All @@ -132,13 +52,15 @@ func (d *Backend) User(ctx context.Context, username string) (proto.User, error)
return &user{
user: m,
publicKeys: pks,
handle: hl,
}, nil
}

// UserByID finds a user by ID.
func (d *Backend) UserByID(ctx context.Context, id int64) (proto.User, error) {
var m models.User
var pks []ssh.PublicKey
var hl models.Handle
if err := d.db.TransactionContext(ctx, func(tx *db.Tx) error {
var err error
m, err = d.store.GetUserByID(ctx, tx, id)
Expand All @@ -147,6 +69,11 @@ func (d *Backend) UserByID(ctx context.Context, id int64) (proto.User, error) {
}

pks, err = d.store.ListPublicKeysByUserID(ctx, tx, m.ID)
if err != nil {
return err
}

hl, err = d.store.GetHandleByUserID(ctx, tx, m.ID)
return err
}); err != nil {
err = db.WrapError(err)
Expand All @@ -160,6 +87,7 @@ func (d *Backend) UserByID(ctx context.Context, id int64) (proto.User, error) {
return &user{
user: m,
publicKeys: pks,
handle: hl,
}, nil
}

Expand All @@ -169,6 +97,7 @@ func (d *Backend) UserByID(ctx context.Context, id int64) (proto.User, error) {
func (d *Backend) UserByPublicKey(ctx context.Context, pk ssh.PublicKey) (proto.User, error) {
var m models.User
var pks []ssh.PublicKey
var hl models.Handle
if err := d.db.TransactionContext(ctx, func(tx *db.Tx) error {
var err error
m, err = d.store.FindUserByPublicKey(ctx, tx, pk)
Expand All @@ -177,6 +106,11 @@ func (d *Backend) UserByPublicKey(ctx context.Context, pk ssh.PublicKey) (proto.
}

pks, err = d.store.ListPublicKeysByUserID(ctx, tx, m.ID)
if err != nil {
return err
}

hl, err = d.store.GetHandleByUserID(ctx, tx, m.ID)
return err
}); err != nil {
err = db.WrapError(err)
Expand All @@ -190,6 +124,7 @@ func (d *Backend) UserByPublicKey(ctx context.Context, pk ssh.PublicKey) (proto.
return &user{
user: m,
publicKeys: pks,
handle: hl,
}, nil
}

Expand All @@ -198,6 +133,7 @@ func (d *Backend) UserByPublicKey(ctx context.Context, pk ssh.PublicKey) (proto.
func (d *Backend) UserByAccessToken(ctx context.Context, token string) (proto.User, error) {
var m models.User
var pks []ssh.PublicKey
var hl models.Handle
token = HashToken(token)

if err := d.db.TransactionContext(ctx, func(tx *db.Tx) error {
Expand All @@ -216,6 +152,11 @@ func (d *Backend) UserByAccessToken(ctx context.Context, token string) (proto.Us
}

pks, err = d.store.ListPublicKeysByUserID(ctx, tx, m.ID)
if err != nil {
return err
}

hl, err = d.store.GetHandleByUserID(ctx, tx, m.ID)
return err
}); err != nil {
err = db.WrapError(err)
Expand All @@ -229,6 +170,7 @@ func (d *Backend) UserByAccessToken(ctx context.Context, token string) (proto.Us
return &user{
user: m,
publicKeys: pks,
handle: hl,
}, nil
}

Expand All @@ -243,8 +185,18 @@ func (d *Backend) Users(ctx context.Context) ([]string, error) {
return err
}

for _, m := range ms {
users = append(users, m.Username)
ids := make([]int64, len(ms))
for i, m := range ms {
ids[i] = m.ID
}

handles, err := d.store.ListHandlesForIDs(ctx, tx, ids)
if err != nil {
return err
}

for _, h := range handles {
users = append(users, h.Handle)
}

return nil
Expand Down Expand Up @@ -391,6 +343,7 @@ func (d *Backend) SetPassword(ctx context.Context, username string, rawPassword
type user struct {
user models.User
publicKeys []ssh.PublicKey
handle models.Handle
}

var _ proto.User = (*user)(nil)
Expand All @@ -407,7 +360,7 @@ func (u *user) PublicKeys() []ssh.PublicKey {

// Username implements proto.User
func (u *user) Username() string {
return u.user.Username
return u.handle.Handle
}

// ID implements proto.User.
Expand Down
1 change: 1 addition & 0 deletions pkg/db/models/repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ type Repo struct {
Mirror bool `db:"mirror"`
Hidden bool `db:"hidden"`
UserID sql.NullInt64 `db:"user_id"`
OrgID sql.NullInt64 `db:"org_id"`
CreatedAt time.Time `db:"created_at"`
UpdatedAt time.Time `db:"updated_at"`
}
Loading

0 comments on commit 84cb588

Please sign in to comment.