forked from tangled.org/core
Monorepo for Tangled — https://tangled.org

refactor signed client

Changed files
+176 -43
appview
knotserver
rbac
+93 -9
appview/state/signer.go
··· 1 1 package state 2 2 3 3 import ( 4 + "bytes" 4 5 "crypto/hmac" 5 6 "crypto/sha256" 6 7 "encoding/hex" 8 + "encoding/json" 9 + "fmt" 7 10 "net/http" 11 + "net/url" 8 12 "time" 9 13 ) 10 14 11 15 type SignerTransport struct { 12 16 Secret string 13 - } 14 - 15 - func SignedClient(secret string) *http.Client { 16 - return &http.Client{ 17 - Timeout: 5 * time.Second, 18 - Transport: SignerTransport{ 19 - Secret: secret, 20 - }, 21 - } 22 17 } 23 18 24 19 func (s SignerTransport) RoundTrip(req *http.Request) (*http.Response, error) { ··· 31 26 req.Header.Set("X-Timestamp", timestamp) 32 27 return http.DefaultTransport.RoundTrip(req) 33 28 } 29 + 30 + type SignedClient struct { 31 + Secret string 32 + Url *url.URL 33 + client *http.Client 34 + } 35 + 36 + func NewSignedClient(domain, secret string) (*SignedClient, error) { 37 + client := &http.Client{ 38 + Timeout: 5 * time.Second, 39 + Transport: SignerTransport{ 40 + Secret: secret, 41 + }, 42 + } 43 + 44 + url, err := url.Parse(fmt.Sprintf("http://%s", domain)) 45 + if err != nil { 46 + return nil, err 47 + } 48 + 49 + signedClient := &SignedClient{ 50 + Secret: secret, 51 + client: client, 52 + Url: url, 53 + } 54 + 55 + return signedClient, nil 56 + } 57 + 58 + func (s *SignedClient) newRequest(method, endpoint string, body []byte) (*http.Request, error) { 59 + return http.NewRequest(method, s.Url.JoinPath(endpoint).String(), bytes.NewReader(body)) 60 + } 61 + 62 + func (s *SignedClient) Init(did string, keys []string) (*http.Response, error) { 63 + const ( 64 + Method = "POST" 65 + Endpoint = "/init" 66 + ) 67 + 68 + body, _ := json.Marshal(map[string]interface{}{ 69 + "did": did, 70 + "keys": keys, 71 + }) 72 + 73 + req, err := s.newRequest(Method, Endpoint, body) 74 + if err != nil { 75 + return nil, err 76 + } 77 + 78 + return s.client.Do(req) 79 + } 80 + 81 + func (s *SignedClient) NewRepo(did, repoName string) (*http.Response, error) { 82 + const ( 83 + Method = "PUT" 84 + Endpoint = "/repo/new" 85 + ) 86 + 87 + body, _ := json.Marshal(map[string]interface{}{ 88 + "did": did, 89 + "name": repoName, 90 + }) 91 + 92 + req, err := s.newRequest(Method, Endpoint, body) 93 + if err != nil { 94 + return nil, err 95 + } 96 + 97 + return s.client.Do(req) 98 + } 99 + 100 + func (s *SignedClient) AddMember(did string, keys []string) (*http.Response, error) { 101 + const ( 102 + Method = "PUT" 103 + Endpoint = "/member/add" 104 + ) 105 + 106 + body, _ := json.Marshal(map[string]interface{}{ 107 + "did": did, 108 + "keys": keys, 109 + }) 110 + 111 + req, err := s.newRequest(Method, Endpoint, body) 112 + if err != nil { 113 + return nil, err 114 + } 115 + 116 + return s.client.Do(req) 117 + }
+43 -30
appview/state/state.go
··· 1 1 package state 2 2 3 3 import ( 4 - "bytes" 5 4 "crypto/hmac" 6 5 "crypto/sha256" 7 6 "encoding/hex" 8 - "encoding/json" 9 7 "fmt" 10 8 "log" 11 9 "net/http" 10 + "path/filepath" 12 11 "strings" 13 12 "time" 14 13 ··· 234 233 } 235 234 log.Println("checking ", domain) 236 235 237 - 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)) 236 + secret, err := s.db.GetRegistrationKey(domain) 244 237 if err != nil { 245 - log.Println("failed to build ping request", err) 238 + log.Printf("no key found for domain %s: %s\n", domain, err) 246 239 return 247 240 } 248 241 249 - secret, err := s.db.GetRegistrationKey(domain) 242 + client, err := NewSignedClient(domain, secret) 250 243 if err != nil { 251 - log.Printf("no key found for domain %s: %s\n", domain, err) 252 - return 244 + log.Println("failed to create client to ", domain) 253 245 } 254 - client := SignedClient(secret) 255 246 256 - resp, err := client.Do(pingRequest) 247 + resp, err := client.Init(user.Did, []string{}) 257 248 if err != nil { 258 249 w.Write([]byte("no dice")) 259 250 log.Println("domain was unreachable after 5 seconds") ··· 338 329 339 330 var members []string 340 331 if reg.Registered != nil { 341 - members, err = s.enforcer.E.GetUsersForRole("server:member", domain) 332 + members, err = s.enforcer.GetUserByRole("server:member", domain) 342 333 if err != nil { 343 334 w.Write([]byte("failed to fetch member list")) 344 335 return 345 336 } 346 337 } 347 338 348 - ok, err := s.enforcer.E.HasGroupingPolicy(user.Did, "server:owner", domain) 339 + ok, err := s.enforcer.IsServerOwner(user.Did, domain) 349 340 isOwner := err == nil && ok 350 341 351 342 p := pages.KnotParams{ ··· 382 373 } 383 374 384 375 // list all members for this domain 385 - memberDids, err := s.enforcer.E.GetUsersForRole("server:member", domain) 376 + memberDids, err := s.enforcer.GetUserByRole("server:member", domain) 386 377 if err != nil { 387 378 w.Write([]byte("failed to fetch member list")) 388 379 return ··· 433 424 log.Printf("failed to create record: %s", err) 434 425 return 435 426 } 427 + log.Println("created atproto record: ", resp.Uri) 436 428 437 - log.Println("created atproto record: ", resp.Uri) 429 + 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 + } 438 450 439 451 err = s.enforcer.AddMember(domain, memberIdent.DID.String()) 440 452 if err != nil { ··· 481 493 return 482 494 } 483 495 484 - 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)) 496 + client, err := NewSignedClient(domain, secret) 497 + if err != nil { 498 + log.Println("failed to create client to ", domain) 499 + } 491 500 492 - resp, err := client.Do(createRepoRequest) 493 - 501 + resp, err := client.NewRepo(user.Did, repoName) 494 502 if err != nil { 495 503 log.Println("failed to send create repo request", err) 496 504 return 497 505 } 498 - 499 506 if resp.StatusCode != http.StatusNoContent { 500 507 log.Println("server returned ", resp.StatusCode) 501 508 return ··· 507 514 Name: repoName, 508 515 Knot: domain, 509 516 } 510 - 511 517 err = s.db.AddRepo(repo) 512 518 if err != nil { 513 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) 514 527 return 515 528 } 516 529
+1 -1
knotserver/handler.go
··· 92 92 93 93 r.Route("/member", func(r chi.Router) { 94 94 r.Use(h.VerifySignature) 95 - r.Put("/add", h.NewRepo) 95 + r.Put("/add", h.AddMember) 96 96 }) 97 97 98 98 // Initialize the knot with an owner and public key.
+8 -3
knotserver/jetstream.go
··· 7 7 "io" 8 8 "log" 9 9 "net/http" 10 - "path" 10 + "net/url" 11 11 "strings" 12 12 "time" 13 13 ··· 66 66 } 67 67 68 68 func (h *Handle) fetchAndAddKeys(did string) { 69 - resp, err := http.Get(path.Join(h.c.AppViewEndpoint, did)) 69 + keysEndpoint, err := url.JoinPath(h.c.AppViewEndpoint, "keys", did) 70 + if err != nil { 71 + log.Printf("error building endpoint url: %s: %v", did, err) 72 + return 73 + } 74 + 75 + resp, err := http.Get(keysEndpoint) 70 76 if err != nil { 71 77 log.Printf("error getting keys for %s: %v", did, err) 72 78 return ··· 112 118 } 113 119 114 120 func (h *Handle) processMessages(messages <-chan []byte) { 115 - log.Println("waiting for knot to be initialized") 116 121 <-h.init 117 122 log.Println("initalized jetstream watcher") 118 123
+31
rbac/rbac.go
··· 3 3 import ( 4 4 "database/sql" 5 5 "path" 6 + "strings" 6 7 7 8 sqladapter "github.com/Blank-Xu/sql-adapter" 8 9 "github.com/casbin/casbin/v2" ··· 98 99 {"server:owner", domain, repo, "repo:delete"}, // server owner can delete any repo 99 100 }) 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) 101 132 } 102 133 103 134 // keyMatch2Func is a wrapper for keyMatch2 to make it compatible with Casbin