social.mx raw
1 package acl
2
3 import (
4 "git.smesh.lol/moxie/pkg/mxutil"
5 "bytes"
6 "time"
7
8 "git.smesh.lol/morly/pkg/grapevine"
9 "git.smesh.lol/morly/pkg/store"
10 )
11
12 // Social is a WoT-depth-based ACL. Uses the grapevine package to
13 // compute follow-graph depth from admin seeds. Pubkeys at different
14 // depths get different treatment (the server can query depth for
15 // throttle decisions).
16 type Social struct {
17 wot *grapevine.WoT
18 admins [][]byte
19 maxDepth int32
20 refreshSec int32
21 lastRefresh int64
22 depthMap map[string]int32
23 }
24
25 func NewSocial(s *store.Engine, adminHexPubkeys []string, maxDepth, refreshSec int32) (sv *Social) {
26 admins := [][]byte{:0:len(adminHexPubkeys)}
27 for _, h := range adminHexPubkeys {
28 if pk := hexDec(h); len(pk) == 32 {
29 admins = mxutil.Ensure(admins, 1)
30 admins = push(admins, pk)
31 }
32 }
33 sc := &Social{
34 wot: grapevine.New(s),
35 admins: admins,
36 maxDepth: maxDepth,
37 refreshSec: refreshSec,
38 depthMap: map[string]int32{},
39 }
40 sc.refresh()
41 return sc
42 }
43
44 func (s *Social) AllowWrite(pubkey []byte, _ uint16) (ok bool) {
45 s.maybeRefresh()
46 for _, a := range s.admins {
47 if bytes.Equal(a, pubkey) {
48 return true
49 }
50 }
51 _, known := s.depthMap[string(pubkey)]
52 return known
53 }
54
55 func (s *Social) AllowRead([]byte) (ok bool) { return true }
56
57 // Depth returns the WoT depth for a pubkey. 0 = admin, -1 = outsider.
58 func (s *Social) Depth(pubkey []byte) (n int32) {
59 s.maybeRefresh()
60 for _, a := range s.admins {
61 if bytes.Equal(a, pubkey) {
62 return 0
63 }
64 }
65 if d, ok := s.depthMap[string(pubkey)]; ok {
66 return d
67 }
68 return -1
69 }
70
71 func (s *Social) maybeRefresh() {
72 now := time.Now().Unix()
73 if now-s.lastRefresh < int64(s.refreshSec) {
74 return
75 }
76 s.refresh()
77 }
78
79 func (s *Social) refresh() {
80 s.lastRefresh = time.Now().Unix()
81 m := map[string]int32{}
82 // Declared outside the loops: a declaration in the body of a self-mutating
83 // method allocates in the sovereign arena on every iteration.
84 var scores []grapevine.Score
85 var key string
86 for _, admin := range s.admins {
87 scores = s.wot.Compute(admin, s.maxDepth)
88 for _, sc := range scores {
89 key = string(sc.Pubkey)
90 if existing, ok := m[key]; ok && existing <= sc.Depth {
91 continue
92 }
93 m[key] = sc.Depth
94 }
95 }
96 s.depthMap = m
97 }
98