internal/policy/access.go

81bb0d14080e45ffcd6ca844cbe506ce1f10e22a
gitbay/internal/policy/access.go history · blame · raw

109 lines · 3149 bytes

  1package policy
  2
  3import (
  4	"strconv"
  5	"strings"
  6
  7	"gitbay.org/gitbay/internal/store"
  8)
  9
 10// CanRead reports whether user may read repo over an authenticated channel.
 11// Public repos are readable by any authenticated user; private repos require
 12// ownership or an explicit grant.
 13func CanRead(user store.User, repo store.Repo, grant string) bool {
 14	if isOwner(user, repo) {
 15		return true
 16	}
 17	if repo.Visibility == "public" {
 18		return true
 19	}
 20	return grant == "read" || grant == "write" || grant == "admin"
 21}
 22
 23// CanWrite reports whether user may push to repo.
 24func CanWrite(user store.User, repo store.Repo, grant string) bool {
 25	if isOwner(user, repo) {
 26		return true
 27	}
 28	return grant == "write" || grant == "admin"
 29}
 30
 31// CanAdmin reports whether user may change repo settings and access.
 32func CanAdmin(user store.User, repo store.Repo, grant string) bool {
 33	if isOwner(user, repo) {
 34		return true
 35	}
 36	return grant == "admin"
 37}
 38
 39func isOwner(user store.User, repo store.Repo) bool {
 40	return repo.OwnerKind == "user" && repo.OwnerID == user.ID
 41}
 42
 43// ScopeAllowsGit reports whether an account-scoped SSH key permits git
 44// transport at all. Deploy scopes are decided by DeployScopeAllows instead.
 45func ScopeAllowsGit(scope, repoPath string, write bool) bool {
 46	switch scope {
 47	case "full", "git":
 48		return true
 49	}
 50	return false
 51}
 52
 53// DeployScopeAllows authorizes a deploy key purely by its scope: the key is
 54// bound to a repository ID (rename- and transfer-proof), grants nothing
 55// anywhere else, and never inherits the access of whoever registered it.
 56func DeployScopeAllows(scope string, repoID int64, write bool) bool {
 57	rest, ok := strings.CutPrefix(scope, "deploy:")
 58	if !ok {
 59		return false
 60	}
 61	idStr, mode, ok := strings.Cut(rest, ":")
 62	if !ok || idStr != strconv.FormatInt(repoID, 10) {
 63		return false
 64	}
 65	switch mode {
 66	case "rw":
 67		return true
 68	case "ro":
 69		return !write
 70	}
 71	return false
 72}
 73
 74// IsDeployScope reports whether a key scope is a deploy binding.
 75func IsDeployScope(scope string) bool { return strings.HasPrefix(scope, "deploy:") }
 76
 77// RefUpdate is one proposed ref change, with git facts computed by the hook
 78// process (which can see quarantined objects; the daemon cannot).
 79type RefUpdate struct {
 80	Ref      string `json:"ref"`
 81	Old      string `json:"old"`
 82	New      string `json:"new"`
 83	IsDelete bool   `json:"is_delete"`
 84	IsForce  bool   `json:"is_force"`
 85}
 86
 87// CheckPush applies ref policy for a push by a user with write access
 88// already established. It returns a denial message, or "" to allow.
 89func CheckPush(repo store.Repo, updates []RefUpdate) string {
 90	protected := map[string]bool{}
 91	for _, b := range repo.Settings.ProtectedBranches {
 92		protected["refs/heads/"+b] = true
 93	}
 94	for _, u := range updates {
 95		if strings.HasPrefix(u.Ref, "refs/merge-requests/") {
 96			return "refs/merge-requests/* is server-owned and cannot be pushed"
 97		}
 98		if protected[u.Ref] {
 99			branch := strings.TrimPrefix(u.Ref, "refs/heads/")
100			if u.IsDelete {
101				return "branch " + branch + " is protected: deletion refused"
102			}
103			if u.IsForce {
104				return "branch " + branch + " is protected: force-push refused"
105			}
106		}
107	}
108	return ""
109}