+14
-5
appview/pages/pages.go
+14
-5
appview/pages/pages.go
···
152
152
}
153
153
154
154
type RepoInfo struct {
155
-
Name string
156
-
OwnerDid string
157
-
OwnerHandle string
158
-
Description string
155
+
Name string
156
+
OwnerDid string
157
+
OwnerHandle string
158
+
Description string
159
+
SettingsAllowed bool
159
160
}
160
161
161
162
func (r RepoInfo) OwnerWithAt() string {
···
177
178
}
178
179
179
180
func (p *Pages) RepoIndexPage(w io.Writer, params RepoIndexParams) error {
180
-
181
181
return p.executeRepo("repo/index", w, params)
182
182
}
183
183
···
239
239
240
240
func (p *Pages) RepoBlob(w io.Writer, params RepoBlobParams) error {
241
241
return p.executeRepo("repo/blob", w, params)
242
+
}
243
+
244
+
type RepoSettingsParams struct {
245
+
LoggedInUser *auth.User
246
+
Collaborators [][]string
247
+
}
248
+
249
+
func (p *Pages) RepoSettings(w io.Writer, params RepoSettingsParams) error {
250
+
return p.executeRepo("repo/settings", w, params)
242
251
}
243
252
244
253
func (p *Pages) Static() http.Handler {
+23
-23
appview/pages/templates/layouts/repobase.html
+23
-23
appview/pages/templates/layouts/repobase.html
···
2
2
3
3
{{ define "content" }}
4
4
5
-
<div id="repo-header">
6
-
<h1>{{ .RepoInfo.FullName }}</h1>
7
-
{{ if .RepoInfo.Description }}
8
-
<h3 class="desc">{{ .RepoInfo.Description }}</h3>
9
-
{{ else }}
10
-
<em>this repo has no description</em>
11
-
</div>
12
-
13
-
{{ with .IsEmpty }}
14
-
{{ else }}
15
-
<div id="repo-links">
16
-
<nav>
17
-
<a href="/{{ .RepoInfo.FullName }}">summary</a> ·
18
-
<a href="/{{ .RepoInfo.FullName }}/branches">branches</a> ·
19
-
<a href="/{{ .RepoInfo.FullName }}/tags">tags</a>
20
-
</nav>
21
-
<div>
22
-
{{ end }}
5
+
<div id="repo-header">
6
+
<h1>{{ .RepoInfo.FullName }}</h1>
7
+
{{ if .RepoInfo.Description }}
8
+
<h3 class="desc">{{ .RepoInfo.Description }}</h3>
9
+
{{ else }}
10
+
<em>this repo has no description</em>
11
+
{{ end }}
12
+
</div>
23
13
24
-
{{ end }}
14
+
{{ with .IsEmpty }}
15
+
{{ else }}
16
+
<div id="repo-links">
17
+
<nav>
18
+
<a href="/{{ .RepoInfo.FullName }}">summary</a> ·
19
+
<a href="/{{ .RepoInfo.FullName }}/branches">branches</a> ·
20
+
<a href="/{{ .RepoInfo.FullName }}/tags">tags</a>
21
+
{{ if .RepoInfo.SettingsAllowed }}
22
+
· <a href="/{{ .RepoInfo.FullName }}/settings">settings</a>
23
+
{{ end }}
24
+
</nav>
25
+
<div>
26
+
{{ end }}
25
27
26
-
{{ block "repoContent" . }} {{ end }}
28
+
{{ block "repoContent" . }} {{ end }}
27
29
28
30
{{ end }}
29
31
30
32
{{ define "layouts/repobase" }}
31
-
32
-
{{ template "layouts/base" . }}
33
-
33
+
{{ template "layouts/base" . }}
34
34
{{ end }}
+5
-1
appview/pages/templates/repo/branches.html
+5
-1
appview/pages/templates/repo/branches.html
+6
appview/pages/templates/repo/settings.html
+6
appview/pages/templates/repo/settings.html
+30
appview/state/middleware.go
+30
appview/state/middleware.go
···
104
104
}
105
105
}
106
106
107
+
func RepoPermissionMiddleware(s *State, requiredPerm string) Middleware {
108
+
return func(next http.Handler) http.Handler {
109
+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
110
+
// requires auth also
111
+
actor := s.auth.GetUser(r)
112
+
if actor == nil {
113
+
// we need a logged in user
114
+
log.Printf("not logged in, redirecting")
115
+
http.Error(w, "Forbiden", http.StatusUnauthorized)
116
+
return
117
+
}
118
+
f, err := fullyResolvedRepo(r)
119
+
if err != nil {
120
+
http.Error(w, "malformed url", http.StatusBadRequest)
121
+
return
122
+
}
123
+
124
+
ok, err := s.enforcer.E.Enforce(actor.Did, f.Knot, f.OwnerSlashRepo(), requiredPerm)
125
+
if err != nil || !ok {
126
+
// we need a logged in user
127
+
log.Printf("%s does not have perms of a %s in repo %s", actor.Did, requiredPerm, f.OwnerSlashRepo())
128
+
http.Error(w, "Forbiden", http.StatusUnauthorized)
129
+
return
130
+
}
131
+
132
+
next.ServeHTTP(w, r)
133
+
})
134
+
}
135
+
}
136
+
107
137
func StripLeadingAt(next http.Handler) http.Handler {
108
138
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
109
139
path := req.URL.Path
+179
-50
appview/state/repo.go
+179
-50
appview/state/repo.go
···
6
6
"io"
7
7
"log"
8
8
"net/http"
9
+
"path/filepath"
9
10
10
11
"github.com/bluesky-social/indigo/atproto/identity"
11
12
"github.com/go-chi/chi/v5"
13
+
"github.com/sotangled/tangled/appview/auth"
12
14
"github.com/sotangled/tangled/appview/pages"
13
15
"github.com/sotangled/tangled/types"
14
16
)
15
17
16
18
func (s *State) RepoIndex(w http.ResponseWriter, r *http.Request) {
17
-
repoName, knot, id, err := repoKnotAndId(r)
19
+
f, err := fullyResolvedRepo(r)
18
20
if err != nil {
19
-
log.Println("failed to get repo and knot", err)
21
+
log.Println("failed to fully resolve repo", err)
20
22
return
21
23
}
22
24
23
-
resp, err := http.Get(fmt.Sprintf("http://%s/%s/%s", knot, id.DID.String(), repoName))
25
+
resp, err := http.Get(fmt.Sprintf("http://%s/%s/%s", f.Knot, f.OwnerDid(), f.RepoName))
24
26
if err != nil {
25
27
log.Println("failed to reach knotserver", err)
26
28
return
···
42
44
43
45
log.Println(resp.Status, result)
44
46
47
+
user := s.auth.GetUser(r)
45
48
s.pages.RepoIndexPage(w, pages.RepoIndexParams{
46
-
LoggedInUser: s.auth.GetUser(r),
49
+
LoggedInUser: user,
47
50
RepoInfo: pages.RepoInfo{
48
-
OwnerDid: id.DID.String(),
49
-
OwnerHandle: id.Handle.String(),
50
-
Name: repoName,
51
+
OwnerDid: f.OwnerDid(),
52
+
OwnerHandle: f.OwnerHandle(),
53
+
Name: f.RepoName,
54
+
SettingsAllowed: settingsAllowed(s, user, f),
51
55
},
52
56
RepoIndexResponse: result,
53
57
})
···
56
60
}
57
61
58
62
func (s *State) RepoLog(w http.ResponseWriter, r *http.Request) {
59
-
repoName, knot, id, err := repoKnotAndId(r)
63
+
f, err := fullyResolvedRepo(r)
60
64
if err != nil {
61
-
log.Println("failed to get repo and knot", err)
65
+
log.Println("failed to fully resolve repo", err)
62
66
return
63
67
}
64
68
65
69
ref := chi.URLParam(r, "ref")
66
-
resp, err := http.Get(fmt.Sprintf("http://%s/%s/%s/log/%s", knot, id.DID.String(), repoName, ref))
70
+
resp, err := http.Get(fmt.Sprintf("http://%s/%s/%s/log/%s", f.Knot, f.OwnerDid(), f.RepoName, ref))
67
71
if err != nil {
68
72
log.Println("failed to reach knotserver", err)
69
73
return
···
82
86
return
83
87
}
84
88
89
+
user := s.auth.GetUser(r)
85
90
s.pages.RepoLog(w, pages.RepoLogParams{
86
-
LoggedInUser: s.auth.GetUser(r),
91
+
LoggedInUser: user,
87
92
RepoInfo: pages.RepoInfo{
88
-
OwnerDid: id.DID.String(),
89
-
OwnerHandle: id.Handle.String(),
90
-
Name: repoName,
93
+
OwnerDid: f.OwnerDid(),
94
+
OwnerHandle: f.OwnerHandle(),
95
+
Name: f.RepoName,
96
+
SettingsAllowed: settingsAllowed(s, user, f),
91
97
},
92
98
RepoLogResponse: result,
93
99
})
···
95
101
}
96
102
97
103
func (s *State) RepoCommit(w http.ResponseWriter, r *http.Request) {
98
-
repoName, knot, id, err := repoKnotAndId(r)
104
+
f, err := fullyResolvedRepo(r)
99
105
if err != nil {
100
-
log.Println("failed to get repo and knot", err)
106
+
log.Println("failed to fully resolve repo", err)
101
107
return
102
108
}
103
109
104
110
ref := chi.URLParam(r, "ref")
105
-
resp, err := http.Get(fmt.Sprintf("http://%s/%s/%s/commit/%s", knot, id.DID.String(), repoName, ref))
111
+
resp, err := http.Get(fmt.Sprintf("http://%s/%s/%s/commit/%s", f.Knot, f.OwnerDid(), f.RepoName, ref))
106
112
if err != nil {
107
113
log.Println("failed to reach knotserver", err)
108
114
return
···
121
127
return
122
128
}
123
129
130
+
user := s.auth.GetUser(r)
124
131
s.pages.RepoCommit(w, pages.RepoCommitParams{
125
-
LoggedInUser: s.auth.GetUser(r),
132
+
LoggedInUser: user,
126
133
RepoInfo: pages.RepoInfo{
127
-
OwnerDid: id.DID.String(),
128
-
OwnerHandle: id.Handle.String(),
129
-
Name: repoName,
134
+
OwnerDid: f.OwnerDid(),
135
+
OwnerHandle: f.OwnerHandle(),
136
+
Name: f.RepoName,
137
+
SettingsAllowed: settingsAllowed(s, user, f),
130
138
},
131
139
RepoCommitResponse: result,
132
140
})
···
134
142
}
135
143
136
144
func (s *State) RepoTree(w http.ResponseWriter, r *http.Request) {
137
-
repoName, knot, id, err := repoKnotAndId(r)
145
+
f, err := fullyResolvedRepo(r)
138
146
if err != nil {
139
-
log.Println("failed to get repo and knot", err)
147
+
log.Println("failed to fully resolve repo", err)
140
148
return
141
149
}
142
150
143
151
ref := chi.URLParam(r, "ref")
144
152
treePath := chi.URLParam(r, "*")
145
-
resp, err := http.Get(fmt.Sprintf("http://%s/%s/%s/tree/%s/%s", knot, id.DID.String(), repoName, ref, treePath))
153
+
resp, err := http.Get(fmt.Sprintf("http://%s/%s/%s/tree/%s/%s", f.Knot, f.OwnerDid(), f.RepoName, ref, treePath))
146
154
if err != nil {
147
155
log.Println("failed to reach knotserver", err)
148
156
return
···
163
171
164
172
log.Println(result)
165
173
174
+
user := s.auth.GetUser(r)
166
175
s.pages.RepoTree(w, pages.RepoTreeParams{
167
-
LoggedInUser: s.auth.GetUser(r),
176
+
LoggedInUser: user,
168
177
RepoInfo: pages.RepoInfo{
169
-
OwnerDid: id.DID.String(),
170
-
OwnerHandle: id.Handle.String(),
171
-
Name: repoName,
178
+
OwnerDid: f.OwnerDid(),
179
+
OwnerHandle: f.OwnerHandle(),
180
+
Name: f.RepoName,
181
+
SettingsAllowed: settingsAllowed(s, user, f),
172
182
},
173
183
RepoTreeResponse: result,
174
184
})
···
176
186
}
177
187
178
188
func (s *State) RepoTags(w http.ResponseWriter, r *http.Request) {
179
-
repoName, knot, id, err := repoKnotAndId(r)
189
+
f, err := fullyResolvedRepo(r)
180
190
if err != nil {
181
191
log.Println("failed to get repo and knot", err)
182
192
return
183
193
}
184
194
185
-
resp, err := http.Get(fmt.Sprintf("http://%s/%s/%s/tags", knot, id.DID.String(), repoName))
195
+
resp, err := http.Get(fmt.Sprintf("http://%s/%s/%s/tags", f.Knot, f.OwnerDid(), f.RepoName))
186
196
if err != nil {
187
197
log.Println("failed to reach knotserver", err)
188
198
return
···
201
211
return
202
212
}
203
213
214
+
user := s.auth.GetUser(r)
204
215
s.pages.RepoTags(w, pages.RepoTagsParams{
205
-
LoggedInUser: s.auth.GetUser(r),
216
+
LoggedInUser: user,
206
217
RepoInfo: pages.RepoInfo{
207
-
OwnerDid: id.DID.String(),
208
-
OwnerHandle: id.Handle.String(),
209
-
Name: repoName,
218
+
OwnerDid: f.OwnerDid(),
219
+
OwnerHandle: f.OwnerHandle(),
220
+
Name: f.RepoName,
221
+
SettingsAllowed: settingsAllowed(s, user, f),
210
222
},
211
223
RepoTagsResponse: result,
212
224
})
···
214
226
}
215
227
216
228
func (s *State) RepoBranches(w http.ResponseWriter, r *http.Request) {
217
-
repoName, knot, id, err := repoKnotAndId(r)
229
+
f, err := fullyResolvedRepo(r)
218
230
if err != nil {
219
231
log.Println("failed to get repo and knot", err)
220
232
return
221
233
}
222
234
223
-
resp, err := http.Get(fmt.Sprintf("http://%s/%s/%s/branches", knot, id.DID.String(), repoName))
235
+
resp, err := http.Get(fmt.Sprintf("http://%s/%s/%s/branches", f.Knot, f.OwnerDid(), f.RepoName))
224
236
if err != nil {
225
237
log.Println("failed to reach knotserver", err)
226
238
return
···
239
251
return
240
252
}
241
253
254
+
user := s.auth.GetUser(r)
242
255
s.pages.RepoBranches(w, pages.RepoBranchesParams{
243
-
LoggedInUser: s.auth.GetUser(r),
256
+
LoggedInUser: user,
244
257
RepoInfo: pages.RepoInfo{
245
-
OwnerDid: id.DID.String(),
246
-
OwnerHandle: id.Handle.String(),
247
-
Name: repoName,
258
+
OwnerDid: f.OwnerDid(),
259
+
OwnerHandle: f.OwnerHandle(),
260
+
Name: f.RepoName,
261
+
SettingsAllowed: settingsAllowed(s, user, f),
248
262
},
249
263
RepoBranchesResponse: result,
250
264
})
···
252
266
}
253
267
254
268
func (s *State) RepoBlob(w http.ResponseWriter, r *http.Request) {
255
-
repoName, knot, id, err := repoKnotAndId(r)
269
+
f, err := fullyResolvedRepo(r)
256
270
if err != nil {
257
271
log.Println("failed to get repo and knot", err)
258
272
return
···
260
274
261
275
ref := chi.URLParam(r, "ref")
262
276
filePath := chi.URLParam(r, "*")
263
-
resp, err := http.Get(fmt.Sprintf("http://%s/%s/%s/blob/%s/%s", knot, id.DID.String(), repoName, ref, filePath))
277
+
resp, err := http.Get(fmt.Sprintf("http://%s/%s/%s/blob/%s/%s", f.Knot, f.OwnerDid(), f.RepoName, ref, filePath))
264
278
if err != nil {
265
279
log.Println("failed to reach knotserver", err)
266
280
return
···
279
293
return
280
294
}
281
295
296
+
user := s.auth.GetUser(r)
282
297
s.pages.RepoBlob(w, pages.RepoBlobParams{
283
-
LoggedInUser: s.auth.GetUser(r),
298
+
LoggedInUser: user,
284
299
RepoInfo: pages.RepoInfo{
285
-
OwnerDid: id.DID.String(),
286
-
OwnerHandle: id.Handle.String(),
287
-
Name: repoName,
300
+
OwnerDid: f.OwnerDid(),
301
+
OwnerHandle: f.OwnerHandle(),
302
+
Name: f.RepoName,
303
+
SettingsAllowed: settingsAllowed(s, user, f),
288
304
},
289
305
RepoBlobResponse: result,
290
306
})
291
307
return
292
308
}
293
309
294
-
func repoKnotAndId(r *http.Request) (string, string, identity.Identity, error) {
310
+
func (s *State) AddCollaborator(w http.ResponseWriter, r *http.Request) {
311
+
f, err := fullyResolvedRepo(r)
312
+
if err != nil {
313
+
log.Println("failed to get repo and knot", err)
314
+
return
315
+
}
316
+
317
+
collaborator := r.FormValue("collaborator")
318
+
if collaborator == "" {
319
+
http.Error(w, "malformed form", http.StatusBadRequest)
320
+
return
321
+
}
322
+
323
+
collaboratorIdent, err := s.resolver.ResolveIdent(r.Context(), collaborator)
324
+
if err != nil {
325
+
w.Write([]byte("failed to resolve collaborator did to a handle"))
326
+
return
327
+
}
328
+
log.Printf("adding %s to %s\n", collaboratorIdent.Handle.String(), f.Knot)
329
+
330
+
// TODO: create an atproto record for this
331
+
332
+
secret, err := s.db.GetRegistrationKey(f.Knot)
333
+
if err != nil {
334
+
log.Printf("no key found for domain %s: %s\n", f.Knot, err)
335
+
return
336
+
}
337
+
338
+
ksClient, err := NewSignedClient(f.Knot, secret)
339
+
if err != nil {
340
+
log.Println("failed to create client to ", f.Knot)
341
+
return
342
+
}
343
+
344
+
ksResp, err := ksClient.AddCollaborator(f.OwnerDid(), f.RepoName, collaboratorIdent.DID.String())
345
+
if err != nil {
346
+
log.Printf("failed to make request to %s: %s", f.Knot, err)
347
+
return
348
+
}
349
+
350
+
if ksResp.StatusCode != http.StatusNoContent {
351
+
w.Write([]byte(fmt.Sprint("knotserver failed to add collaborator: ", err)))
352
+
return
353
+
}
354
+
355
+
err = s.enforcer.AddCollaborator(collaboratorIdent.DID.String(), f.Knot, f.OwnerSlashRepo())
356
+
if err != nil {
357
+
w.Write([]byte(fmt.Sprint("failed to add collaborator: ", err)))
358
+
return
359
+
}
360
+
361
+
w.Write([]byte(fmt.Sprint("added collaborator: ", collaboratorIdent.Handle.String())))
362
+
363
+
}
364
+
365
+
func (s *State) RepoSettings(w http.ResponseWriter, r *http.Request) {
366
+
f, err := fullyResolvedRepo(r)
367
+
if err != nil {
368
+
log.Println("failed to get repo and knot", err)
369
+
return
370
+
}
371
+
372
+
switch r.Method {
373
+
case http.MethodGet:
374
+
// for now, this is just pubkeys
375
+
user := s.auth.GetUser(r)
376
+
repoCollaborators, err := s.enforcer.E.GetImplicitUsersForResourceByDomain(f.OwnerSlashRepo(), f.Knot)
377
+
if err != nil {
378
+
log.Println("failed to get collaborators", err)
379
+
}
380
+
log.Println(repoCollaborators)
381
+
382
+
s.pages.RepoSettings(w, pages.RepoSettingsParams{
383
+
LoggedInUser: user,
384
+
Collaborators: repoCollaborators,
385
+
})
386
+
}
387
+
}
388
+
389
+
type FullyResolvedRepo struct {
390
+
Knot string
391
+
OwnerId identity.Identity
392
+
RepoName string
393
+
}
394
+
395
+
func (f *FullyResolvedRepo) OwnerDid() string {
396
+
return f.OwnerId.DID.String()
397
+
}
398
+
399
+
func (f *FullyResolvedRepo) OwnerHandle() string {
400
+
return f.OwnerId.Handle.String()
401
+
}
402
+
403
+
func (f *FullyResolvedRepo) OwnerSlashRepo() string {
404
+
return filepath.Join(f.OwnerDid(), f.RepoName)
405
+
}
406
+
407
+
func fullyResolvedRepo(r *http.Request) (*FullyResolvedRepo, error) {
295
408
repoName := chi.URLParam(r, "repo")
296
409
knot, ok := r.Context().Value("knot").(string)
297
410
if !ok {
298
411
log.Println("malformed middleware")
299
-
return "", "", identity.Identity{}, fmt.Errorf("malformed middleware")
412
+
return nil, fmt.Errorf("malformed middleware")
300
413
}
301
414
id, ok := r.Context().Value("resolvedId").(identity.Identity)
302
415
if !ok {
303
416
log.Println("malformed middleware")
304
-
return "", "", identity.Identity{}, fmt.Errorf("malformed middleware")
417
+
return nil, fmt.Errorf("malformed middleware")
305
418
}
306
419
307
-
return repoName, knot, id, nil
420
+
return &FullyResolvedRepo{
421
+
Knot: knot,
422
+
OwnerId: id,
423
+
RepoName: repoName,
424
+
}, nil
425
+
}
426
+
427
+
func settingsAllowed(s *State, u *auth.User, f *FullyResolvedRepo) bool {
428
+
settingsAllowed := false
429
+
if u != nil {
430
+
ok, err := s.enforcer.IsSettingsAllowed(u.Did, f.Knot, f.OwnerSlashRepo())
431
+
if err == nil && ok {
432
+
settingsAllowed = true
433
+
}
434
+
}
435
+
436
+
return settingsAllowed
308
437
}
+18
appview/state/signer.go
+18
appview/state/signer.go
···
113
113
114
114
return s.client.Do(req)
115
115
}
116
+
117
+
func (s *SignedClient) AddCollaborator(ownerDid, repoName, memberDid string) (*http.Response, error) {
118
+
const (
119
+
Method = "POST"
120
+
)
121
+
endpoint := fmt.Sprintf("/{ownerDid}/{repoName}/collaborator/add")
122
+
123
+
body, _ := json.Marshal(map[string]interface{}{
124
+
"did": memberDid,
125
+
})
126
+
127
+
req, err := s.newRequest(Method, endpoint, body)
128
+
if err != nil {
129
+
return nil, err
130
+
}
131
+
132
+
return s.client.Do(req)
133
+
}
+8
-5
appview/state/state.go
+8
-5
appview/state/state.go
···
382
382
AddedAt: &addedAt,
383
383
}},
384
384
})
385
+
385
386
// invalid record
386
387
if err != nil {
387
388
log.Printf("failed to create record: %s", err)
···
563
564
return
564
565
}
565
566
566
-
// invalid record
567
-
if err != nil {
568
-
log.Printf("failed to create record: %s", err)
569
-
return
570
-
}
571
567
log.Println("created atproto record: ", resp.Uri)
572
568
573
569
return
···
611
607
r.Get("/info/refs", s.InfoRefs)
612
608
r.Post("/git-upload-pack", s.UploadPack)
613
609
610
+
// settings routes, needs auth
611
+
r.Group(func(r chi.Router) {
612
+
r.With(RepoPermissionMiddleware(s, "repo:settings")).Route("/settings", func(r chi.Router) {
613
+
r.Get("/", s.RepoSettings)
614
+
r.With(RepoPermissionMiddleware(s, "repo:invite")).Put("/collaborator", s.AddCollaborator)
615
+
})
616
+
})
614
617
})
615
618
})
616
619
+2
-2
knotserver/routes.go
+2
-2
knotserver/routes.go
···
515
515
h.jc.AddDid(data.Did)
516
516
517
517
repoName := filepath.Join(ownerDid, repo)
518
-
if err := h.e.AddRepo(data.Did, ThisServer, repoName); err != nil {
518
+
if err := h.e.AddCollaborator(data.Did, ThisServer, repoName); err != nil {
519
519
l.Error("adding repo collaborator", "error", err.Error())
520
520
writeError(w, err.Error(), http.StatusInternalServerError)
521
521
return
···
527
527
return
528
528
}
529
529
530
-
w.WriteHeader(http.StatusOK)
530
+
w.WriteHeader(http.StatusNoContent)
531
531
}
532
532
533
533
func (h *Handle) Init(w http.ResponseWriter, r *http.Request) {
+24
rbac/rbac.go
+24
rbac/rbac.go
···
2
2
3
3
import (
4
4
"database/sql"
5
+
"fmt"
5
6
"path"
6
7
"strings"
7
8
···
95
96
}
96
97
97
98
func (e *Enforcer) AddRepo(member, domain, repo string) error {
99
+
// sanity check, repo must be of the form ownerDid/repo
100
+
if parts := strings.SplitN(repo, "/", 2); !strings.HasPrefix(parts[0], "did:") {
101
+
return fmt.Errorf("invalid repo: %s", repo)
102
+
}
103
+
98
104
_, err := e.E.AddPolicies([][]string{
105
+
{member, domain, repo, "repo:settings"},
99
106
{member, domain, repo, "repo:push"},
100
107
{member, domain, repo, "repo:owner"},
101
108
{member, domain, repo, "repo:invite"},
102
109
{member, domain, repo, "repo:delete"},
103
110
{"server:owner", domain, repo, "repo:delete"}, // server owner can delete any repo
111
+
})
112
+
return err
113
+
}
114
+
115
+
func (e *Enforcer) AddCollaborator(collaborator, domain, repo string) error {
116
+
// sanity check, repo must be of the form ownerDid/repo
117
+
if parts := strings.SplitN(repo, "/", 2); !strings.HasPrefix(parts[0], "did:") {
118
+
return fmt.Errorf("invalid repo: %s", repo)
119
+
}
120
+
121
+
_, err := e.E.AddPolicies([][]string{
122
+
{collaborator, domain, repo, "repo:settings"},
123
+
{collaborator, domain, repo, "repo:push"},
104
124
})
105
125
return err
106
126
}
···
137
157
138
158
func (e *Enforcer) IsPushAllowed(user, domain, repo string) (bool, error) {
139
159
return e.E.Enforce(user, domain, repo, "repo:push")
160
+
}
161
+
162
+
func (e *Enforcer) IsSettingsAllowed(user, domain, repo string) (bool, error) {
163
+
return e.E.Enforce(user, domain, repo, "repo:settings")
140
164
}
141
165
142
166
// keyMatch2Func is a wrapper for keyMatch2 to make it compatible with Casbin