node acl service (wip), fixes
This commit is contained in:
parent
348acd6d84
commit
41a3f0502e
@ -37,7 +37,7 @@ func newListStorage(spaceId string, db *badger.DB, txn *badger.Txn) (ls storage.
|
|||||||
|
|
||||||
ls = &listStorage{
|
ls = &listStorage{
|
||||||
db: db,
|
db: db,
|
||||||
keys: keys,
|
keys: newACLKeys(spaceId),
|
||||||
id: stringId,
|
id: stringId,
|
||||||
root: rootWithId,
|
root: rootWithId,
|
||||||
}
|
}
|
||||||
@ -70,14 +70,14 @@ func createListStorage(spaceId string, db *badger.DB, txn *badger.Txn, root *acl
|
|||||||
|
|
||||||
ls = &listStorage{
|
ls = &listStorage{
|
||||||
db: db,
|
db: db,
|
||||||
keys: keys,
|
keys: newACLKeys(spaceId),
|
||||||
id: root.Id,
|
id: root.Id,
|
||||||
root: root,
|
root: root,
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *listStorage) ID() string {
|
func (l *listStorage) Id() string {
|
||||||
return l.id
|
return l.id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -3,7 +3,7 @@ package storage
|
|||||||
import (
|
import (
|
||||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/spacesyncproto"
|
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/spacesyncproto"
|
||||||
spacestorage "github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/storage"
|
spacestorage "github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/storage"
|
||||||
storage2 "github.com/anytypeio/go-anytype-infrastructure-experiments/common/pkg/acl/storage"
|
storage "github.com/anytypeio/go-anytype-infrastructure-experiments/common/pkg/acl/storage"
|
||||||
"github.com/dgraph-io/badger/v3"
|
"github.com/dgraph-io/badger/v3"
|
||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
@ -12,7 +12,7 @@ type spaceStorage struct {
|
|||||||
spaceId string
|
spaceId string
|
||||||
objDb *badger.DB
|
objDb *badger.DB
|
||||||
keys spaceKeys
|
keys spaceKeys
|
||||||
aclStorage storage2.ListStorage
|
aclStorage storage.ListStorage
|
||||||
header *spacesyncproto.RawSpaceHeaderWithId
|
header *spacesyncproto.RawSpaceHeaderWithId
|
||||||
mx sync.Mutex
|
mx sync.Mutex
|
||||||
}
|
}
|
||||||
@ -77,15 +77,15 @@ func createSpaceStorage(db *badger.DB, payload spacestorage.SpaceStorageCreatePa
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *spaceStorage) ID() string {
|
func (s *spaceStorage) Id() string {
|
||||||
return s.spaceId
|
return s.spaceId
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *spaceStorage) TreeStorage(id string) (storage2.TreeStorage, error) {
|
func (s *spaceStorage) TreeStorage(id string) (storage.TreeStorage, error) {
|
||||||
return newTreeStorage(s.objDb, s.spaceId, id)
|
return newTreeStorage(s.objDb, s.spaceId, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *spaceStorage) CreateTreeStorage(payload storage2.TreeStorageCreatePayload) (ts storage2.TreeStorage, err error) {
|
func (s *spaceStorage) CreateTreeStorage(payload storage.TreeStorageCreatePayload) (ts storage.TreeStorage, err error) {
|
||||||
// we have mutex here, so we prevent overwriting the heads of a tree on concurrent creation
|
// we have mutex here, so we prevent overwriting the heads of a tree on concurrent creation
|
||||||
s.mx.Lock()
|
s.mx.Lock()
|
||||||
defer s.mx.Unlock()
|
defer s.mx.Unlock()
|
||||||
@ -93,7 +93,7 @@ func (s *spaceStorage) CreateTreeStorage(payload storage2.TreeStorageCreatePaylo
|
|||||||
return createTreeStorage(s.objDb, s.spaceId, payload)
|
return createTreeStorage(s.objDb, s.spaceId, payload)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *spaceStorage) ACLStorage() (storage2.ListStorage, error) {
|
func (s *spaceStorage) ACLStorage() (storage.ListStorage, error) {
|
||||||
return s.aclStorage, nil
|
return s.aclStorage, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -2,7 +2,7 @@ package storage
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
storage "github.com/anytypeio/go-anytype-infrastructure-experiments/common/pkg/acl/storage"
|
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/pkg/acl/storage"
|
||||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/pkg/acl/treechangeproto"
|
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/pkg/acl/treechangeproto"
|
||||||
"github.com/dgraph-io/badger/v3"
|
"github.com/dgraph-io/badger/v3"
|
||||||
)
|
)
|
||||||
@ -40,14 +40,11 @@ func newTreeStorage(db *badger.DB, spaceId, treeId string) (ts storage.TreeStora
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
if err == badger.ErrKeyNotFound {
|
|
||||||
err = storage.ErrUnknownTreeId
|
|
||||||
}
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func createTreeStorage(db *badger.DB, spaceId string, payload storage.TreeStorageCreatePayload) (ts storage.TreeStorage, err error) {
|
func createTreeStorage(db *badger.DB, spaceId string, payload storage.TreeStorageCreatePayload) (ts storage.TreeStorage, err error) {
|
||||||
keys := newTreeKeys(spaceId, payload.RootRawChange.Id)
|
keys := newTreeKeys(spaceId, payload.TreeId)
|
||||||
if hasDB(db, keys.RootIdKey()) {
|
if hasDB(db, keys.RootIdKey()) {
|
||||||
err = storage.ErrTreeExists
|
err = storage.ErrTreeExists
|
||||||
return
|
return
|
||||||
@ -88,7 +85,7 @@ func createTreeStorage(db *badger.DB, spaceId string, payload storage.TreeStorag
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *treeStorage) ID() string {
|
func (t *treeStorage) Id() string {
|
||||||
return t.id
|
return t.id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -6,15 +6,12 @@ import (
|
|||||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/app"
|
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/app"
|
||||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/app/logger"
|
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/app/logger"
|
||||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/diffservice"
|
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/diffservice"
|
||||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/spacesyncproto"
|
|
||||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/storage"
|
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/storage"
|
||||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/syncservice"
|
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/syncservice"
|
||||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/treegetter"
|
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/treegetter"
|
||||||
config2 "github.com/anytypeio/go-anytype-infrastructure-experiments/common/config"
|
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/config"
|
||||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/net/peer"
|
|
||||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/net/pool"
|
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/net/pool"
|
||||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/nodeconf"
|
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/nodeconf"
|
||||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/pkg/acl/aclrecordproto"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const CName = "common.commonspace"
|
const CName = "common.commonspace"
|
||||||
@ -29,12 +26,11 @@ type Service interface {
|
|||||||
DeriveSpace(ctx context.Context, payload SpaceDerivePayload) (string, error)
|
DeriveSpace(ctx context.Context, payload SpaceDerivePayload) (string, error)
|
||||||
CreateSpace(ctx context.Context, payload SpaceCreatePayload) (string, error)
|
CreateSpace(ctx context.Context, payload SpaceCreatePayload) (string, error)
|
||||||
GetSpace(ctx context.Context, id string) (sp Space, err error)
|
GetSpace(ctx context.Context, id string) (sp Space, err error)
|
||||||
AddSpace(ctx context.Context, spaceDescription SpaceDescription) (err error)
|
|
||||||
app.Component
|
app.Component
|
||||||
}
|
}
|
||||||
|
|
||||||
type service struct {
|
type service struct {
|
||||||
config config2.Space
|
config config.Space
|
||||||
account account.Service
|
account account.Service
|
||||||
configurationService nodeconf.Service
|
configurationService nodeconf.Service
|
||||||
storageProvider storage.SpaceStorageProvider
|
storageProvider storage.SpaceStorageProvider
|
||||||
@ -43,7 +39,7 @@ type service struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *service) Init(a *app.App) (err error) {
|
func (s *service) Init(a *app.App) (err error) {
|
||||||
s.config = a.MustComponent(config2.CName).(*config2.Config).Space
|
s.config = a.MustComponent(config.CName).(*config.Config).Space
|
||||||
s.account = a.MustComponent(account.CName).(account.Service)
|
s.account = a.MustComponent(account.CName).(account.Service)
|
||||||
s.storageProvider = a.MustComponent(storage.CName).(storage.SpaceStorageProvider)
|
s.storageProvider = a.MustComponent(storage.CName).(storage.SpaceStorageProvider)
|
||||||
s.configurationService = a.MustComponent(nodeconf.CName).(nodeconf.Service)
|
s.configurationService = a.MustComponent(nodeconf.CName).(nodeconf.Service)
|
||||||
@ -56,9 +52,7 @@ func (s *service) Name() (name string) {
|
|||||||
return CName
|
return CName
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *service) CreateSpace(
|
func (s *service) CreateSpace(ctx context.Context, payload SpaceCreatePayload) (id string, err error) {
|
||||||
ctx context.Context,
|
|
||||||
payload SpaceCreatePayload) (id string, err error) {
|
|
||||||
storageCreate, err := storagePayloadForSpaceCreate(payload)
|
storageCreate, err := storagePayloadForSpaceCreate(payload)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
@ -68,12 +62,10 @@ func (s *service) CreateSpace(
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
return store.ID(), nil
|
return store.Id(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *service) DeriveSpace(
|
func (s *service) DeriveSpace(ctx context.Context, payload SpaceDerivePayload) (id string, err error) {
|
||||||
ctx context.Context,
|
|
||||||
payload SpaceDerivePayload) (id string, err error) {
|
|
||||||
storageCreate, err := storagePayloadForSpaceDerive(payload)
|
storageCreate, err := storagePayloadForSpaceDerive(payload)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
@ -83,53 +75,15 @@ func (s *service) DeriveSpace(
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
return store.ID(), nil
|
return store.Id(), nil
|
||||||
}
|
|
||||||
|
|
||||||
func (s *service) AddSpace(ctx context.Context, spaceDescription SpaceDescription) (err error) {
|
|
||||||
_, err = s.storageProvider.SpaceStorage(spaceDescription.SpaceHeader.Id)
|
|
||||||
if err == nil {
|
|
||||||
err = spacesyncproto.ErrSpaceExists
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err != storage.ErrSpaceStorageMissing {
|
|
||||||
err = spacesyncproto.ErrUnexpected
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
payload := storage.SpaceStorageCreatePayload{
|
|
||||||
RecWithId: &aclrecordproto.RawACLRecordWithId{
|
|
||||||
Payload: spaceDescription.AclPayload,
|
|
||||||
Id: spaceDescription.AclId,
|
|
||||||
},
|
|
||||||
SpaceHeaderWithId: spaceDescription.SpaceHeader,
|
|
||||||
}
|
|
||||||
st, err := s.storageProvider.CreateSpaceStorage(payload)
|
|
||||||
if err != nil {
|
|
||||||
err = spacesyncproto.ErrUnexpected
|
|
||||||
if err == storage.ErrSpaceStorageExists {
|
|
||||||
err = spacesyncproto.ErrSpaceExists
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = st.Close()
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *service) GetSpace(ctx context.Context, id string) (Space, error) {
|
func (s *service) GetSpace(ctx context.Context, id string) (Space, error) {
|
||||||
st, err := s.storageProvider.SpaceStorage(id)
|
st, err := s.storageProvider.SpaceStorage(id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err != spacesyncproto.ErrSpaceMissing {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
st, err = s.getSpaceStorageFromRemote(ctx, id)
|
|
||||||
if err != nil {
|
|
||||||
err = storage.ErrSpaceStorageMissing
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
lastConfiguration := s.configurationService.GetLast()
|
lastConfiguration := s.configurationService.GetLast()
|
||||||
confConnector := nodeconf.NewConfConnector(lastConfiguration, s.pool)
|
confConnector := nodeconf.NewConfConnector(lastConfiguration, s.pool)
|
||||||
diffService := diffservice.NewDiffService(id, s.config.SyncPeriod, st, confConnector, s.treeGetter, log)
|
diffService := diffservice.NewDiffService(id, s.config.SyncPeriod, st, confConnector, s.treeGetter, log)
|
||||||
@ -148,38 +102,3 @@ func (s *service) GetSpace(ctx context.Context, id string) (Space, error) {
|
|||||||
}
|
}
|
||||||
return sp, nil
|
return sp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *service) getSpaceStorageFromRemote(ctx context.Context, id string) (st storage.SpaceStorage, err error) {
|
|
||||||
var p peer.Peer
|
|
||||||
peerId, err := syncservice.GetPeerIdFromStreamContext(ctx)
|
|
||||||
if err == nil {
|
|
||||||
p, err = s.pool.Dial(ctx, peerId)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
lastConfiguration := s.configurationService.GetLast()
|
|
||||||
// for nodes we always get remote space only if we have id in the context
|
|
||||||
if lastConfiguration.IsResponsible(id) {
|
|
||||||
err = spacesyncproto.ErrSpaceMissing
|
|
||||||
return
|
|
||||||
}
|
|
||||||
p, err = s.pool.DialOneOf(ctx, lastConfiguration.NodeIds(id))
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
cl := spacesyncproto.NewDRPCSpaceClient(p)
|
|
||||||
res, err := cl.PullSpace(ctx, &spacesyncproto.PullSpaceRequest{Id: id})
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
st, err = s.storageProvider.CreateSpaceStorage(storage.SpaceStorageCreatePayload{
|
|
||||||
RecWithId: &aclrecordproto.RawACLRecordWithId{
|
|
||||||
Payload: res.AclPayload,
|
|
||||||
Id: res.AclPayloadId,
|
|
||||||
},
|
|
||||||
SpaceHeaderWithId: res.SpaceHeader,
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|||||||
@ -162,18 +162,18 @@ func (mr *MockSpaceStorageMockRecorder) CreateTreeStorage(arg0 interface{}) *gom
|
|||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateTreeStorage", reflect.TypeOf((*MockSpaceStorage)(nil).CreateTreeStorage), arg0)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateTreeStorage", reflect.TypeOf((*MockSpaceStorage)(nil).CreateTreeStorage), arg0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ID mocks base method.
|
// Id mocks base method.
|
||||||
func (m *MockSpaceStorage) ID() string {
|
func (m *MockSpaceStorage) Id() string {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "ID")
|
ret := m.ctrl.Call(m, "Id")
|
||||||
ret0, _ := ret[0].(string)
|
ret0, _ := ret[0].(string)
|
||||||
return ret0
|
return ret0
|
||||||
}
|
}
|
||||||
|
|
||||||
// ID indicates an expected call of ID.
|
// Id indicates an expected call of Id.
|
||||||
func (mr *MockSpaceStorageMockRecorder) ID() *gomock.Call {
|
func (mr *MockSpaceStorageMockRecorder) Id() *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ID", reflect.TypeOf((*MockSpaceStorage)(nil).ID))
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Id", reflect.TypeOf((*MockSpaceStorage)(nil).Id))
|
||||||
}
|
}
|
||||||
|
|
||||||
// SpaceHeader mocks base method.
|
// SpaceHeader mocks base method.
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import (
|
|||||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/app"
|
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/app"
|
||||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/spacesyncproto"
|
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/spacesyncproto"
|
||||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/pkg/acl/aclrecordproto"
|
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/pkg/acl/aclrecordproto"
|
||||||
storage "github.com/anytypeio/go-anytype-infrastructure-experiments/common/pkg/acl/storage"
|
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/pkg/acl/storage"
|
||||||
)
|
)
|
||||||
|
|
||||||
const CName = "commonspace.storage"
|
const CName = "commonspace.storage"
|
||||||
@ -16,7 +16,7 @@ var ErrSpaceStorageMissing = errors.New("space storage missing")
|
|||||||
|
|
||||||
type SpaceStorage interface {
|
type SpaceStorage interface {
|
||||||
storage.Provider
|
storage.Provider
|
||||||
ID() string
|
Id() string
|
||||||
ACLStorage() (storage.ListStorage, error)
|
ACLStorage() (storage.ListStorage, error)
|
||||||
SpaceHeader() (*spacesyncproto.RawSpaceHeaderWithId, error)
|
SpaceHeader() (*spacesyncproto.RawSpaceHeaderWithId, error)
|
||||||
StoredIds() ([]string, error)
|
StoredIds() ([]string, error)
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -10,6 +10,8 @@ message RawACLRecord {
|
|||||||
message RawACLRecordWithId {
|
message RawACLRecordWithId {
|
||||||
bytes payload = 1;
|
bytes payload = 1;
|
||||||
string id = 2;
|
string id = 2;
|
||||||
|
bytes acceptorIdentity = 3;
|
||||||
|
bytes acceptorSignature = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
message ACLRecord {
|
message ACLRecord {
|
||||||
@ -63,24 +65,31 @@ message ACLUserAdd {
|
|||||||
ACLUserPermissions permissions = 4;
|
ACLUserPermissions permissions = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// accept key, encrypt key, invite id
|
||||||
|
// GetSpace(id) -> ... (space header + acl root) -> diff
|
||||||
|
// Join(ACLJoinRecord) -> Ok
|
||||||
|
|
||||||
message ACLUserInvite {
|
message ACLUserInvite {
|
||||||
bytes acceptPublicKey = 1;
|
bytes acceptPublicKey = 1;
|
||||||
uint64 encryptSymKeyHash = 2;
|
// TODO: change to read key
|
||||||
|
bytes encryptPublicKey = 2;
|
||||||
repeated bytes encryptedReadKeys = 3;
|
repeated bytes encryptedReadKeys = 3;
|
||||||
ACLUserPermissions permissions = 4;
|
ACLUserPermissions permissions = 4;
|
||||||
|
// TODO: either derive inviteId from pub keys or think if it is possible to just use ACL record id
|
||||||
|
string inviteId = 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
message ACLUserJoin {
|
message ACLUserJoin {
|
||||||
bytes identity = 1;
|
bytes identity = 1;
|
||||||
bytes encryptionKey = 2;
|
bytes encryptionKey = 2;
|
||||||
bytes acceptSignature = 3;
|
bytes acceptSignature = 3;
|
||||||
bytes acceptPubKey = 4;
|
string inviteId = 4;
|
||||||
repeated bytes encryptedReadKeys = 5;
|
repeated bytes encryptedReadKeys = 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
message ACLUserRemove {
|
message ACLUserRemove {
|
||||||
bytes identity = 1;
|
bytes identity = 1;
|
||||||
repeated ACLReadKeyReplace readKeyReplaces = 2;
|
repeated ACLReadKeyReplace readKeyReplaces = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
message ACLReadKeyReplace {
|
message ACLReadKeyReplace {
|
||||||
@ -99,3 +108,19 @@ enum ACLUserPermissions {
|
|||||||
Writer = 1;
|
Writer = 1;
|
||||||
Reader = 2;
|
Reader = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
message ACLSyncMessage {
|
||||||
|
ACLSyncContentValue content = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ACLSyncContentValue provides different types for acl sync
|
||||||
|
message ACLSyncContentValue {
|
||||||
|
oneof value {
|
||||||
|
ACLAddRecords addRecords = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
message ACLAddRecords {
|
||||||
|
repeated RawACLRecordWithId records = 1;
|
||||||
|
}
|
||||||
@ -25,11 +25,10 @@ type RWLocker interface {
|
|||||||
type ACLList interface {
|
type ACLList interface {
|
||||||
RWLocker
|
RWLocker
|
||||||
ID() string
|
ID() string
|
||||||
Root() *aclrecordproto.RawACLRecordWithId
|
Root() *aclrecordproto.ACLRoot
|
||||||
Records() []*ACLRecord
|
Records() []*ACLRecord
|
||||||
ACLState() *ACLState
|
ACLState() *ACLState
|
||||||
IsAfter(first string, second string) (bool, error)
|
IsAfter(first string, second string) (bool, error)
|
||||||
AddRawRecords(ctx context.Context, rec []*aclrecordproto.RawACLRecordWithId) (err error)
|
|
||||||
Head() *ACLRecord
|
Head() *ACLRecord
|
||||||
Get(id string) (*ACLRecord, error)
|
Get(id string) (*ACLRecord, error)
|
||||||
Iterate(iterFunc IterFunc)
|
Iterate(iterFunc IterFunc)
|
||||||
@ -38,32 +37,38 @@ type ACLList interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type aclList struct {
|
type aclList struct {
|
||||||
root *aclrecordproto.RawACLRecordWithId
|
root *aclrecordproto.ACLRoot
|
||||||
records []*ACLRecord
|
records []*ACLRecord
|
||||||
indexes map[string]int
|
indexes map[string]int
|
||||||
id string
|
id string
|
||||||
|
|
||||||
stateBuilder *aclStateBuilder
|
builder *aclStateBuilder
|
||||||
recordBuilder ACLRecordBuilder
|
|
||||||
aclState *ACLState
|
aclState *ACLState
|
||||||
keychain *common.Keychain
|
keychain *common.Keychain
|
||||||
storage storage.ListStorage
|
|
||||||
|
|
||||||
sync.RWMutex
|
sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func BuildACLListWithIdentity(acc *account.AccountData, storage storage.ListStorage) (ACLList, error) {
|
func BuildACLListWithIdentity(acc *account.AccountData, storage storage.ListStorage) (ACLList, error) {
|
||||||
id := storage.ID()
|
|
||||||
builder := newACLStateBuilderWithIdentity(acc)
|
builder := newACLStateBuilderWithIdentity(acc)
|
||||||
return build(id, builder, newACLRecordBuilder(id, common.NewKeychain()), storage)
|
return build(storage.Id(), builder, newACLRecordBuilder(storage.Id(), common.NewKeychain()), storage)
|
||||||
}
|
}
|
||||||
|
|
||||||
func BuildACLList(storage storage.ListStorage) (ACLList, error) {
|
func BuildACLList(storage storage.ListStorage) (ACLList, error) {
|
||||||
id := storage.ID()
|
return build(storage.Id(), newACLStateBuilder(), newACLRecordBuilder(storage.Id(), common.NewKeychain()), storage)
|
||||||
return build(id, newACLStateBuilder(), newACLRecordBuilder(id, common.NewKeychain()), storage)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func build(id string, stateBuilder *aclStateBuilder, recBuilder ACLRecordBuilder, storage storage.ListStorage) (list ACLList, err error) {
|
func build(id string, stateBuilder *aclStateBuilder, recBuilder ACLRecordBuilder, storage storage.ListStorage) (list ACLList, err error) {
|
||||||
|
// TODO: need to add context here
|
||||||
|
rootWithId, err := storage.Root()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
aclRecRoot, err := recBuilder.ConvertFromRaw(rootWithId)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
head, err := storage.Head()
|
head, err := storage.Head()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
@ -80,7 +85,7 @@ func build(id string, stateBuilder *aclStateBuilder, recBuilder ACLRecordBuilder
|
|||||||
}
|
}
|
||||||
records := []*ACLRecord{record}
|
records := []*ACLRecord{record}
|
||||||
|
|
||||||
for record.PrevId != "" {
|
for record.PrevId != "" && record.PrevId != id {
|
||||||
rawRecordWithId, err = storage.GetRawRecord(context.Background(), record.PrevId)
|
rawRecordWithId, err = storage.GetRawRecord(context.Background(), record.PrevId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
@ -92,6 +97,8 @@ func build(id string, stateBuilder *aclStateBuilder, recBuilder ACLRecordBuilder
|
|||||||
}
|
}
|
||||||
records = append(records, record)
|
records = append(records, record)
|
||||||
}
|
}
|
||||||
|
// adding root in the end, because we already parsed it
|
||||||
|
records = append(records, aclRecRoot)
|
||||||
|
|
||||||
indexes := make(map[string]int)
|
indexes := make(map[string]int)
|
||||||
for i, j := 0, len(records)-1; i < j; i, j = i+1, j-1 {
|
for i, j := 0, len(records)-1; i < j; i, j = i+1, j-1 {
|
||||||
@ -110,19 +117,12 @@ func build(id string, stateBuilder *aclStateBuilder, recBuilder ACLRecordBuilder
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
rootWithId, err := storage.Root()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
list = &aclList{
|
list = &aclList{
|
||||||
root: rootWithId,
|
root: aclRecRoot.Model.(*aclrecordproto.ACLRoot),
|
||||||
records: records,
|
records: records,
|
||||||
indexes: indexes,
|
indexes: indexes,
|
||||||
stateBuilder: stateBuilder,
|
builder: stateBuilder,
|
||||||
recordBuilder: recBuilder,
|
|
||||||
aclState: state,
|
aclState: state,
|
||||||
storage: storage,
|
|
||||||
id: id,
|
id: id,
|
||||||
RWMutex: sync.RWMutex{},
|
RWMutex: sync.RWMutex{},
|
||||||
}
|
}
|
||||||
@ -137,44 +137,10 @@ func (a *aclList) ID() string {
|
|||||||
return a.id
|
return a.id
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *aclList) Root() *aclrecordproto.RawACLRecordWithId {
|
func (a *aclList) Root() *aclrecordproto.ACLRoot {
|
||||||
return a.root
|
return a.root
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *aclList) AddRawRecords(ctx context.Context, records []*aclrecordproto.RawACLRecordWithId) (err error) {
|
|
||||||
if len(records) == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// converting and verifying
|
|
||||||
var aclRecords []*ACLRecord
|
|
||||||
for _, rec := range records {
|
|
||||||
var record *ACLRecord
|
|
||||||
record, err = a.recordBuilder.ConvertFromRaw(rec)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
aclRecords = append(aclRecords, record)
|
|
||||||
}
|
|
||||||
|
|
||||||
// trying to append them to state
|
|
||||||
err = a.stateBuilder.Append(a.aclState, aclRecords)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// saving to storage
|
|
||||||
for _, rec := range records {
|
|
||||||
err = a.storage.AddRawRecord(ctx, rec)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// setting new head
|
|
||||||
err = a.storage.SetHead(records[len(records)-1].Id)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *aclList) ACLState() *ACLState {
|
func (a *aclList) ACLState() *ACLState {
|
||||||
return a.aclState
|
return a.aclState
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,10 +9,8 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type inMemoryACLListStorage struct {
|
type inMemoryACLListStorage struct {
|
||||||
|
records []*aclrecordproto.RawACLRecordWithId
|
||||||
id string
|
id string
|
||||||
root *aclrecordproto.RawACLRecordWithId
|
|
||||||
head string
|
|
||||||
records map[string]*aclrecordproto.RawACLRecordWithId
|
|
||||||
|
|
||||||
sync.RWMutex
|
sync.RWMutex
|
||||||
}
|
}
|
||||||
@ -20,63 +18,48 @@ type inMemoryACLListStorage struct {
|
|||||||
func NewInMemoryACLListStorage(
|
func NewInMemoryACLListStorage(
|
||||||
id string,
|
id string,
|
||||||
records []*aclrecordproto.RawACLRecordWithId) (ListStorage, error) {
|
records []*aclrecordproto.RawACLRecordWithId) (ListStorage, error) {
|
||||||
|
|
||||||
allRecords := make(map[string]*aclrecordproto.RawACLRecordWithId)
|
|
||||||
for _, ch := range records {
|
|
||||||
allRecords[ch.Id] = ch
|
|
||||||
}
|
|
||||||
root := records[0]
|
|
||||||
head := records[len(records)-1]
|
|
||||||
|
|
||||||
return &inMemoryACLListStorage{
|
return &inMemoryACLListStorage{
|
||||||
id: root.Id,
|
id: id,
|
||||||
root: root,
|
records: records,
|
||||||
head: head.Id,
|
|
||||||
records: allRecords,
|
|
||||||
RWMutex: sync.RWMutex{},
|
RWMutex: sync.RWMutex{},
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *inMemoryACLListStorage) ID() string {
|
func (i *inMemoryACLListStorage) Root() (*aclrecordproto.RawACLRecordWithId, error) {
|
||||||
t.RLock()
|
i.RLock()
|
||||||
defer t.RUnlock()
|
defer i.RUnlock()
|
||||||
return t.id
|
return i.records[0], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *inMemoryACLListStorage) Root() (*aclrecordproto.RawACLRecordWithId, error) {
|
func (i *inMemoryACLListStorage) SetHead(headId string) error {
|
||||||
t.RLock()
|
panic("implement me")
|
||||||
defer t.RUnlock()
|
|
||||||
return t.root, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *inMemoryACLListStorage) Head() (string, error) {
|
func (i *inMemoryACLListStorage) Head() (string, error) {
|
||||||
t.RLock()
|
i.RLock()
|
||||||
defer t.RUnlock()
|
defer i.RUnlock()
|
||||||
return t.head, nil
|
return i.records[len(i.records)-1].Id, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *inMemoryACLListStorage) SetHead(head string) error {
|
func (i *inMemoryACLListStorage) GetRawRecord(ctx context.Context, id string) (*aclrecordproto.RawACLRecordWithId, error) {
|
||||||
t.Lock()
|
i.RLock()
|
||||||
defer t.Unlock()
|
defer i.RUnlock()
|
||||||
t.head = head
|
for _, rec := range i.records {
|
||||||
return nil
|
if rec.Id == id {
|
||||||
|
return rec, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("no such record")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *inMemoryACLListStorage) AddRawRecord(ctx context.Context, record *aclrecordproto.RawACLRecordWithId) error {
|
func (i *inMemoryACLListStorage) AddRawRecord(ctx context.Context, rec *aclrecordproto.RawACLRecordWithId) error {
|
||||||
t.Lock()
|
panic("implement me")
|
||||||
defer t.Unlock()
|
|
||||||
// TODO: better to do deep copy
|
|
||||||
t.records[record.Id] = record
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *inMemoryACLListStorage) GetRawRecord(ctx context.Context, recordId string) (*aclrecordproto.RawACLRecordWithId, error) {
|
func (i *inMemoryACLListStorage) Id() string {
|
||||||
t.RLock()
|
i.RLock()
|
||||||
defer t.RUnlock()
|
defer i.RUnlock()
|
||||||
if res, exists := t.records[recordId]; exists {
|
return i.id
|
||||||
return res, nil
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf("could not get record with id: %s", recordId)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type inMemoryTreeStorage struct {
|
type inMemoryTreeStorage struct {
|
||||||
@ -89,6 +72,7 @@ type inMemoryTreeStorage struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewInMemoryTreeStorage(
|
func NewInMemoryTreeStorage(
|
||||||
|
treeId string,
|
||||||
root *treechangeproto.RawTreeChangeWithId,
|
root *treechangeproto.RawTreeChangeWithId,
|
||||||
heads []string,
|
heads []string,
|
||||||
changes []*treechangeproto.RawTreeChangeWithId) (TreeStorage, error) {
|
changes []*treechangeproto.RawTreeChangeWithId) (TreeStorage, error) {
|
||||||
@ -96,10 +80,10 @@ func NewInMemoryTreeStorage(
|
|||||||
for _, ch := range changes {
|
for _, ch := range changes {
|
||||||
allChanges[ch.Id] = ch
|
allChanges[ch.Id] = ch
|
||||||
}
|
}
|
||||||
allChanges[root.Id] = root
|
allChanges[treeId] = root
|
||||||
|
|
||||||
return &inMemoryTreeStorage{
|
return &inMemoryTreeStorage{
|
||||||
id: root.Id,
|
id: treeId,
|
||||||
root: root,
|
root: root,
|
||||||
heads: heads,
|
heads: heads,
|
||||||
changes: allChanges,
|
changes: allChanges,
|
||||||
@ -112,7 +96,7 @@ func (t *inMemoryTreeStorage) HasChange(ctx context.Context, id string) (bool, e
|
|||||||
return exists, nil
|
return exists, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *inMemoryTreeStorage) ID() string {
|
func (t *inMemoryTreeStorage) Id() string {
|
||||||
t.RLock()
|
t.RLock()
|
||||||
defer t.RUnlock()
|
defer t.RUnlock()
|
||||||
return t.id
|
return t.id
|
||||||
@ -175,12 +159,12 @@ func (i *inMemoryStorageProvider) TreeStorage(id string) (TreeStorage, error) {
|
|||||||
func (i *inMemoryStorageProvider) CreateTreeStorage(payload TreeStorageCreatePayload) (TreeStorage, error) {
|
func (i *inMemoryStorageProvider) CreateTreeStorage(payload TreeStorageCreatePayload) (TreeStorage, error) {
|
||||||
i.Lock()
|
i.Lock()
|
||||||
defer i.Unlock()
|
defer i.Unlock()
|
||||||
res, err := NewInMemoryTreeStorage(payload.RootRawChange, payload.Heads, payload.Changes)
|
res, err := NewInMemoryTreeStorage(payload.TreeId, payload.RootRawChange, payload.Heads, payload.Changes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
i.objects[payload.RootRawChange.Id] = res
|
i.objects[payload.TreeId] = res
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -12,7 +12,7 @@ var ErrACLExists = errors.New("acl already exists")
|
|||||||
var ErrUnknownRecord = errors.New("record doesn't exist")
|
var ErrUnknownRecord = errors.New("record doesn't exist")
|
||||||
|
|
||||||
type ListStorage interface {
|
type ListStorage interface {
|
||||||
ID() string
|
Id() string
|
||||||
Root() (*aclrecordproto.RawACLRecordWithId, error)
|
Root() (*aclrecordproto.RawACLRecordWithId, error)
|
||||||
Head() (string, error)
|
Head() (string, error)
|
||||||
SetHead(headId string) error
|
SetHead(headId string) error
|
||||||
|
|||||||
@ -80,18 +80,18 @@ func (mr *MockListStorageMockRecorder) Head() *gomock.Call {
|
|||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Head", reflect.TypeOf((*MockListStorage)(nil).Head))
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Head", reflect.TypeOf((*MockListStorage)(nil).Head))
|
||||||
}
|
}
|
||||||
|
|
||||||
// ID mocks base method.
|
// Id mocks base method.
|
||||||
func (m *MockListStorage) ID() string {
|
func (m *MockListStorage) Id() string {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "ID")
|
ret := m.ctrl.Call(m, "Id")
|
||||||
ret0, _ := ret[0].(string)
|
ret0, _ := ret[0].(string)
|
||||||
return ret0
|
return ret0
|
||||||
}
|
}
|
||||||
|
|
||||||
// ID indicates an expected call of ID.
|
// Id indicates an expected call of Id.
|
||||||
func (mr *MockListStorageMockRecorder) ID() *gomock.Call {
|
func (mr *MockListStorageMockRecorder) Id() *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ID", reflect.TypeOf((*MockListStorage)(nil).ID))
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Id", reflect.TypeOf((*MockListStorage)(nil).Id))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Root mocks base method.
|
// Root mocks base method.
|
||||||
@ -205,18 +205,18 @@ func (mr *MockTreeStorageMockRecorder) Heads() *gomock.Call {
|
|||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Heads", reflect.TypeOf((*MockTreeStorage)(nil).Heads))
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Heads", reflect.TypeOf((*MockTreeStorage)(nil).Heads))
|
||||||
}
|
}
|
||||||
|
|
||||||
// ID mocks base method.
|
// Id mocks base method.
|
||||||
func (m *MockTreeStorage) ID() string {
|
func (m *MockTreeStorage) Id() string {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "ID")
|
ret := m.ctrl.Call(m, "Id")
|
||||||
ret0, _ := ret[0].(string)
|
ret0, _ := ret[0].(string)
|
||||||
return ret0
|
return ret0
|
||||||
}
|
}
|
||||||
|
|
||||||
// ID indicates an expected call of ID.
|
// Id indicates an expected call of Id.
|
||||||
func (mr *MockTreeStorageMockRecorder) ID() *gomock.Call {
|
func (mr *MockTreeStorageMockRecorder) Id() *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ID", reflect.TypeOf((*MockTreeStorage)(nil).ID))
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Id", reflect.TypeOf((*MockTreeStorage)(nil).Id))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Root mocks base method.
|
// Root mocks base method.
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type TreeStorage interface {
|
type TreeStorage interface {
|
||||||
ID() string
|
Id() string
|
||||||
Root() (*treechangeproto.RawTreeChangeWithId, error)
|
Root() (*treechangeproto.RawTreeChangeWithId, error)
|
||||||
Heads() ([]string, error)
|
Heads() ([]string, error)
|
||||||
SetHeads(heads []string) error
|
SetHeads(heads []string) error
|
||||||
|
|||||||
@ -3,13 +3,13 @@ package acllistbuilder
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
aclrecordproto "github.com/anytypeio/go-anytype-infrastructure-experiments/common/pkg/acl/aclrecordproto"
|
aclrecordproto2 "github.com/anytypeio/go-anytype-infrastructure-experiments/common/pkg/acl/aclrecordproto"
|
||||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/pkg/acl/storage"
|
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/pkg/acl/storage"
|
||||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/pkg/acl/testutils/yamltests"
|
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/pkg/acl/testutils/yamltests"
|
||||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/util/cid"
|
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/util/cid"
|
||||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/util/keys/asymmetric/encryptionkey"
|
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/util/keys/asymmetric/encryptionkey"
|
||||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/util/keys/asymmetric/signingkey"
|
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/util/keys/asymmetric/signingkey"
|
||||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/util/keys/symmetric"
|
"hash/fnv"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"path"
|
"path"
|
||||||
"time"
|
"time"
|
||||||
@ -19,12 +19,20 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type ACLListStorageBuilder struct {
|
type ACLListStorageBuilder struct {
|
||||||
storage.ListStorage
|
aclList string
|
||||||
|
records []*aclrecordproto2.ACLRecord
|
||||||
|
rawRecords []*aclrecordproto2.RawACLRecordWithId
|
||||||
|
indexes map[string]int
|
||||||
keychain *YAMLKeychain
|
keychain *YAMLKeychain
|
||||||
|
rawRoot *aclrecordproto2.RawACLRecordWithId
|
||||||
|
root *aclrecordproto2.ACLRoot
|
||||||
|
id string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewACLListStorageBuilder(keychain *YAMLKeychain) *ACLListStorageBuilder {
|
func NewACLListStorageBuilder(keychain *YAMLKeychain) *ACLListStorageBuilder {
|
||||||
return &ACLListStorageBuilder{
|
return &ACLListStorageBuilder{
|
||||||
|
records: make([]*aclrecordproto2.ACLRecord, 0),
|
||||||
|
indexes: make(map[string]int),
|
||||||
keychain: keychain,
|
keychain: keychain,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -52,7 +60,7 @@ func NewACLListStorageBuilderFromFile(file string) (*ACLListStorageBuilder, erro
|
|||||||
return tb, nil
|
return tb, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *ACLListStorageBuilder) createRaw(rec proto.Marshaler, identity []byte) *aclrecordproto.RawACLRecordWithId {
|
func (t *ACLListStorageBuilder) createRaw(rec proto.Marshaler, identity []byte) *aclrecordproto2.RawACLRecordWithId {
|
||||||
protoMarshalled, err := rec.Marshal()
|
protoMarshalled, err := rec.Marshal()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic("should be able to marshal final acl message!")
|
panic("should be able to marshal final acl message!")
|
||||||
@ -63,7 +71,7 @@ func (t *ACLListStorageBuilder) createRaw(rec proto.Marshaler, identity []byte)
|
|||||||
panic("should be able to sign final acl message!")
|
panic("should be able to sign final acl message!")
|
||||||
}
|
}
|
||||||
|
|
||||||
rawRec := &aclrecordproto.RawACLRecord{
|
rawRec := &aclrecordproto2.RawACLRecord{
|
||||||
Payload: protoMarshalled,
|
Payload: protoMarshalled,
|
||||||
Signature: signature,
|
Signature: signature,
|
||||||
}
|
}
|
||||||
@ -75,62 +83,94 @@ func (t *ACLListStorageBuilder) createRaw(rec proto.Marshaler, identity []byte)
|
|||||||
|
|
||||||
id, _ := cid.NewCIDFromBytes(rawMarshalled)
|
id, _ := cid.NewCIDFromBytes(rawMarshalled)
|
||||||
|
|
||||||
return &aclrecordproto.RawACLRecordWithId{
|
return &aclrecordproto2.RawACLRecordWithId{
|
||||||
Payload: rawMarshalled,
|
Payload: rawMarshalled,
|
||||||
Id: id,
|
Id: id,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *ACLListStorageBuilder) Head() (string, error) {
|
||||||
|
l := len(t.records)
|
||||||
|
if l > 0 {
|
||||||
|
return t.rawRecords[l-1].Id, nil
|
||||||
|
}
|
||||||
|
return t.rawRoot.Id, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *ACLListStorageBuilder) SetHead(headId string) error {
|
||||||
|
panic("SetHead is not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *ACLListStorageBuilder) Root() (*aclrecordproto2.RawACLRecordWithId, error) {
|
||||||
|
return t.rawRoot, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *ACLListStorageBuilder) GetRawRecord(ctx context.Context, id string) (*aclrecordproto2.RawACLRecordWithId, error) {
|
||||||
|
recIdx, ok := t.indexes[id]
|
||||||
|
if !ok {
|
||||||
|
if id == t.rawRoot.Id {
|
||||||
|
return t.rawRoot, nil
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("no such record")
|
||||||
|
}
|
||||||
|
return t.rawRecords[recIdx], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *ACLListStorageBuilder) AddRawRecord(ctx context.Context, rec *aclrecordproto2.RawACLRecordWithId) error {
|
||||||
|
panic("implement me")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *ACLListStorageBuilder) Id() string {
|
||||||
|
return t.id
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *ACLListStorageBuilder) GetRawRecords() []*aclrecordproto2.RawACLRecordWithId {
|
||||||
|
return t.rawRecords
|
||||||
|
}
|
||||||
|
|
||||||
func (t *ACLListStorageBuilder) GetKeychain() *YAMLKeychain {
|
func (t *ACLListStorageBuilder) GetKeychain() *YAMLKeychain {
|
||||||
return t.keychain
|
return t.keychain
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *ACLListStorageBuilder) Parse(l *YMLList) {
|
func (t *ACLListStorageBuilder) Parse(tree *YMLList) {
|
||||||
// Just to clarify - we are generating new identities for the ones that
|
// Just to clarify - we are generating new identities for the ones that
|
||||||
// are specified in the yml file, because our identities should be Ed25519
|
// are specified in the yml file, because our identities should be Ed25519
|
||||||
// the same thing is happening for the encryption keys
|
// the same thing is happening for the encryption keys
|
||||||
t.keychain.ParseKeys(&l.Keys)
|
t.keychain.ParseKeys(&tree.Keys)
|
||||||
rawRoot := t.parseRoot(l.Root)
|
t.parseRoot(tree.Root)
|
||||||
var err error
|
prevId := t.id
|
||||||
t.ListStorage, err = storage.NewInMemoryACLListStorage(rawRoot.Id, []*aclrecordproto.RawACLRecordWithId{rawRoot})
|
for idx, rec := range tree.Records {
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
prevId := rawRoot.Id
|
|
||||||
for _, rec := range l.Records {
|
|
||||||
newRecord := t.parseRecord(rec, prevId)
|
newRecord := t.parseRecord(rec, prevId)
|
||||||
rawRecord := t.createRaw(newRecord, newRecord.Identity)
|
rawRecord := t.createRaw(newRecord, newRecord.Identity)
|
||||||
err = t.AddRawRecord(context.Background(), rawRecord)
|
t.records = append(t.records, newRecord)
|
||||||
if err != nil {
|
t.rawRecords = append(t.rawRecords, rawRecord)
|
||||||
panic(err)
|
t.indexes[rawRecord.Id] = idx
|
||||||
}
|
|
||||||
prevId = rawRecord.Id
|
prevId = rawRecord.Id
|
||||||
}
|
}
|
||||||
t.SetHead(prevId)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *ACLListStorageBuilder) parseRecord(rec *Record, prevId string) *aclrecordproto.ACLRecord {
|
func (t *ACLListStorageBuilder) parseRecord(rec *Record, prevId string) *aclrecordproto2.ACLRecord {
|
||||||
k := t.keychain.GetKey(rec.ReadKey).(*SymKey)
|
k := t.keychain.GetKey(rec.ReadKey).(*SymKey)
|
||||||
var aclChangeContents []*aclrecordproto.ACLContentValue
|
var aclChangeContents []*aclrecordproto2.ACLContentValue
|
||||||
for _, ch := range rec.AclChanges {
|
for _, ch := range rec.AclChanges {
|
||||||
aclChangeContent := t.parseACLChange(ch)
|
aclChangeContent := t.parseACLChange(ch)
|
||||||
aclChangeContents = append(aclChangeContents, aclChangeContent)
|
aclChangeContents = append(aclChangeContents, aclChangeContent)
|
||||||
}
|
}
|
||||||
data := &aclrecordproto.ACLData{
|
data := &aclrecordproto2.ACLData{
|
||||||
AclContent: aclChangeContents,
|
AclContent: aclChangeContents,
|
||||||
}
|
}
|
||||||
bytes, _ := data.Marshal()
|
bytes, _ := data.Marshal()
|
||||||
|
|
||||||
return &aclrecordproto.ACLRecord{
|
return &aclrecordproto2.ACLRecord{
|
||||||
PrevId: prevId,
|
PrevId: prevId,
|
||||||
Identity: []byte(t.keychain.GetIdentity(rec.Identity)),
|
Identity: []byte(t.keychain.GetIdentity(rec.Identity)),
|
||||||
Data: bytes,
|
Data: bytes,
|
||||||
CurrentReadKeyHash: k.Hash,
|
CurrentReadKeyHash: k.Hash,
|
||||||
Timestamp: time.Now().UnixNano(),
|
Timestamp: time.Now().Unix(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *ACLListStorageBuilder) parseACLChange(ch *ACLChange) (convCh *aclrecordproto.ACLContentValue) {
|
func (t *ACLListStorageBuilder) parseACLChange(ch *ACLChange) (convCh *aclrecordproto2.ACLContentValue) {
|
||||||
switch {
|
switch {
|
||||||
case ch.UserAdd != nil:
|
case ch.UserAdd != nil:
|
||||||
add := ch.UserAdd
|
add := ch.UserAdd
|
||||||
@ -138,12 +178,12 @@ func (t *ACLListStorageBuilder) parseACLChange(ch *ACLChange) (convCh *aclrecord
|
|||||||
encKey := t.keychain.GetKey(add.EncryptionKey).(encryptionkey.PrivKey)
|
encKey := t.keychain.GetKey(add.EncryptionKey).(encryptionkey.PrivKey)
|
||||||
rawKey, _ := encKey.GetPublic().Raw()
|
rawKey, _ := encKey.GetPublic().Raw()
|
||||||
|
|
||||||
convCh = &aclrecordproto.ACLContentValue{
|
convCh = &aclrecordproto2.ACLContentValue{
|
||||||
Value: &aclrecordproto.ACLContentValue_UserAdd{
|
Value: &aclrecordproto2.ACLContentValue_UserAdd{
|
||||||
UserAdd: &aclrecordproto.ACLUserAdd{
|
UserAdd: &aclrecordproto2.ACLUserAdd{
|
||||||
Identity: []byte(t.keychain.GetIdentity(add.Identity)),
|
Identity: []byte(t.keychain.GetIdentity(add.Identity)),
|
||||||
EncryptionKey: rawKey,
|
EncryptionKey: rawKey,
|
||||||
EncryptedReadKeys: t.encryptReadKeysWithPubKey(add.EncryptedReadKeys, encKey),
|
EncryptedReadKeys: t.encryptReadKeys(add.EncryptedReadKeys, encKey),
|
||||||
Permissions: t.convertPermission(add.Permission),
|
Permissions: t.convertPermission(add.Permission),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -151,50 +191,52 @@ func (t *ACLListStorageBuilder) parseACLChange(ch *ACLChange) (convCh *aclrecord
|
|||||||
case ch.UserJoin != nil:
|
case ch.UserJoin != nil:
|
||||||
join := ch.UserJoin
|
join := ch.UserJoin
|
||||||
|
|
||||||
encKey := t.keychain.GetKey(join.EncryptionKey).(encryptionkey.PrivKey)
|
encKey := t.keychain.
|
||||||
|
GetKey(join.EncryptionKey).(encryptionkey.PrivKey)
|
||||||
rawKey, _ := encKey.GetPublic().Raw()
|
rawKey, _ := encKey.GetPublic().Raw()
|
||||||
|
|
||||||
idKey, _ := t.keychain.SigningKeysByYAMLName[join.Identity].GetPublic().Raw()
|
idKey, _ := t.keychain.SigningKeysByYAMLIdentity[join.Identity].GetPublic().Raw()
|
||||||
signKey := t.keychain.GetKey(join.AcceptKey).(signingkey.PrivKey)
|
signKey := t.keychain.GetKey(join.AcceptSignature).(signingkey.PrivKey)
|
||||||
signature, err := signKey.Sign(idKey)
|
signature, err := signKey.Sign(idKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
acceptPubKey, _ := signKey.GetPublic().Raw()
|
|
||||||
|
|
||||||
convCh = &aclrecordproto.ACLContentValue{
|
convCh = &aclrecordproto2.ACLContentValue{
|
||||||
Value: &aclrecordproto.ACLContentValue_UserJoin{
|
Value: &aclrecordproto2.ACLContentValue_UserJoin{
|
||||||
UserJoin: &aclrecordproto.ACLUserJoin{
|
UserJoin: &aclrecordproto2.ACLUserJoin{
|
||||||
Identity: []byte(t.keychain.GetIdentity(join.Identity)),
|
Identity: []byte(t.keychain.GetIdentity(join.Identity)),
|
||||||
EncryptionKey: rawKey,
|
EncryptionKey: rawKey,
|
||||||
AcceptSignature: signature,
|
AcceptSignature: signature,
|
||||||
AcceptPubKey: acceptPubKey,
|
InviteId: join.InviteId,
|
||||||
EncryptedReadKeys: t.encryptReadKeysWithPubKey(join.EncryptedReadKeys, encKey),
|
EncryptedReadKeys: t.encryptReadKeys(join.EncryptedReadKeys, encKey),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
case ch.UserInvite != nil:
|
case ch.UserInvite != nil:
|
||||||
invite := ch.UserInvite
|
invite := ch.UserInvite
|
||||||
rawAcceptKey, _ := t.keychain.GetKey(invite.AcceptKey).(signingkey.PrivKey).GetPublic().Raw()
|
rawAcceptKey, _ := t.keychain.GetKey(invite.AcceptKey).(signingkey.PrivKey).GetPublic().Raw()
|
||||||
hash := t.keychain.GetKey(invite.EncryptionKey).(*SymKey).Hash
|
encKey := t.keychain.
|
||||||
encKey := t.keychain.ReadKeysByHash[hash]
|
GetKey(invite.EncryptionKey).(encryptionkey.PrivKey)
|
||||||
|
rawEncKey, _ := encKey.GetPublic().Raw()
|
||||||
|
|
||||||
convCh = &aclrecordproto.ACLContentValue{
|
convCh = &aclrecordproto2.ACLContentValue{
|
||||||
Value: &aclrecordproto.ACLContentValue_UserInvite{
|
Value: &aclrecordproto2.ACLContentValue_UserInvite{
|
||||||
UserInvite: &aclrecordproto.ACLUserInvite{
|
UserInvite: &aclrecordproto2.ACLUserInvite{
|
||||||
AcceptPublicKey: rawAcceptKey,
|
AcceptPublicKey: rawAcceptKey,
|
||||||
EncryptSymKeyHash: hash,
|
EncryptPublicKey: rawEncKey,
|
||||||
EncryptedReadKeys: t.encryptReadKeysWithSymKey(invite.EncryptedReadKeys, encKey.Key),
|
EncryptedReadKeys: t.encryptReadKeys(invite.EncryptedReadKeys, encKey),
|
||||||
Permissions: t.convertPermission(invite.Permissions),
|
Permissions: t.convertPermission(invite.Permissions),
|
||||||
|
InviteId: invite.InviteId,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
case ch.UserPermissionChange != nil:
|
case ch.UserPermissionChange != nil:
|
||||||
permissionChange := ch.UserPermissionChange
|
permissionChange := ch.UserPermissionChange
|
||||||
|
|
||||||
convCh = &aclrecordproto.ACLContentValue{
|
convCh = &aclrecordproto2.ACLContentValue{
|
||||||
Value: &aclrecordproto.ACLContentValue_UserPermissionChange{
|
Value: &aclrecordproto2.ACLContentValue_UserPermissionChange{
|
||||||
UserPermissionChange: &aclrecordproto.ACLUserPermissionChange{
|
UserPermissionChange: &aclrecordproto2.ACLUserPermissionChange{
|
||||||
Identity: []byte(t.keychain.GetIdentity(permissionChange.Identity)),
|
Identity: []byte(t.keychain.GetIdentity(permissionChange.Identity)),
|
||||||
Permissions: t.convertPermission(permissionChange.Permission),
|
Permissions: t.convertPermission(permissionChange.Permission),
|
||||||
},
|
},
|
||||||
@ -205,24 +247,24 @@ func (t *ACLListStorageBuilder) parseACLChange(ch *ACLChange) (convCh *aclrecord
|
|||||||
|
|
||||||
newReadKey := t.keychain.GetKey(remove.NewReadKey).(*SymKey)
|
newReadKey := t.keychain.GetKey(remove.NewReadKey).(*SymKey)
|
||||||
|
|
||||||
var replaces []*aclrecordproto.ACLReadKeyReplace
|
var replaces []*aclrecordproto2.ACLReadKeyReplace
|
||||||
for _, id := range remove.IdentitiesLeft {
|
for _, id := range remove.IdentitiesLeft {
|
||||||
encKey := t.keychain.EncryptionKeysByYAMLName[id]
|
encKey := t.keychain.EncryptionKeysByYAMLIdentity[id]
|
||||||
rawEncKey, _ := encKey.GetPublic().Raw()
|
rawEncKey, _ := encKey.GetPublic().Raw()
|
||||||
encReadKey, err := encKey.GetPublic().Encrypt(newReadKey.Key.Bytes())
|
encReadKey, err := encKey.GetPublic().Encrypt(newReadKey.Key.Bytes())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
replaces = append(replaces, &aclrecordproto.ACLReadKeyReplace{
|
replaces = append(replaces, &aclrecordproto2.ACLReadKeyReplace{
|
||||||
Identity: []byte(t.keychain.GetIdentity(id)),
|
Identity: []byte(t.keychain.GetIdentity(id)),
|
||||||
EncryptionKey: rawEncKey,
|
EncryptionKey: rawEncKey,
|
||||||
EncryptedReadKey: encReadKey,
|
EncryptedReadKey: encReadKey,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
convCh = &aclrecordproto.ACLContentValue{
|
convCh = &aclrecordproto2.ACLContentValue{
|
||||||
Value: &aclrecordproto.ACLContentValue_UserRemove{
|
Value: &aclrecordproto2.ACLContentValue_UserRemove{
|
||||||
UserRemove: &aclrecordproto.ACLUserRemove{
|
UserRemove: &aclrecordproto2.ACLUserRemove{
|
||||||
Identity: []byte(t.keychain.GetIdentity(remove.RemovedIdentity)),
|
Identity: []byte(t.keychain.GetIdentity(remove.RemovedIdentity)),
|
||||||
ReadKeyReplaces: replaces,
|
ReadKeyReplaces: replaces,
|
||||||
},
|
},
|
||||||
@ -236,7 +278,7 @@ func (t *ACLListStorageBuilder) parseACLChange(ch *ACLChange) (convCh *aclrecord
|
|||||||
return convCh
|
return convCh
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *ACLListStorageBuilder) encryptReadKeysWithPubKey(keys []string, encKey encryptionkey.PrivKey) (enc [][]byte) {
|
func (t *ACLListStorageBuilder) encryptReadKeys(keys []string, encKey encryptionkey.PrivKey) (enc [][]byte) {
|
||||||
for _, k := range keys {
|
for _, k := range keys {
|
||||||
realKey := t.keychain.GetKey(k).(*SymKey).Key.Bytes()
|
realKey := t.keychain.GetKey(k).(*SymKey).Key.Bytes()
|
||||||
res, err := encKey.GetPublic().Encrypt(realKey)
|
res, err := encKey.GetPublic().Encrypt(realKey)
|
||||||
@ -249,47 +291,43 @@ func (t *ACLListStorageBuilder) encryptReadKeysWithPubKey(keys []string, encKey
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *ACLListStorageBuilder) encryptReadKeysWithSymKey(keys []string, key *symmetric.Key) (enc [][]byte) {
|
func (t *ACLListStorageBuilder) convertPermission(perm string) aclrecordproto2.ACLUserPermissions {
|
||||||
for _, k := range keys {
|
|
||||||
realKey := t.keychain.GetKey(k).(*SymKey).Key.Bytes()
|
|
||||||
res, err := key.Encrypt(realKey)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
enc = append(enc, res)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *ACLListStorageBuilder) convertPermission(perm string) aclrecordproto.ACLUserPermissions {
|
|
||||||
switch perm {
|
switch perm {
|
||||||
case "admin":
|
case "admin":
|
||||||
return aclrecordproto.ACLUserPermissions_Admin
|
return aclrecordproto2.ACLUserPermissions_Admin
|
||||||
case "writer":
|
case "writer":
|
||||||
return aclrecordproto.ACLUserPermissions_Writer
|
return aclrecordproto2.ACLUserPermissions_Writer
|
||||||
case "reader":
|
case "reader":
|
||||||
return aclrecordproto.ACLUserPermissions_Reader
|
return aclrecordproto2.ACLUserPermissions_Reader
|
||||||
default:
|
default:
|
||||||
panic(fmt.Sprintf("incorrect permission: %s", perm))
|
panic(fmt.Sprintf("incorrect permission: %s", perm))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *ACLListStorageBuilder) traverseFromHead(f func(rec *aclrecordproto.ACLRecord, id string) error) (err error) {
|
func (t *ACLListStorageBuilder) traverseFromHead(f func(rec *aclrecordproto2.ACLRecord, id string) error) (err error) {
|
||||||
panic("this was removed, add if needed")
|
for i := len(t.records) - 1; i >= 0; i-- {
|
||||||
|
err = f(t.records[i], t.rawRecords[i].Id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *ACLListStorageBuilder) parseRoot(root *Root) (rawRoot *aclrecordproto.RawACLRecordWithId) {
|
func (t *ACLListStorageBuilder) parseRoot(root *Root) {
|
||||||
rawSignKey, _ := t.keychain.SigningKeysByYAMLName[root.Identity].GetPublic().Raw()
|
rawSignKey, _ := t.keychain.SigningKeysByYAMLIdentity[root.Identity].GetPublic().Raw()
|
||||||
rawEncKey, _ := t.keychain.EncryptionKeysByYAMLName[root.Identity].GetPublic().Raw()
|
rawEncKey, _ := t.keychain.EncryptionKeysByYAMLIdentity[root.Identity].GetPublic().Raw()
|
||||||
readKey := t.keychain.ReadKeysByYAMLName[root.Identity]
|
readKey, _ := aclrecordproto2.ACLReadKeyDerive(rawSignKey, rawEncKey)
|
||||||
aclRoot := &aclrecordproto.ACLRoot{
|
hasher := fnv.New64()
|
||||||
|
hasher.Write(readKey.Bytes())
|
||||||
|
t.root = &aclrecordproto2.ACLRoot{
|
||||||
Identity: rawSignKey,
|
Identity: rawSignKey,
|
||||||
EncryptionKey: rawEncKey,
|
EncryptionKey: rawEncKey,
|
||||||
SpaceId: root.SpaceId,
|
SpaceId: root.SpaceId,
|
||||||
EncryptedReadKey: nil,
|
EncryptedReadKey: nil,
|
||||||
DerivationScheme: "scheme",
|
DerivationScheme: "scheme",
|
||||||
CurrentReadKeyHash: readKey.Hash,
|
CurrentReadKeyHash: hasher.Sum64(),
|
||||||
}
|
}
|
||||||
return t.createRaw(aclRoot, rawSignKey)
|
t.rawRoot = t.createRaw(t.root, rawSignKey)
|
||||||
|
t.id = t.rawRoot.Id
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,7 +3,7 @@ package tree
|
|||||||
import (
|
import (
|
||||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/pkg/acl/common"
|
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/pkg/acl/common"
|
||||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/pkg/acl/list"
|
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/pkg/acl/list"
|
||||||
storage "github.com/anytypeio/go-anytype-infrastructure-experiments/common/pkg/acl/storage"
|
storage2 "github.com/anytypeio/go-anytype-infrastructure-experiments/common/pkg/acl/storage"
|
||||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/pkg/acl/treechangeproto"
|
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/pkg/acl/treechangeproto"
|
||||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/util/keys/asymmetric/signingkey"
|
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/util/keys/asymmetric/signingkey"
|
||||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/util/keys/symmetric"
|
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/util/keys/symmetric"
|
||||||
@ -20,7 +20,7 @@ type ObjectTreeCreatePayload struct {
|
|||||||
Identity []byte
|
Identity []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
func BuildObjectTree(treeStorage storage.TreeStorage, aclList list.ACLList) (ObjectTree, error) {
|
func BuildObjectTree(treeStorage storage2.TreeStorage, aclList list.ACLList) (ObjectTree, error) {
|
||||||
rootChange, err := treeStorage.Root()
|
rootChange, err := treeStorage.Root()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -32,14 +32,14 @@ func BuildObjectTree(treeStorage storage.TreeStorage, aclList list.ACLList) (Obj
|
|||||||
func CreateDerivedObjectTree(
|
func CreateDerivedObjectTree(
|
||||||
payload ObjectTreeCreatePayload,
|
payload ObjectTreeCreatePayload,
|
||||||
aclList list.ACLList,
|
aclList list.ACLList,
|
||||||
createStorage storage.TreeStorageCreatorFunc) (objTree ObjectTree, err error) {
|
createStorage storage2.TreeStorageCreatorFunc) (objTree ObjectTree, err error) {
|
||||||
return createObjectTree(payload, 0, nil, aclList, createStorage)
|
return createObjectTree(payload, 0, nil, aclList, createStorage)
|
||||||
}
|
}
|
||||||
|
|
||||||
func CreateObjectTree(
|
func CreateObjectTree(
|
||||||
payload ObjectTreeCreatePayload,
|
payload ObjectTreeCreatePayload,
|
||||||
aclList list.ACLList,
|
aclList list.ACLList,
|
||||||
createStorage storage.TreeStorageCreatorFunc) (objTree ObjectTree, err error) {
|
createStorage storage2.TreeStorageCreatorFunc) (objTree ObjectTree, err error) {
|
||||||
bytes := make([]byte, 32)
|
bytes := make([]byte, 32)
|
||||||
_, err = rand.Read(bytes)
|
_, err = rand.Read(bytes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -53,7 +53,7 @@ func createObjectTree(
|
|||||||
timestamp int64,
|
timestamp int64,
|
||||||
seed []byte,
|
seed []byte,
|
||||||
aclList list.ACLList,
|
aclList list.ACLList,
|
||||||
createStorage storage.TreeStorageCreatorFunc) (objTree ObjectTree, err error) {
|
createStorage storage2.TreeStorageCreatorFunc) (objTree ObjectTree, err error) {
|
||||||
aclList.RLock()
|
aclList.RLock()
|
||||||
aclHeadId := aclList.Head().Id
|
aclHeadId := aclList.Head().Id
|
||||||
aclList.RUnlock()
|
aclList.RUnlock()
|
||||||
@ -77,7 +77,8 @@ func createObjectTree(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// create storage
|
// create storage
|
||||||
st, err := createStorage(storage.TreeStorageCreatePayload{
|
st, err := createStorage(storage2.TreeStorageCreatePayload{
|
||||||
|
TreeId: raw.Id,
|
||||||
RootRawChange: raw,
|
RootRawChange: raw,
|
||||||
Changes: []*treechangeproto.RawTreeChangeWithId{raw},
|
Changes: []*treechangeproto.RawTreeChangeWithId{raw},
|
||||||
Heads: []string{raw.Id},
|
Heads: []string{raw.Id},
|
||||||
@ -126,7 +127,8 @@ func buildObjectTree(deps objectTreeDeps) (ObjectTree, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
objTree.id = objTree.treeStorage.ID()
|
objTree.id = objTree.treeStorage.Id()
|
||||||
|
|
||||||
objTree.root, err = objTree.treeStorage.Root()
|
objTree.root, err = objTree.treeStorage.Root()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
111
node/acl/service.go
Normal file
111
node/acl/service.go
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
package acl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/account"
|
||||||
|
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/app"
|
||||||
|
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/app/logger"
|
||||||
|
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/syncservice/synchandler"
|
||||||
|
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/pkg/acl/aclrecordproto"
|
||||||
|
"github.com/anytypeio/go-anytype-infrastructure-experiments/consensus/consensusclient"
|
||||||
|
"github.com/anytypeio/go-anytype-infrastructure-experiments/consensus/consensusproto"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const CName = "node.acl"
|
||||||
|
|
||||||
|
var log = logger.NewNamed(CName)
|
||||||
|
|
||||||
|
type Service interface {
|
||||||
|
app.Component
|
||||||
|
}
|
||||||
|
|
||||||
|
type service struct {
|
||||||
|
consService consensusclient.Service
|
||||||
|
account account.Service
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *service) Init(a *app.App) (err error) {
|
||||||
|
s.consService = a.MustComponent(consensusclient.CName).(consensusclient.Service)
|
||||||
|
s.account = a.MustComponent(account.CName).(account.Service)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *service) Name() (name string) {
|
||||||
|
return CName
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *service) CreateLog(ctx context.Context, aclId string, rec *aclrecordproto.RawACLRecordWithId) (err error) {
|
||||||
|
logId, err := cidToByte(aclId)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
recId, err := cidToByte(rec.Id)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
acc := s.account.Account()
|
||||||
|
rec.AcceptorIdentity = acc.Identity
|
||||||
|
if rec.AcceptorSignature, err = acc.SignKey.Sign(rec.Payload); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
recPayload, err := rec.Marshal()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return s.consService.AddLog(ctx, &consensusproto.Log{
|
||||||
|
Id: logId,
|
||||||
|
Records: []*consensusproto.Record{
|
||||||
|
{
|
||||||
|
Id: recId,
|
||||||
|
Payload: recPayload,
|
||||||
|
CreatedUnix: uint64(time.Now().Unix()),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *service) AddRecord(ctx context.Context, aclId string, rec *aclrecordproto.RawACLRecordWithId) (err error) {
|
||||||
|
logId, err := cidToByte(aclId)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
recId, err := cidToByte(rec.Id)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
acc := s.account.Account()
|
||||||
|
rec.AcceptorIdentity = acc.Identity
|
||||||
|
if rec.AcceptorSignature, err = acc.SignKey.Sign(rec.Payload); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
recPayload, err := rec.Marshal()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return s.consService.AddRecord(ctx, logId, &consensusproto.Record{
|
||||||
|
Id: recId,
|
||||||
|
PrevId: nil, //TODO:
|
||||||
|
Payload: recPayload,
|
||||||
|
CreatedUnix: uint64(time.Now().Unix()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *service) Watch(ctx context.Context, spaceId, aclId string, h synchandler.SyncHandler) (err error) {
|
||||||
|
w, err := newWatcher(spaceId, aclId, h)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err = s.consService.Watch(w.logId, w); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return w.Ready(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *service) UnWatch(aclId string) (err error) {
|
||||||
|
logId, err := cidToByte(aclId)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return s.consService.UnWatch(logId)
|
||||||
|
}
|
||||||
19
node/acl/util.go
Normal file
19
node/acl/util.go
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
package acl
|
||||||
|
|
||||||
|
import "github.com/ipfs/go-cid"
|
||||||
|
|
||||||
|
func cidToString(b []byte) (s string, err error) {
|
||||||
|
rcid, err := cid.Cast(b)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return rcid.String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func cidToByte(s string) (b []byte, err error) {
|
||||||
|
rcid, err := cid.Decode(s)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return rcid.Bytes(), nil
|
||||||
|
}
|
||||||
16
node/acl/util_test.go
Normal file
16
node/acl/util_test.go
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
package acl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/util/cid"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCIDLen(t *testing.T) {
|
||||||
|
s, _ := cid.NewCIDFromBytes([]byte("some data"))
|
||||||
|
t.Log(s, len(s))
|
||||||
|
b, _ := cidToByte(s)
|
||||||
|
t.Log(b, len(b))
|
||||||
|
s2, _ := cidToString(b)
|
||||||
|
assert.Equal(t, s, s2)
|
||||||
|
}
|
||||||
93
node/acl/watcher.go
Normal file
93
node/acl/watcher.go
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
package acl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/spacesyncproto"
|
||||||
|
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/syncservice/synchandler"
|
||||||
|
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/pkg/acl/aclrecordproto"
|
||||||
|
"github.com/anytypeio/go-anytype-infrastructure-experiments/consensus/consensusproto"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newWatcher(spaceId, aclId string, h synchandler.SyncHandler) (w *watcher, err error) {
|
||||||
|
w = &watcher{
|
||||||
|
aclId: aclId,
|
||||||
|
spaceId: spaceId,
|
||||||
|
handler: h,
|
||||||
|
ready: make(chan struct{}),
|
||||||
|
}
|
||||||
|
if w.logId, err = cidToByte(aclId); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type watcher struct {
|
||||||
|
spaceId string
|
||||||
|
aclId string
|
||||||
|
logId []byte
|
||||||
|
handler synchandler.SyncHandler
|
||||||
|
ready chan struct{}
|
||||||
|
isReady sync.Once
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *watcher) AddConsensusRecords(recs []*consensusproto.Record) {
|
||||||
|
w.isReady.Do(func() {
|
||||||
|
close(w.ready)
|
||||||
|
})
|
||||||
|
records := make([]*aclrecordproto.RawACLRecordWithId, 0, len(recs))
|
||||||
|
|
||||||
|
for _, rec := range recs {
|
||||||
|
recId, err := cidToString(rec.Id)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("received invalid id from consensus node", zap.Error(err))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
records = append(records, &aclrecordproto.RawACLRecordWithId{
|
||||||
|
Payload: rec.Payload,
|
||||||
|
Id: recId,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
aclReq := &aclrecordproto.ACLSyncMessage{
|
||||||
|
Content: &aclrecordproto.ACLSyncContentValue{
|
||||||
|
Value: &aclrecordproto.ACLSyncContentValue_AddRecords{
|
||||||
|
AddRecords: &aclrecordproto.ACLAddRecords{
|
||||||
|
Records: records,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
payload, err := aclReq.Marshal()
|
||||||
|
if err != nil {
|
||||||
|
log.Error("acl payload marshal error", zap.Error(err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
req := &spacesyncproto.ObjectSyncMessage{
|
||||||
|
SpaceId: w.spaceId,
|
||||||
|
Payload: payload,
|
||||||
|
ObjectId: w.aclId,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = w.handler.HandleMessage(context.TODO(), "", req); err != nil {
|
||||||
|
log.Warn("handle message error", zap.Error(err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *watcher) AddConsensusError(err error) {
|
||||||
|
w.isReady.Do(func() {
|
||||||
|
w.err = err
|
||||||
|
close(w.ready)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *watcher) Ready(ctx context.Context) (err error) {
|
||||||
|
select {
|
||||||
|
case <-w.ready:
|
||||||
|
return w.err
|
||||||
|
case <-ctx.Done():
|
||||||
|
return ctx.Err()
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -82,7 +82,7 @@ func createListStorage(db *pogreb.DB, root *aclrecordproto.RawACLRecordWithId) (
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *listStorage) ID() string {
|
func (l *listStorage) Id() string {
|
||||||
return l.id
|
return l.id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import (
|
|||||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/app/logger"
|
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/app/logger"
|
||||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/spacesyncproto"
|
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/spacesyncproto"
|
||||||
spacestorage "github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/storage"
|
spacestorage "github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/storage"
|
||||||
storage2 "github.com/anytypeio/go-anytype-infrastructure-experiments/common/pkg/acl/storage"
|
storage "github.com/anytypeio/go-anytype-infrastructure-experiments/common/pkg/acl/storage"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
"path"
|
"path"
|
||||||
"sync"
|
"sync"
|
||||||
@ -22,7 +22,7 @@ type spaceStorage struct {
|
|||||||
spaceId string
|
spaceId string
|
||||||
objDb *pogreb.DB
|
objDb *pogreb.DB
|
||||||
keys spaceKeys
|
keys spaceKeys
|
||||||
aclStorage storage2.ListStorage
|
aclStorage storage.ListStorage
|
||||||
header *spacesyncproto.RawSpaceHeaderWithId
|
header *spacesyncproto.RawSpaceHeaderWithId
|
||||||
mx sync.Mutex
|
mx sync.Mutex
|
||||||
}
|
}
|
||||||
@ -88,8 +88,8 @@ func createSpaceStorage(rootPath string, payload spacestorage.SpaceStorageCreate
|
|||||||
}
|
}
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
if err != nil {
|
|
||||||
log.With(zap.String("id", payload.SpaceHeaderWithId.Id), zap.Error(err)).Warn("failed to create storage")
|
log.With(zap.String("id", payload.SpaceHeaderWithId.Id), zap.Error(err)).Warn("failed to create storage")
|
||||||
|
if err != nil {
|
||||||
db.Close()
|
db.Close()
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
@ -129,15 +129,15 @@ func createSpaceStorage(rootPath string, payload spacestorage.SpaceStorageCreate
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *spaceStorage) ID() string {
|
func (s *spaceStorage) Id() string {
|
||||||
return s.spaceId
|
return s.spaceId
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *spaceStorage) TreeStorage(id string) (storage2.TreeStorage, error) {
|
func (s *spaceStorage) TreeStorage(id string) (storage.TreeStorage, error) {
|
||||||
return newTreeStorage(s.objDb, id)
|
return newTreeStorage(s.objDb, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *spaceStorage) CreateTreeStorage(payload storage2.TreeStorageCreatePayload) (ts storage2.TreeStorage, err error) {
|
func (s *spaceStorage) CreateTreeStorage(payload storage.TreeStorageCreatePayload) (ts storage.TreeStorage, err error) {
|
||||||
// we have mutex here, so we prevent overwriting the heads of a tree on concurrent creation
|
// we have mutex here, so we prevent overwriting the heads of a tree on concurrent creation
|
||||||
s.mx.Lock()
|
s.mx.Lock()
|
||||||
defer s.mx.Unlock()
|
defer s.mx.Unlock()
|
||||||
@ -145,7 +145,7 @@ func (s *spaceStorage) CreateTreeStorage(payload storage2.TreeStorageCreatePaylo
|
|||||||
return createTreeStorage(s.objDb, payload)
|
return createTreeStorage(s.objDb, payload)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *spaceStorage) ACLStorage() (storage2.ListStorage, error) {
|
func (s *spaceStorage) ACLStorage() (storage.ListStorage, error) {
|
||||||
return s.aclStorage, nil
|
return s.aclStorage, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -156,13 +156,13 @@ func (s *spaceStorage) SpaceHeader() (header *spacesyncproto.RawSpaceHeaderWithI
|
|||||||
func (s *spaceStorage) StoredIds() (ids []string, err error) {
|
func (s *spaceStorage) StoredIds() (ids []string, err error) {
|
||||||
index := s.objDb.Items()
|
index := s.objDb.Items()
|
||||||
|
|
||||||
key, _, err := index.Next()
|
key, val, err := index.Next()
|
||||||
for err == nil {
|
for err == nil {
|
||||||
strKey := string(key)
|
strKey := string(key)
|
||||||
if isRootIdKey(strKey) {
|
if isRootIdKey(strKey) {
|
||||||
ids = append(ids, getRootId(strKey))
|
ids = append(ids, string(val))
|
||||||
}
|
}
|
||||||
key, _, err = index.Next()
|
key, val, err = index.Next()
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != pogreb.ErrIterationDone {
|
if err != pogreb.ErrIterationDone {
|
||||||
|
|||||||
@ -3,7 +3,7 @@ package storage
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"github.com/akrylysov/pogreb"
|
"github.com/akrylysov/pogreb"
|
||||||
storage "github.com/anytypeio/go-anytype-infrastructure-experiments/common/pkg/acl/storage"
|
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/pkg/acl/storage"
|
||||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/pkg/acl/treechangeproto"
|
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/pkg/acl/treechangeproto"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -48,7 +48,7 @@ func newTreeStorage(db *pogreb.DB, treeId string) (ts storage.TreeStorage, err e
|
|||||||
}
|
}
|
||||||
|
|
||||||
func createTreeStorage(db *pogreb.DB, payload storage.TreeStorageCreatePayload) (ts storage.TreeStorage, err error) {
|
func createTreeStorage(db *pogreb.DB, payload storage.TreeStorageCreatePayload) (ts storage.TreeStorage, err error) {
|
||||||
keys := newTreeKeys(payload.RootRawChange.Id)
|
keys := newTreeKeys(payload.TreeId)
|
||||||
has, err := db.Has(keys.HeadsKey())
|
has, err := db.Has(keys.HeadsKey())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
@ -86,7 +86,7 @@ func createTreeStorage(db *pogreb.DB, payload storage.TreeStorageCreatePayload)
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *treeStorage) ID() string {
|
func (t *treeStorage) Id() string {
|
||||||
return t.id
|
return t.id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user