···1package state
23import (
4- "bytes"
5 "crypto/hmac"
6 "crypto/sha256"
7 "encoding/hex"
8- "encoding/json"
9 "fmt"
10 "log"
11 "net/http"
012 "strings"
13 "time"
14···234 }
235 log.Println("checking ", domain)
236237- url := fmt.Sprintf("http://%s/init", domain)
238-239- body, _ := json.Marshal(map[string]interface{}{
240- "did": user.Did,
241- "keys": []string{},
242- })
243- pingRequest, err := http.NewRequest("POST", url, bytes.NewBuffer(body))
244 if err != nil {
245- log.Println("failed to build ping request", err)
246 return
247 }
248249- secret, err := s.db.GetRegistrationKey(domain)
250 if err != nil {
251- log.Printf("no key found for domain %s: %s\n", domain, err)
252- return
253 }
254- client := SignedClient(secret)
255256- resp, err := client.Do(pingRequest)
257 if err != nil {
258 w.Write([]byte("no dice"))
259 log.Println("domain was unreachable after 5 seconds")
···338339 var members []string
340 if reg.Registered != nil {
341- members, err = s.enforcer.E.GetUsersForRole("server:member", domain)
342 if err != nil {
343 w.Write([]byte("failed to fetch member list"))
344 return
345 }
346 }
347348- ok, err := s.enforcer.E.HasGroupingPolicy(user.Did, "server:owner", domain)
349 isOwner := err == nil && ok
350351 p := pages.KnotParams{
···382 }
383384 // list all members for this domain
385- memberDids, err := s.enforcer.E.GetUsersForRole("server:member", domain)
386 if err != nil {
387 w.Write([]byte("failed to fetch member list"))
388 return
···433 log.Printf("failed to create record: %s", err)
434 return
435 }
0436437- log.Println("created atproto record: ", resp.Uri)
00000000000000000000438439 err = s.enforcer.AddMember(domain, memberIdent.DID.String())
440 if err != nil {
···481 return
482 }
483484- client := SignedClient(secret)
485- url := fmt.Sprintf("http://%s/repo/new", domain)
486- body, _ := json.Marshal(map[string]interface{}{
487- "did": user.Did,
488- "name": repoName,
489- })
490- createRepoRequest, err := http.NewRequest("PUT", url, bytes.NewReader(body))
491492- resp, err := client.Do(createRepoRequest)
493-494 if err != nil {
495 log.Println("failed to send create repo request", err)
496 return
497 }
498-499 if resp.StatusCode != http.StatusNoContent {
500 log.Println("server returned ", resp.StatusCode)
501 return
···507 Name: repoName,
508 Knot: domain,
509 }
510-511 err = s.db.AddRepo(repo)
512 if err != nil {
513 log.Println("failed to add repo to db", err)
0000000514 return
515 }
516
···1package state
23import (
04 "crypto/hmac"
5 "crypto/sha256"
6 "encoding/hex"
07 "fmt"
8 "log"
9 "net/http"
10+ "path/filepath"
11 "strings"
12 "time"
13···233 }
234 log.Println("checking ", domain)
235236+ secret, err := s.db.GetRegistrationKey(domain)
000000237 if err != nil {
238+ log.Printf("no key found for domain %s: %s\n", domain, err)
239 return
240 }
241242+ client, err := NewSignedClient(domain, secret)
243 if err != nil {
244+ log.Println("failed to create client to ", domain)
0245 }
0246247+ resp, err := client.Init(user.Did, []string{})
248 if err != nil {
249 w.Write([]byte("no dice"))
250 log.Println("domain was unreachable after 5 seconds")
···329330 var members []string
331 if reg.Registered != nil {
332+ members, err = s.enforcer.GetUserByRole("server:member", domain)
333 if err != nil {
334 w.Write([]byte("failed to fetch member list"))
335 return
336 }
337 }
338339+ ok, err := s.enforcer.IsServerOwner(user.Did, domain)
340 isOwner := err == nil && ok
341342 p := pages.KnotParams{
···373 }
374375 // list all members for this domain
376+ memberDids, err := s.enforcer.GetUserByRole("server:member", domain)
377 if err != nil {
378 w.Write([]byte("failed to fetch member list"))
379 return
···424 log.Printf("failed to create record: %s", err)
425 return
426 }
427+ log.Println("created atproto record: ", resp.Uri)
428429+ secret, err := s.db.GetRegistrationKey(domain)
430+ if err != nil {
431+ log.Printf("no key found for domain %s: %s\n", domain, err)
432+ return
433+ }
434+435+ ksClient, err := NewSignedClient(domain, secret)
436+ if err != nil {
437+ log.Println("failed to create client to ", domain)
438+ return
439+ }
440+441+ ksResp, err := ksClient.AddMember(memberIdent.DID.String(), []string{})
442+ if err != nil {
443+ log.Printf("failet to make request to %s: %s", domain, err)
444+ }
445+446+ if ksResp.StatusCode != http.StatusNoContent {
447+ w.Write([]byte(fmt.Sprint("knotserver failed to add member: ", err)))
448+ return
449+ }
450451 err = s.enforcer.AddMember(domain, memberIdent.DID.String())
452 if err != nil {
···493 return
494 }
495496+ client, err := NewSignedClient(domain, secret)
497+ if err != nil {
498+ log.Println("failed to create client to ", domain)
499+ }
000500501+ resp, err := client.NewRepo(user.Did, repoName)
0502 if err != nil {
503 log.Println("failed to send create repo request", err)
504 return
505 }
0506 if resp.StatusCode != http.StatusNoContent {
507 log.Println("server returned ", resp.StatusCode)
508 return
···514 Name: repoName,
515 Knot: domain,
516 }
0517 err = s.db.AddRepo(repo)
518 if err != nil {
519 log.Println("failed to add repo to db", err)
520+ return
521+ }
522+523+ // acls
524+ err = s.enforcer.AddRepo(user.Did, domain, filepath.Join(user.Did, repoName))
525+ if err != nil {
526+ log.Println("failed to set up acls", err)
527 return
528 }
529
+1-1
knotserver/handler.go
···9293 r.Route("/member", func(r chi.Router) {
94 r.Use(h.VerifySignature)
95- r.Put("/add", h.NewRepo)
96 })
9798 // Initialize the knot with an owner and public key.
···9293 r.Route("/member", func(r chi.Router) {
94 r.Use(h.VerifySignature)
95+ r.Put("/add", h.AddMember)
96 })
9798 // Initialize the knot with an owner and public key.
···3import (
4 "database/sql"
5 "path"
067 sqladapter "github.com/Blank-Xu/sql-adapter"
8 "github.com/casbin/casbin/v2"
···98 {"server:owner", domain, repo, "repo:delete"}, // server owner can delete any repo
99 })
100 return err
000000000000000000000000000000101}
102103// keyMatch2Func is a wrapper for keyMatch2 to make it compatible with Casbin
···3import (
4 "database/sql"
5 "path"
6+ "strings"
78 sqladapter "github.com/Blank-Xu/sql-adapter"
9 "github.com/casbin/casbin/v2"
···99 {"server:owner", domain, repo, "repo:delete"}, // server owner can delete any repo
100 })
101 return err
102+}
103+104+func (e *Enforcer) GetUserByRole(role, domain string) ([]string, error) {
105+ var membersWithoutRoles []string
106+107+ // this includes roles too, casbin does not differentiate.
108+ // the filtering criteria is to remove strings not starting with `did:`
109+ members, err := e.E.Enforcer.GetImplicitUsersForRole(role, domain)
110+ for _, m := range members {
111+ if strings.HasPrefix(m, "did:") {
112+ membersWithoutRoles = append(membersWithoutRoles, m)
113+ }
114+ }
115+ if err != nil {
116+ return nil, err
117+ }
118+119+ return membersWithoutRoles, nil
120+}
121+122+func (e *Enforcer) isRole(user, role, domain string) (bool, error) {
123+ return e.E.HasGroupingPolicy(user, role, domain)
124+}
125+126+func (e *Enforcer) IsServerOwner(user, domain string) (bool, error) {
127+ return e.isRole(user, "server:owner", domain)
128+}
129+130+func (e *Enforcer) IsServerMember(user, domain string) (bool, error) {
131+ return e.isRole(user, "server:member", domain)
132}
133134// keyMatch2Func is a wrapper for keyMatch2 to make it compatible with Casbin