social.mx raw

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