Gitfed
bastien-mrq/gitfed / internal / acl / acl.go
// Package acl implements the repo access-control model described in
// DESIGN.md §6: an owner plus a list of collaborators (local or federated
// principals) each granted read, write or admin.
package acl

import (
	"fmt"

	"git.neuromancer.ovh/bastien-mrq/gitfed/internal/store"
)

var roleRank = map[store.Role]int{
	store.RoleRead:  1,
	store.RoleWrite: 2,
	store.RoleAdmin: 3,
}

// Satisfies reports whether having role `have` satisfies a requirement of
// role `want`.
func Satisfies(have, want store.Role) bool {
	return roleRank[have] >= roleRank[want]
}

// Check determines whether principal has at least `want` access on repo.
// The repo owner always has admin. Returns the effective role and whether
// access is granted.
func Check(s *store.Store, repoName, principal string, want store.Role) (store.Role, bool, error) {
	repo, err := s.GetRepo(repoName)
	if err != nil {
		return "", false, fmt.Errorf("acl: repo %q: %w", repoName, err)
	}
	if repo.Owner == principal {
		return store.RoleAdmin, true, nil
	}

	a, err := s.GetACL(repoName)
	if err != nil && err != store.ErrNotFound {
		return "", false, err
	}
	for _, c := range a.Collaborators {
		if c.Principal == principal {
			return c.Role, Satisfies(c.Role, want), nil
		}
	}

	// A public repo is readable by any authenticated principal without an
	// explicit collaborator entry — this never extends to write/admin.
	if repo.Public && want == store.RoleRead {
		return store.RoleRead, true, nil
	}
	return "", false, nil
}

// Grant adds or updates a collaborator's role on a repo. It does not itself
// resolve federation trust for remote principals — callers should invoke
// federation.Resolver.EnsureTrust first per DESIGN.md §6.
func Grant(s *store.Store, repoName string, principal string, role store.Role) error {
	if !role.Valid() {
		return fmt.Errorf("acl: invalid role %q", role)
	}
	return s.UpsertCollaborator(repoName, store.Collaborator{Principal: principal, Role: role})
}

// Revoke removes a collaborator from a repo's ACL.
func Revoke(s *store.Store, repoName, principal string) error {
	return s.RemoveCollaborator(repoName, principal)
}