Gitfed
bastien-mrq/gitfed / internal / store / acl.go
package store

import (
	bolt "go.etcd.io/bbolt"
)

// Role is a level of access granted to a principal on a repo.
type Role string

const (
	RoleRead  Role = "read"
	RoleWrite Role = "write"
	RoleAdmin Role = "admin"
)

func (r Role) Valid() bool {
	switch r {
	case RoleRead, RoleWrite, RoleAdmin:
		return true
	}
	return false
}

// Collaborator grants a role to a principal (local or federated) on a repo.
type Collaborator struct {
	Principal string `json:"principal"` // "<username>@<domain>"
	Role      Role   `json:"role"`
}

// ACL is the access control list for a single repo.
type ACL struct {
	Repo          string         `json:"repo"`
	Owner         string         `json:"owner"`
	Collaborators []Collaborator `json:"collaborators"`
}

func (s *Store) GetACL(repo string) (ACL, error) {
	var a ACL
	err := s.db.View(func(tx *bolt.Tx) error {
		return getJSON(tx, bucketACL, repo, &a)
	})
	return a, err
}

func (s *Store) PutACL(a ACL) error {
	return s.db.Update(func(tx *bolt.Tx) error {
		return putJSON(tx, bucketACL, a.Repo, a)
	})
}

// UpsertCollaborator adds or updates a collaborator's role on a repo's ACL.
func (s *Store) UpsertCollaborator(repo string, c Collaborator) error {
	return s.db.Update(func(tx *bolt.Tx) error {
		var a ACL
		if err := getJSON(tx, bucketACL, repo, &a); err != nil {
			if err != ErrNotFound {
				return err
			}
			a = ACL{Repo: repo}
		}
		replaced := false
		for i, existing := range a.Collaborators {
			if existing.Principal == c.Principal {
				a.Collaborators[i] = c
				replaced = true
				break
			}
		}
		if !replaced {
			a.Collaborators = append(a.Collaborators, c)
		}
		return putJSON(tx, bucketACL, repo, a)
	})
}

// RemoveCollaborator removes a principal from a repo's ACL.
func (s *Store) RemoveCollaborator(repo, principal string) error {
	return s.db.Update(func(tx *bolt.Tx) error {
		var a ACL
		if err := getJSON(tx, bucketACL, repo, &a); err != nil {
			return err
		}
		kept := a.Collaborators[:0]
		for _, c := range a.Collaborators {
			if c.Principal != principal {
				kept = append(kept, c)
			}
		}
		a.Collaborators = kept
		return putJSON(tx, bucketACL, repo, a)
	})
}

func (s *Store) DeleteACL(repo string) error {
	return s.db.Update(func(tx *bolt.Tx) error {
		return deleteKey(tx, bucketACL, repo)
	})
}