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