mirror of
https://github.com/anyproto/any-sync.git
synced 2025-06-07 21:47:02 +09:00
198 lines
5.2 KiB
Go
198 lines
5.2 KiB
Go
//go:generate mockgen -destination mock_acl/mock_acl.go github.com/anyproto/any-sync/acl AclService
|
|
package acl
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"time"
|
|
|
|
"github.com/prometheus/client_golang/prometheus"
|
|
|
|
commonaccount "github.com/anyproto/any-sync/accountservice"
|
|
"github.com/anyproto/any-sync/app"
|
|
"github.com/anyproto/any-sync/app/logger"
|
|
"github.com/anyproto/any-sync/app/ocache"
|
|
"github.com/anyproto/any-sync/commonspace/object/acl/list"
|
|
"github.com/anyproto/any-sync/consensus/consensusclient"
|
|
"github.com/anyproto/any-sync/consensus/consensusproto"
|
|
"github.com/anyproto/any-sync/metric"
|
|
"github.com/anyproto/any-sync/util/crypto"
|
|
)
|
|
|
|
const CName = "coordinator.acl"
|
|
|
|
var log = logger.NewNamed(CName)
|
|
|
|
var ErrLimitExceed = errors.New("limit exceed")
|
|
|
|
type Limits struct {
|
|
ReadMembers uint32
|
|
WriteMembers uint32
|
|
}
|
|
|
|
func New() AclService {
|
|
return &aclService{}
|
|
}
|
|
|
|
type AclService interface {
|
|
AddRecord(ctx context.Context, spaceId string, rec *consensusproto.RawRecord, limits Limits) (result *consensusproto.RawRecordWithId, err error)
|
|
RecordsAfter(ctx context.Context, spaceId, aclHead string) (result []*consensusproto.RawRecordWithId, err error)
|
|
Permissions(ctx context.Context, identity crypto.PubKey, spaceId string) (res list.AclPermissions, err error)
|
|
OwnerPubKey(ctx context.Context, spaceId string) (ownerIdentity crypto.PubKey, err error)
|
|
ReadState(ctx context.Context, spaceId string, f func(s *list.AclState) error) (err error)
|
|
HasRecord(ctx context.Context, spaceId, recordId string) (has bool, err error)
|
|
app.ComponentRunnable
|
|
}
|
|
|
|
type aclService struct {
|
|
consService consensusclient.Service
|
|
cache ocache.OCache
|
|
accountService commonaccount.Service
|
|
}
|
|
|
|
func (as *aclService) Init(a *app.App) (err error) {
|
|
as.consService = app.MustComponent[consensusclient.Service](a)
|
|
as.accountService = app.MustComponent[commonaccount.Service](a)
|
|
|
|
var metricReg *prometheus.Registry
|
|
if m := a.Component(metric.CName); m != nil {
|
|
metricReg = m.(metric.Metric).Registry()
|
|
}
|
|
as.cache = ocache.New(as.loadObject,
|
|
ocache.WithTTL(5*time.Minute),
|
|
ocache.WithLogger(log.Sugar()),
|
|
ocache.WithPrometheus(metricReg, "acl", ""),
|
|
)
|
|
return
|
|
}
|
|
|
|
func (as *aclService) Name() (name string) {
|
|
return CName
|
|
}
|
|
|
|
func (as *aclService) loadObject(ctx context.Context, id string) (ocache.Object, error) {
|
|
return as.newAclObject(ctx, id)
|
|
}
|
|
|
|
func (as *aclService) get(ctx context.Context, spaceId string) (list.AclList, error) {
|
|
obj, err := as.cache.Get(ctx, spaceId)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
aObj := obj.(*aclObject)
|
|
aObj.lastUsage.Store(time.Now())
|
|
return aObj.AclList, nil
|
|
}
|
|
|
|
func (as *aclService) AddRecord(ctx context.Context, spaceId string, rec *consensusproto.RawRecord, limits Limits) (result *consensusproto.RawRecordWithId, err error) {
|
|
if limits.ReadMembers <= 1 && limits.WriteMembers <= 1 {
|
|
return nil, ErrLimitExceed
|
|
}
|
|
|
|
acl, err := as.get(ctx, spaceId)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
acl.RLock()
|
|
defer acl.RUnlock()
|
|
|
|
var beforeReaders, beforeWriters int
|
|
for _, acc := range acl.AclState().CurrentAccounts() {
|
|
if !acc.Permissions.NoPermissions() {
|
|
beforeReaders++
|
|
}
|
|
if acc.Permissions.CanWrite() {
|
|
beforeWriters++
|
|
}
|
|
}
|
|
|
|
err = acl.ValidateRawRecord(rec, func(state *list.AclState) error {
|
|
var readers, writers int
|
|
for _, acc := range state.CurrentAccounts() {
|
|
if acc.Permissions.NoPermissions() {
|
|
continue
|
|
}
|
|
readers++
|
|
if acc.Permissions.CanWrite() {
|
|
writers++
|
|
}
|
|
}
|
|
if readers > beforeReaders && uint32(readers) > limits.ReadMembers {
|
|
return ErrLimitExceed
|
|
}
|
|
if writers > beforeWriters && uint32(writers) > limits.WriteMembers {
|
|
return ErrLimitExceed
|
|
}
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
return as.consService.AddRecord(ctx, spaceId, rec)
|
|
}
|
|
|
|
func (as *aclService) RecordsAfter(ctx context.Context, spaceId, aclHead string) (result []*consensusproto.RawRecordWithId, err error) {
|
|
acl, err := as.get(ctx, spaceId)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
acl.RLock()
|
|
defer acl.RUnlock()
|
|
return acl.RecordsAfter(ctx, aclHead)
|
|
}
|
|
|
|
func (as *aclService) OwnerPubKey(ctx context.Context, spaceId string) (ownerIdentity crypto.PubKey, err error) {
|
|
acl, err := as.get(ctx, spaceId)
|
|
if err != nil {
|
|
return
|
|
}
|
|
acl.RLock()
|
|
defer acl.RUnlock()
|
|
return acl.AclState().OwnerPubKey()
|
|
}
|
|
|
|
func (as *aclService) Permissions(ctx context.Context, identity crypto.PubKey, spaceId string) (res list.AclPermissions, err error) {
|
|
acl, err := as.get(ctx, spaceId)
|
|
if err != nil {
|
|
return
|
|
}
|
|
acl.RLock()
|
|
defer acl.RUnlock()
|
|
return acl.AclState().Permissions(identity), nil
|
|
}
|
|
|
|
func (as *aclService) ReadState(ctx context.Context, spaceId string, f func(s *list.AclState) error) (err error) {
|
|
acl, err := as.get(ctx, spaceId)
|
|
if err != nil {
|
|
return
|
|
}
|
|
acl.RLock()
|
|
defer acl.RUnlock()
|
|
return f(acl.AclState())
|
|
}
|
|
|
|
func (as *aclService) HasRecord(ctx context.Context, spaceId, recordId string) (has bool, err error) {
|
|
acl, err := as.get(ctx, spaceId)
|
|
if err != nil {
|
|
return
|
|
}
|
|
acl.RLock()
|
|
defer acl.RUnlock()
|
|
acl.Iterate(func(record *list.AclRecord) (isContinue bool) {
|
|
if record.Id == recordId {
|
|
has = true
|
|
return false
|
|
}
|
|
return true
|
|
})
|
|
return
|
|
}
|
|
|
|
func (as *aclService) Run(ctx context.Context) (err error) {
|
|
return
|
|
}
|
|
|
|
func (as *aclService) Close(ctx context.Context) (err error) {
|
|
return as.cache.Close()
|
|
}
|