Eric Bower
·
2026-02-24
backend.go
1package git
2
3import (
4 "bytes"
5 "encoding/base64"
6 "fmt"
7 "log/slog"
8 "strings"
9
10 "github.com/jmoiron/sqlx"
11 "golang.org/x/crypto/ssh"
12)
13
14type Backend struct {
15 Logger *slog.Logger
16 DB *sqlx.DB
17 Cfg *GitCfg
18}
19
20var ErrRepoNoNamespace = fmt.Errorf("repo must be namespaced by username")
21
22// Repo Namespace.
23func (be *Backend) CreateRepoNs(userName, repoName string) string {
24 if be.Cfg.CreateRepo == "admin" {
25 return repoName
26 }
27 return fmt.Sprintf("%s/%s", userName, repoName)
28}
29
30func (be *Backend) ValidateRepoNs(repoNs string) error {
31 _, repoID := be.SplitRepoNs(repoNs)
32 if strings.Contains(repoID, "/") {
33 return fmt.Errorf("repo can only contain a single forward-slash")
34 }
35 return nil
36}
37
38func (be *Backend) SplitRepoNs(repoNs string) (string, string) {
39 results := strings.SplitN(repoNs, "/", 2)
40 if len(results) == 1 {
41 return "", results[0]
42 }
43
44 return results[0], results[1]
45}
46
47func (be *Backend) CanCreateRepo(repo *Repo, requester *User) error {
48 pubkey, err := be.PubkeyToPublicKey(requester.Pubkey)
49 if err != nil {
50 return err
51 }
52 isAdmin := be.IsAdmin(pubkey)
53 if isAdmin {
54 return nil
55 }
56
57 // can create repo is a misnomer since we are saying it's ok to create
58 // a repo even though one already exists. this is a hack since this function
59 // is used exclusively inside pr creation flow.
60 if repo != nil {
61 return nil
62 }
63
64 if be.Cfg.CreateRepo == "user" {
65 return nil
66 }
67
68 // new repo with cfg indicating only admins can create prs/repos
69 return fmt.Errorf("you are not authorized to create repo")
70}
71
72func (be *Backend) Pubkey(pk ssh.PublicKey) string {
73 return be.KeyForKeyText(pk)
74}
75
76func (be *Backend) KeyForFingerprint(pk ssh.PublicKey) string {
77 return ssh.FingerprintSHA256(pk)
78}
79
80func (be *Backend) PubkeyToPublicKey(pubkey string) (ssh.PublicKey, error) {
81 kk, _, _, _, err := ssh.ParseAuthorizedKey([]byte(pubkey))
82 return kk, err
83}
84
85func (be *Backend) KeyForKeyText(pk ssh.PublicKey) string {
86 kb := base64.StdEncoding.EncodeToString(pk.Marshal())
87 return fmt.Sprintf("%s %s", pk.Type(), kb)
88}
89
90func (be *Backend) KeysEqual(pka, pkb string) bool {
91 return pka == pkb
92}
93
94func (be *Backend) PublicKeysEqual(a, b ssh.PublicKey) bool {
95 return bytes.Equal(a.Marshal(), b.Marshal())
96}
97
98func (be *Backend) IsAdmin(pk ssh.PublicKey) bool {
99 for _, apk := range be.Cfg.Admins {
100 if be.PublicKeysEqual(pk, apk) {
101 return true
102 }
103 }
104 return false
105}
106
107func (be *Backend) IsPrOwner(pka, pkb int64) bool {
108 return pka == pkb
109}
110
111type PrAcl struct {
112 CanModify bool
113 CanDelete bool
114 CanReview bool
115 CanAddPatchset bool
116}
117
118func (be *Backend) GetPatchRequestAcl(repo *Repo, prq *PatchRequest, requester *User) *PrAcl {
119 acl := &PrAcl{}
120 if requester == nil {
121 return acl
122 }
123
124 pubkey, err := be.PubkeyToPublicKey(requester.Pubkey)
125 if err != nil {
126 return acl
127 }
128
129 isAdmin := be.IsAdmin(pubkey)
130 // admin can do it all
131 if isAdmin {
132 acl.CanModify = true
133 acl.CanReview = true
134 acl.CanDelete = true
135 acl.CanAddPatchset = true
136 return acl
137 }
138
139 // repo owner can do it all
140 if repo.UserID == requester.ID {
141 acl.CanModify = true
142 acl.CanReview = true
143 acl.CanDelete = true
144 acl.CanAddPatchset = true
145 return acl
146 }
147
148 // pr creator has special priv
149 if be.IsPrOwner(prq.UserID, requester.ID) {
150 acl.CanModify = true
151 acl.CanReview = false
152 acl.CanDelete = true
153 acl.CanAddPatchset = true
154 return acl
155 }
156
157 // otherwise no perms
158 acl.CanModify = false
159 acl.CanDelete = false
160 acl.CanReview = false
161 // anyone can add a patchset
162 acl.CanAddPatchset = true
163
164 return acl
165}