+93
-9
appview/state/signer.go
+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
+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
+1
-1
knotserver/handler.go
+8
-3
knotserver/jetstream.go
+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
+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