Monorepo for Tangled tangled.org

add not-working repo settings

Changed files
+309 -86
appview
knotserver
rbac
+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
··· 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>&nbsp;· 18 - <a href="/{{ .RepoInfo.FullName }}/branches">branches</a>&nbsp;· 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>&nbsp;· 19 + <a href="/{{ .RepoInfo.FullName }}/branches">branches</a>&nbsp;· 20 + <a href="/{{ .RepoInfo.FullName }}/tags">tags</a> 21 + {{ if .RepoInfo.SettingsAllowed }} 22 + ·&nbsp;<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
··· 1 - {{ define "repoContent" }} 1 + {{ define "title" }} 2 + branches | {{ .RepoInfo.FullName }} 3 + {{ end }} 4 + 5 + {{ define "content" }} 2 6 {{ $name := .RepoInfo.Name }} 3 7 <h3>branches</h3> 4 8 <div class="refs">
+6
appview/pages/templates/repo/settings.html
··· 1 + {{define "repoContent"}} 2 + <main> 3 + <h1>settings</h1> 4 + </main> 5 + {{end}} 6 +
+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
··· 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
··· 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
··· 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
··· 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
··· 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