Eric Bower
·
2025-03-13
backend.go
1package git
2
3import (
4 "encoding/base64"
5 "fmt"
6 "log/slog"
7 "strings"
8
9 "github.com/charmbracelet/ssh"
10 "github.com/jmoiron/sqlx"
11 gossh "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 gossh.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) IsAdmin(pk ssh.PublicKey) bool {
95 for _, apk := range be.Cfg.Admins {
96 if ssh.KeysEqual(pk, apk) {
97 return true
98 }
99 }
100 return false
101}
102
103func (be *Backend) IsPrOwner(pka, pkb int64) bool {
104 return pka == pkb
105}
106
107type PrAcl struct {
108 CanModify bool
109 CanDelete bool
110 CanReview bool
111 CanAddPatchset bool
112}
113
114func (be *Backend) GetPatchRequestAcl(repo *Repo, prq *PatchRequest, requester *User) *PrAcl {
115 acl := &PrAcl{}
116 if requester == nil {
117 return acl
118 }
119
120 pubkey, err := be.PubkeyToPublicKey(requester.Pubkey)
121 if err != nil {
122 return acl
123 }
124
125 isAdmin := be.IsAdmin(pubkey)
126 // admin can do it all
127 if isAdmin {
128 acl.CanModify = true
129 acl.CanReview = true
130 acl.CanDelete = true
131 acl.CanAddPatchset = true
132 return acl
133 }
134
135 // repo owner can do it all
136 if repo.UserID == requester.ID {
137 acl.CanModify = true
138 acl.CanReview = true
139 acl.CanDelete = true
140 acl.CanAddPatchset = true
141 return acl
142 }
143
144 // pr creator have special priv
145 if be.IsPrOwner(prq.UserID, requester.ID) {
146 acl.CanModify = true
147 acl.CanReview = false
148 acl.CanDelete = true
149 acl.CanAddPatchset = true
150 return acl
151 }
152
153 // otherwise no perms
154 acl.CanModify = false
155 acl.CanDelete = false
156 // anyone can review or add a patchset
157 acl.CanReview = true
158 acl.CanAddPatchset = true
159
160 return acl
161}