mirror of
https://github.com/anyproto/any-sync.git
synced 2025-06-07 21:47:02 +09:00
Merge pull request #449 from anyproto/GO-4400-add-new-invites
GO-4400: Add new invite type
This commit is contained in:
commit
c37a139fba
36 changed files with 2437 additions and 438 deletions
|
@ -10,6 +10,7 @@ import (
|
|||
"go.uber.org/zap"
|
||||
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/list"
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/recordverifier"
|
||||
"github.com/anyproto/any-sync/consensus/consensusproto"
|
||||
)
|
||||
|
||||
|
@ -58,7 +59,7 @@ func (a *aclObject) AddConsensusRecords(recs []*consensusproto.RawRecordWithId)
|
|||
if a.store, a.consErr = list.NewInMemoryStorage(a.id, recs); a.consErr != nil {
|
||||
return
|
||||
}
|
||||
if a.AclList, a.consErr = list.BuildAclListWithIdentity(a.aclService.accountService.Account(), a.store, list.NoOpAcceptorVerifier{}); a.consErr != nil {
|
||||
if a.AclList, a.consErr = list.BuildAclListWithIdentity(a.aclService.accountService.Account(), a.store, recordverifier.New()); a.consErr != nil {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
"github.com/anyproto/any-sync/app"
|
||||
"github.com/anyproto/any-sync/commonspace/object/accountdata"
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/list"
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/recordverifier"
|
||||
"github.com/anyproto/any-sync/consensus/consensusproto"
|
||||
"github.com/anyproto/any-sync/node/nodeclient"
|
||||
)
|
||||
|
@ -20,6 +21,7 @@ type AclJoiningClient interface {
|
|||
AclGetRecords(ctx context.Context, spaceId, aclHead string) ([]*consensusproto.RawRecordWithId, error)
|
||||
RequestJoin(ctx context.Context, spaceId string, payload list.RequestJoinPayload) (aclHeadId string, err error)
|
||||
CancelJoin(ctx context.Context, spaceId string) (err error)
|
||||
InviteJoin(ctx context.Context, spaceId string, payload list.InviteJoinPayload) (aclHeadId string, err error)
|
||||
CancelRemoveSelf(ctx context.Context, spaceId string) (err error)
|
||||
}
|
||||
|
||||
|
@ -59,7 +61,7 @@ func (c *aclJoiningClient) getAcl(ctx context.Context, spaceId string) (l list.A
|
|||
if err != nil {
|
||||
return
|
||||
}
|
||||
return list.BuildAclListWithIdentity(c.keys, storage, list.NoOpAcceptorVerifier{})
|
||||
return list.BuildAclListWithIdentity(c.keys, storage, recordverifier.New())
|
||||
}
|
||||
|
||||
func (c *aclJoiningClient) CancelJoin(ctx context.Context, spaceId string) (err error) {
|
||||
|
@ -106,6 +108,23 @@ func (c *aclJoiningClient) RequestJoin(ctx context.Context, spaceId string, payl
|
|||
return
|
||||
}
|
||||
|
||||
func (c *aclJoiningClient) InviteJoin(ctx context.Context, spaceId string, payload list.InviteJoinPayload) (aclHeadId string, err error) {
|
||||
acl, err := c.getAcl(ctx, spaceId)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
rec, err := acl.RecordBuilder().BuildInviteJoin(payload)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
recWithId, err := c.nodeClient.AclAddRecord(ctx, spaceId, rec)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
aclHeadId = recWithId.Id
|
||||
return
|
||||
}
|
||||
|
||||
func (c *aclJoiningClient) CancelRemoveSelf(ctx context.Context, spaceId string) (err error) {
|
||||
acl, err := c.getAcl(ctx, spaceId)
|
||||
if err != nil {
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"errors"
|
||||
|
||||
"github.com/anyproto/any-sync/app"
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/aclrecordproto"
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/list"
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/syncacl"
|
||||
"github.com/anyproto/any-sync/commonspace/spacestate"
|
||||
|
@ -18,6 +19,10 @@ type InviteResponse struct {
|
|||
InviteKey crypto.PrivKey
|
||||
}
|
||||
|
||||
type InviteChange struct {
|
||||
Perms list.AclPermissions
|
||||
}
|
||||
|
||||
type GetRecordsResponse struct {
|
||||
Records []*consensusproto.RawRecordWithId
|
||||
}
|
||||
|
@ -26,7 +31,8 @@ type InviteSaveFunc func()
|
|||
|
||||
type AclSpaceClient interface {
|
||||
app.Component
|
||||
GenerateInvite() (list.InviteResult, error)
|
||||
GenerateInvite(shouldRevokeAll, isRequestToJoin bool, permissions list.AclPermissions) (list.InviteResult, error)
|
||||
ChangeInvite(ctx context.Context, inviteId string, permissions list.AclPermissions) error
|
||||
StopSharing(ctx context.Context, readKeyChange list.ReadKeyChangePayload) (err error)
|
||||
AddRecord(ctx context.Context, consRec *consensusproto.RawRecord) error
|
||||
RemoveAccounts(ctx context.Context, payload list.AccountRemovePayload) error
|
||||
|
@ -127,7 +133,7 @@ func (c *aclSpaceClient) RevokeAllInvites(ctx context.Context) (err error) {
|
|||
return
|
||||
}
|
||||
c.acl.Unlock()
|
||||
return c.sendRecordAndUpdate(ctx, c.spaceId, res)
|
||||
return c.sendRecordAndUpdate(ctx, c.spaceId, res.Rec)
|
||||
}
|
||||
|
||||
func (c *aclSpaceClient) StopSharing(ctx context.Context, readKeyChange list.ReadKeyChangePayload) (err error) {
|
||||
|
@ -164,7 +170,7 @@ func (c *aclSpaceClient) StopSharing(ctx context.Context, readKeyChange list.Rea
|
|||
return
|
||||
}
|
||||
c.acl.Unlock()
|
||||
return c.sendRecordAndUpdate(ctx, c.spaceId, res)
|
||||
return c.sendRecordAndUpdate(ctx, c.spaceId, res.Rec)
|
||||
}
|
||||
|
||||
func (c *aclSpaceClient) DeclineRequest(ctx context.Context, identity crypto.PubKey) (err error) {
|
||||
|
@ -210,10 +216,54 @@ func (c *aclSpaceClient) AcceptRequest(ctx context.Context, payload list.Request
|
|||
return c.sendRecordAndUpdate(ctx, c.spaceId, res)
|
||||
}
|
||||
|
||||
func (c *aclSpaceClient) GenerateInvite() (resp list.InviteResult, err error) {
|
||||
c.acl.RLock()
|
||||
defer c.acl.RUnlock()
|
||||
return c.acl.RecordBuilder().BuildInvite()
|
||||
func (c *aclSpaceClient) ChangeInvite(ctx context.Context, inviteId string, permissions list.AclPermissions) error {
|
||||
c.acl.Lock()
|
||||
res, err := c.acl.RecordBuilder().BuildInviteChange(list.InviteChangePayload{
|
||||
IniviteRecordId: inviteId,
|
||||
Permissions: permissions,
|
||||
})
|
||||
if err != nil {
|
||||
c.acl.Unlock()
|
||||
return err
|
||||
}
|
||||
c.acl.Unlock()
|
||||
return c.sendRecordAndUpdate(ctx, c.spaceId, res)
|
||||
}
|
||||
|
||||
func (c *aclSpaceClient) GenerateInvite(isRevoke, isRequestToJoin bool, permissions list.AclPermissions) (list.InviteResult, error) {
|
||||
c.acl.Lock()
|
||||
defer c.acl.Unlock()
|
||||
var inviteIds []string
|
||||
if isRevoke {
|
||||
for _, invite := range c.acl.AclState().Invites() {
|
||||
if isRequestToJoin && invite.Type == aclrecordproto.AclInviteType_RequestToJoin {
|
||||
return list.InviteResult{}, list.ErrDuplicateInvites
|
||||
} else if invite.Permissions == permissions {
|
||||
return list.InviteResult{}, list.ErrDuplicateInvites
|
||||
}
|
||||
}
|
||||
inviteIds = c.acl.AclState().InviteIds()
|
||||
}
|
||||
var payload list.BatchRequestPayload
|
||||
if isRequestToJoin {
|
||||
payload = list.BatchRequestPayload{
|
||||
InviteRevokes: inviteIds,
|
||||
NewInvites: []list.AclPermissions{list.AclPermissionsNone},
|
||||
}
|
||||
} else {
|
||||
payload = list.BatchRequestPayload{
|
||||
InviteRevokes: inviteIds,
|
||||
NewInvites: []list.AclPermissions{permissions},
|
||||
}
|
||||
}
|
||||
res, err := c.acl.RecordBuilder().BuildBatchRequest(payload)
|
||||
if err != nil {
|
||||
return list.InviteResult{}, err
|
||||
}
|
||||
return list.InviteResult{
|
||||
InviteRec: res.Rec,
|
||||
InviteKey: res.Invites[0],
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *aclSpaceClient) AddRecord(ctx context.Context, consRec *consensusproto.RawRecord) (err error) {
|
||||
|
|
|
@ -99,6 +99,21 @@ func (mr *MockAclJoiningClientMockRecorder) Init(arg0 any) *gomock.Call {
|
|||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Init", reflect.TypeOf((*MockAclJoiningClient)(nil).Init), arg0)
|
||||
}
|
||||
|
||||
// InviteJoin mocks base method.
|
||||
func (m *MockAclJoiningClient) InviteJoin(arg0 context.Context, arg1 string, arg2 list.InviteJoinPayload) (string, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "InviteJoin", arg0, arg1, arg2)
|
||||
ret0, _ := ret[0].(string)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// InviteJoin indicates an expected call of InviteJoin.
|
||||
func (mr *MockAclJoiningClientMockRecorder) InviteJoin(arg0, arg1, arg2 any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InviteJoin", reflect.TypeOf((*MockAclJoiningClient)(nil).InviteJoin), arg0, arg1, arg2)
|
||||
}
|
||||
|
||||
// Name mocks base method.
|
||||
func (m *MockAclJoiningClient) Name() string {
|
||||
m.ctrl.T.Helper()
|
||||
|
@ -207,6 +222,20 @@ func (mr *MockAclSpaceClientMockRecorder) CancelRequest(arg0 any) *gomock.Call {
|
|||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CancelRequest", reflect.TypeOf((*MockAclSpaceClient)(nil).CancelRequest), arg0)
|
||||
}
|
||||
|
||||
// ChangeInvite mocks base method.
|
||||
func (m *MockAclSpaceClient) ChangeInvite(arg0 context.Context, arg1 string, arg2 list.AclPermissions) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "ChangeInvite", arg0, arg1, arg2)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// ChangeInvite indicates an expected call of ChangeInvite.
|
||||
func (mr *MockAclSpaceClientMockRecorder) ChangeInvite(arg0, arg1, arg2 any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ChangeInvite", reflect.TypeOf((*MockAclSpaceClient)(nil).ChangeInvite), arg0, arg1, arg2)
|
||||
}
|
||||
|
||||
// ChangePermissions mocks base method.
|
||||
func (m *MockAclSpaceClient) ChangePermissions(arg0 context.Context, arg1 list.PermissionChangesPayload) error {
|
||||
m.ctrl.T.Helper()
|
||||
|
@ -236,18 +265,18 @@ func (mr *MockAclSpaceClientMockRecorder) DeclineRequest(arg0, arg1 any) *gomock
|
|||
}
|
||||
|
||||
// GenerateInvite mocks base method.
|
||||
func (m *MockAclSpaceClient) GenerateInvite() (list.InviteResult, error) {
|
||||
func (m *MockAclSpaceClient) GenerateInvite(arg0, arg1 bool, arg2 list.AclPermissions) (list.InviteResult, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GenerateInvite")
|
||||
ret := m.ctrl.Call(m, "GenerateInvite", arg0, arg1, arg2)
|
||||
ret0, _ := ret[0].(list.InviteResult)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GenerateInvite indicates an expected call of GenerateInvite.
|
||||
func (mr *MockAclSpaceClientMockRecorder) GenerateInvite() *gomock.Call {
|
||||
func (mr *MockAclSpaceClientMockRecorder) GenerateInvite(arg0, arg1, arg2 any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GenerateInvite", reflect.TypeOf((*MockAclSpaceClient)(nil).GenerateInvite))
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GenerateInvite", reflect.TypeOf((*MockAclSpaceClient)(nil).GenerateInvite), arg0, arg1, arg2)
|
||||
}
|
||||
|
||||
// Init mocks base method.
|
||||
|
|
|
@ -14,6 +14,7 @@ import (
|
|||
"github.com/anyproto/any-sync/commonspace/acl/aclclient"
|
||||
"github.com/anyproto/any-sync/commonspace/object/accountdata"
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/list"
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/recordverifier"
|
||||
"github.com/anyproto/any-sync/util/periodicsync"
|
||||
)
|
||||
|
||||
|
@ -82,7 +83,7 @@ func (a *aclWaiter) loop(ctx context.Context) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
acl, err := list.BuildAclListWithIdentity(a.keys, storage, list.NoOpAcceptorVerifier{})
|
||||
acl, err := list.BuildAclListWithIdentity(a.keys, storage, recordverifier.New())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/anyproto/any-sync/commonspace/object/accountdata"
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/recordverifier"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
||||
"github.com/anyproto/any-sync/commonspace/spacepayloads"
|
||||
"github.com/anyproto/any-sync/commonspace/spacestorage"
|
||||
|
@ -17,6 +18,14 @@ import (
|
|||
"github.com/anyproto/any-sync/util/crypto"
|
||||
)
|
||||
|
||||
func mockDeps() Deps {
|
||||
return Deps{
|
||||
TreeSyncer: mockTreeSyncer{},
|
||||
SyncStatus: syncstatus.NewNoOpSyncStatus(),
|
||||
recordVerifier: recordverifier.NewValidateFull(),
|
||||
}
|
||||
}
|
||||
|
||||
func createTree(t *testing.T, ctx context.Context, spc Space, acc *accountdata.AccountKeys) string {
|
||||
bytes := make([]byte, 32)
|
||||
rand.Read(bytes)
|
||||
|
@ -60,7 +69,7 @@ func TestSpaceDeleteIdsMarkDeleted(t *testing.T) {
|
|||
require.NotNil(t, sp)
|
||||
|
||||
// initializing space
|
||||
spc, err := fx.spaceService.NewSpace(ctx, sp, Deps{TreeSyncer: mockTreeSyncer{}, SyncStatus: syncstatus.NewNoOpSyncStatus()})
|
||||
spc, err := fx.spaceService.NewSpace(ctx, sp, mockDeps())
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, spc)
|
||||
// adding space to tree manager
|
||||
|
@ -109,7 +118,7 @@ func TestSpaceDeleteIdsMarkDeleted(t *testing.T) {
|
|||
time.Sleep(100 * time.Millisecond)
|
||||
storeSetter := fx.storageProvider.(storeSetter)
|
||||
storeSetter.SetStore(sp, newStore)
|
||||
spc, err = fx.spaceService.NewSpace(ctx, sp, Deps{TreeSyncer: mockTreeSyncer{}, SyncStatus: syncstatus.NewNoOpSyncStatus()})
|
||||
spc, err = fx.spaceService.NewSpace(ctx, sp, mockDeps())
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, spc)
|
||||
waitTest := make(chan struct{})
|
||||
|
@ -153,7 +162,7 @@ func TestSpaceDeleteIds(t *testing.T) {
|
|||
require.NotNil(t, sp)
|
||||
|
||||
// initializing space
|
||||
spc, err := fx.spaceService.NewSpace(ctx, sp, Deps{TreeSyncer: mockTreeSyncer{}, SyncStatus: syncstatus.NewNoOpSyncStatus()})
|
||||
spc, err := fx.spaceService.NewSpace(ctx, sp, mockDeps())
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, spc)
|
||||
// adding space to tree manager
|
||||
|
@ -202,7 +211,7 @@ func TestSpaceDeleteIds(t *testing.T) {
|
|||
time.Sleep(100 * time.Millisecond)
|
||||
storeSetter := fx.storageProvider.(storeSetter)
|
||||
storeSetter.SetStore(sp, newStore)
|
||||
spc, err = fx.spaceService.NewSpace(ctx, sp, Deps{TreeSyncer: mockTreeSyncer{}, SyncStatus: syncstatus.NewNoOpSyncStatus()})
|
||||
spc, err = fx.spaceService.NewSpace(ctx, sp, mockDeps())
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, spc)
|
||||
waitTest := make(chan struct{})
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
|
||||
"github.com/anyproto/any-sync/app/logger"
|
||||
"github.com/anyproto/any-sync/commonspace/deletionstate"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/synctree"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
|
||||
"github.com/anyproto/any-sync/commonspace/object/treemanager"
|
||||
"github.com/anyproto/any-sync/commonspace/spacestorage"
|
||||
|
@ -43,7 +44,7 @@ func (d *deleter) Delete(ctx context.Context) {
|
|||
}
|
||||
} else {
|
||||
err = d.getter.DeleteTree(ctx, spaceId, id)
|
||||
if err != nil && !errors.Is(err, spacestorage.ErrTreeStorageAlreadyDeleted) {
|
||||
if err != nil && !errors.Is(err, spacestorage.ErrTreeStorageAlreadyDeleted) && !errors.Is(err, synctree.ErrSyncTreeDeleted) {
|
||||
log.Error("failed to delete object", zap.Error(err))
|
||||
continue
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -18,6 +18,19 @@ message AclRoot {
|
|||
// AclAccountInvite contains the public invite key, the private part of which is sent to the user directly
|
||||
message AclAccountInvite {
|
||||
bytes inviteKey = 1;
|
||||
AclInviteType inviteType = 2;
|
||||
AclUserPermissions permissions = 3;
|
||||
bytes encryptedReadKey = 4;
|
||||
}
|
||||
|
||||
message AclAccountInviteChange {
|
||||
string inviteRecordId = 1;
|
||||
AclUserPermissions permissions = 2;
|
||||
}
|
||||
|
||||
enum AclInviteType {
|
||||
RequestToJoin = 0;
|
||||
AnyoneCanJoin = 1;
|
||||
}
|
||||
|
||||
// AclAccountRequestJoin contains the reference to the invite record and the data of the person who wants to join, confirmed by the private invite key
|
||||
|
@ -29,6 +42,17 @@ message AclAccountRequestJoin {
|
|||
bytes metadata = 4;
|
||||
}
|
||||
|
||||
// AclInviteJoin contains the reference to the invite record and the data of the person who wants to join, confirmed by the private invite key
|
||||
// The person must encrypt the key with its own public key
|
||||
message AclAccountInviteJoin {
|
||||
bytes identity = 1;
|
||||
string inviteRecordId = 2;
|
||||
bytes inviteIdentitySignature = 3;
|
||||
// Metadata is encrypted with metadata key of the space
|
||||
bytes metadata = 4;
|
||||
bytes encryptedReadKey = 5;
|
||||
}
|
||||
|
||||
// AclAccountRequestAccept contains the reference to join record and all read keys, encrypted with the identity of the requestor
|
||||
message AclAccountRequestAccept {
|
||||
bytes identity = 1;
|
||||
|
@ -90,6 +114,7 @@ message AclReadKeyChange {
|
|||
bytes encryptedMetadataPrivKey = 3;
|
||||
// EncryptedOldReadKey is encrypted with new read key
|
||||
bytes encryptedOldReadKey = 4;
|
||||
repeated AclEncryptedReadKey inviteKeys = 5;
|
||||
}
|
||||
|
||||
// AclAccountRemove removes an account and changes read key for space
|
||||
|
@ -118,6 +143,8 @@ message AclContentValue {
|
|||
AclAccountPermissionChanges permissionChanges = 10;
|
||||
AclAccountsAdd accountsAdd = 11;
|
||||
AclAccountRequestCancel requestCancel = 12;
|
||||
AclAccountInviteJoin inviteJoin = 13;
|
||||
AclAccountInviteChange inviteChange = 14;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
|
||||
"github.com/anyproto/any-sync/commonspace/object/accountdata"
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/aclrecordproto"
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/recordverifier"
|
||||
"github.com/anyproto/any-sync/consensus/consensusproto"
|
||||
"github.com/anyproto/any-sync/util/cidutil"
|
||||
"github.com/anyproto/any-sync/util/crypto"
|
||||
|
@ -26,6 +27,11 @@ type RequestJoinPayload struct {
|
|||
Metadata []byte
|
||||
}
|
||||
|
||||
type InviteJoinPayload struct {
|
||||
InviteKey crypto.PrivKey
|
||||
Metadata []byte
|
||||
}
|
||||
|
||||
type ReadKeyChangePayload struct {
|
||||
MetadataKey crypto.PrivKey
|
||||
ReadKey crypto.SymKey
|
||||
|
@ -41,6 +47,11 @@ type PermissionChangePayload struct {
|
|||
Permissions AclPermissions
|
||||
}
|
||||
|
||||
type InviteChangePayload struct {
|
||||
IniviteRecordId string
|
||||
Permissions AclPermissions
|
||||
}
|
||||
|
||||
type PermissionChangesPayload struct {
|
||||
Changes []PermissionChangePayload
|
||||
}
|
||||
|
@ -55,6 +66,10 @@ type AccountAdd struct {
|
|||
Metadata []byte
|
||||
}
|
||||
|
||||
type NewInvites struct {
|
||||
Permissions AclPermissions
|
||||
}
|
||||
|
||||
type BatchRequestPayload struct {
|
||||
Additions []AccountAdd
|
||||
Changes []PermissionChangePayload
|
||||
|
@ -62,6 +77,8 @@ type BatchRequestPayload struct {
|
|||
Approvals []RequestAcceptPayload
|
||||
Declines []string
|
||||
InviteRevokes []string
|
||||
InviteChanges []InviteChangePayload
|
||||
NewInvites []AclPermissions
|
||||
}
|
||||
|
||||
type AccountRemovePayload struct {
|
||||
|
@ -74,14 +91,22 @@ type InviteResult struct {
|
|||
InviteKey crypto.PrivKey
|
||||
}
|
||||
|
||||
type BatchResult struct {
|
||||
Rec *consensusproto.RawRecord
|
||||
Invites []crypto.PrivKey
|
||||
}
|
||||
|
||||
type AclRecordBuilder interface {
|
||||
UnmarshallWithId(rawIdRecord *consensusproto.RawRecordWithId) (rec *AclRecord, err error)
|
||||
Unmarshall(rawRecord *consensusproto.RawRecord) (rec *AclRecord, err error)
|
||||
|
||||
BuildRoot(content RootContent) (rec *consensusproto.RawRecordWithId, err error)
|
||||
BuildBatchRequest(payload BatchRequestPayload) (rawRecord *consensusproto.RawRecord, err error)
|
||||
BuildBatchRequest(payload BatchRequestPayload) (batchResult BatchResult, err error)
|
||||
BuildInvite() (res InviteResult, err error)
|
||||
BuildInviteAnyone(permissions AclPermissions) (res InviteResult, err error)
|
||||
BuildInviteChange(inviteChange InviteChangePayload) (rawRecord *consensusproto.RawRecord, err error)
|
||||
BuildInviteRevoke(inviteRecordId string) (rawRecord *consensusproto.RawRecord, err error)
|
||||
BuildInviteJoin(payload InviteJoinPayload) (rawRecord *consensusproto.RawRecord, err error)
|
||||
BuildRequestJoin(payload RequestJoinPayload) (rawRecord *consensusproto.RawRecord, err error)
|
||||
BuildRequestAccept(payload RequestAcceptPayload) (rawRecord *consensusproto.RawRecord, err error)
|
||||
BuildRequestDecline(requestRecordId string) (rawRecord *consensusproto.RawRecord, err error)
|
||||
|
@ -98,11 +123,11 @@ type aclRecordBuilder struct {
|
|||
id string
|
||||
keyStorage crypto.KeyStorage
|
||||
accountKeys *accountdata.AccountKeys
|
||||
verifier AcceptorVerifier
|
||||
verifier recordverifier.AcceptorVerifier
|
||||
state *AclState
|
||||
}
|
||||
|
||||
func NewAclRecordBuilder(id string, keyStorage crypto.KeyStorage, keys *accountdata.AccountKeys, verifier AcceptorVerifier) AclRecordBuilder {
|
||||
func NewAclRecordBuilder(id string, keyStorage crypto.KeyStorage, keys *accountdata.AccountKeys, verifier recordverifier.AcceptorVerifier) AclRecordBuilder {
|
||||
return &aclRecordBuilder{
|
||||
id: id,
|
||||
keyStorage: keyStorage,
|
||||
|
@ -111,51 +136,84 @@ func NewAclRecordBuilder(id string, keyStorage crypto.KeyStorage, keys *accountd
|
|||
}
|
||||
}
|
||||
|
||||
func (a *aclRecordBuilder) BuildBatchRequest(payload BatchRequestPayload) (rawRec *consensusproto.RawRecord, err error) {
|
||||
var aclContent []*aclrecordproto.AclContentValue
|
||||
if len(payload.Additions) > 0 {
|
||||
content, err := a.buildAccountsAdd(AccountsAddPayload{Additions: payload.Additions})
|
||||
func (a *aclRecordBuilder) BuildBatchRequest(payload BatchRequestPayload) (batchResult BatchResult, err error) {
|
||||
var (
|
||||
contentList []*aclrecordproto.AclContentValue
|
||||
content *aclrecordproto.AclContentValue
|
||||
)
|
||||
if len(payload.Removals.Identities) > 0 {
|
||||
content, err = a.buildAccountRemove(payload.Removals)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return
|
||||
}
|
||||
aclContent = append(aclContent, content)
|
||||
contentList = append(contentList, content)
|
||||
}
|
||||
if len(payload.Additions) > 0 {
|
||||
content, err = a.buildAccountsAdd(AccountsAddPayload{Additions: payload.Additions}, payload.Removals.Change.MetadataKey.GetPublic(), payload.Removals.Change.ReadKey)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
contentList = append(contentList, content)
|
||||
}
|
||||
if len(payload.Changes) > 0 {
|
||||
content, err := a.buildPermissionChanges(PermissionChangesPayload{Changes: payload.Changes})
|
||||
content, err = a.buildPermissionChanges(PermissionChangesPayload{Changes: payload.Changes})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return
|
||||
}
|
||||
aclContent = append(aclContent, content)
|
||||
contentList = append(contentList, content)
|
||||
}
|
||||
for _, acc := range payload.Approvals {
|
||||
content, err := a.buildRequestAccept(acc)
|
||||
content, err = a.buildRequestAccept(acc, payload.Removals.Change.ReadKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return
|
||||
}
|
||||
aclContent = append(aclContent, content)
|
||||
contentList = append(contentList, content)
|
||||
}
|
||||
for _, id := range payload.Declines {
|
||||
content, err := a.buildRequestDecline(id)
|
||||
content, err = a.buildRequestDecline(id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return
|
||||
}
|
||||
aclContent = append(aclContent, content)
|
||||
contentList = append(contentList, content)
|
||||
}
|
||||
for _, id := range payload.InviteRevokes {
|
||||
content, err := a.buildInviteRevoke(id)
|
||||
content, err = a.buildInviteRevoke(id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return
|
||||
}
|
||||
aclContent = append(aclContent, content)
|
||||
contentList = append(contentList, content)
|
||||
}
|
||||
if len(payload.Removals.Identities) > 0 {
|
||||
content, err := a.buildAccountRemove(payload.Removals)
|
||||
for _, invite := range payload.InviteChanges {
|
||||
content, err = a.buildInviteChange(invite)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return
|
||||
}
|
||||
aclContent = append(aclContent, content)
|
||||
contentList = append(contentList, content)
|
||||
}
|
||||
return a.buildRecords(aclContent)
|
||||
for _, perms := range payload.NewInvites {
|
||||
var privKey crypto.PrivKey
|
||||
if perms.NoPermissions() {
|
||||
privKey, content, err = a.buildInvite()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
contentList = append(contentList, content)
|
||||
batchResult.Invites = append(batchResult.Invites, privKey)
|
||||
} else {
|
||||
privKey, content, err = a.buildInviteAnyone(perms)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
contentList = append(contentList, content)
|
||||
batchResult.Invites = append(batchResult.Invites, privKey)
|
||||
}
|
||||
}
|
||||
res, err := a.buildRecords(contentList)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
batchResult.Rec = res
|
||||
return
|
||||
}
|
||||
|
||||
func (a *aclRecordBuilder) buildRecord(aclContent *aclrecordproto.AclContentValue) (rawRec *consensusproto.RawRecord, err error) {
|
||||
|
@ -190,9 +248,20 @@ func (a *aclRecordBuilder) buildRecords(aclContent []*aclrecordproto.AclContentV
|
|||
Payload: marshalledRec,
|
||||
Signature: signature,
|
||||
}
|
||||
err = a.preflightCheck(rawRec)
|
||||
return
|
||||
}
|
||||
|
||||
func (a *aclRecordBuilder) preflightCheck(rawRecord *consensusproto.RawRecord) (err error) {
|
||||
aclRec, err := a.Unmarshall(rawRecord)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
cp := a.state.Copy()
|
||||
cp.contentValidator.(*contentValidator).verifier = recordverifier.NewValidateFull()
|
||||
return cp.ApplyRecord(aclRec)
|
||||
}
|
||||
|
||||
func (a *aclRecordBuilder) BuildPermissionChanges(payload PermissionChangesPayload) (rawRecord *consensusproto.RawRecord, err error) {
|
||||
content, err := a.buildPermissionChanges(payload)
|
||||
if err != nil {
|
||||
|
@ -231,14 +300,14 @@ func (a *aclRecordBuilder) buildPermissionChanges(payload PermissionChangesPaylo
|
|||
}
|
||||
|
||||
func (a *aclRecordBuilder) BuildAccountsAdd(payload AccountsAddPayload) (rawRecord *consensusproto.RawRecord, err error) {
|
||||
content, err := a.buildAccountsAdd(payload)
|
||||
content, err := a.buildAccountsAdd(payload, nil, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return a.buildRecord(content)
|
||||
}
|
||||
|
||||
func (a *aclRecordBuilder) buildAccountsAdd(payload AccountsAddPayload) (value *aclrecordproto.AclContentValue, err error) {
|
||||
func (a *aclRecordBuilder) buildAccountsAdd(payload AccountsAddPayload, mkKey crypto.PubKey, readKey crypto.SymKey) (value *aclrecordproto.AclContentValue, err error) {
|
||||
var accs []*aclrecordproto.AclAccountAdd
|
||||
for _, acc := range payload.Additions {
|
||||
if !a.state.Permissions(acc.Identity).NoPermissions() {
|
||||
|
@ -247,9 +316,11 @@ func (a *aclRecordBuilder) buildAccountsAdd(payload AccountsAddPayload) (value *
|
|||
if acc.Permissions.IsOwner() {
|
||||
return nil, ErrIsOwner
|
||||
}
|
||||
mkKey, err := a.state.CurrentMetadataKey()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
if mkKey == nil {
|
||||
mkKey, err = a.state.CurrentMetadataKey()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
encMeta, err := mkKey.Encrypt(acc.Metadata)
|
||||
if err != nil {
|
||||
|
@ -258,9 +329,11 @@ func (a *aclRecordBuilder) buildAccountsAdd(payload AccountsAddPayload) (value *
|
|||
if len(encMeta) > MaxMetadataLen {
|
||||
return nil, ErrMetadataTooLarge
|
||||
}
|
||||
readKey, err := a.state.CurrentReadKey()
|
||||
if err != nil {
|
||||
return nil, ErrNoReadKey
|
||||
if readKey == nil {
|
||||
readKey, err = a.state.CurrentReadKey()
|
||||
if err != nil {
|
||||
return nil, ErrNoReadKey
|
||||
}
|
||||
}
|
||||
protoKey, err := readKey.Marshall()
|
||||
if err != nil {
|
||||
|
@ -287,6 +360,20 @@ func (a *aclRecordBuilder) buildAccountsAdd(payload AccountsAddPayload) (value *
|
|||
}
|
||||
|
||||
func (a *aclRecordBuilder) BuildInvite() (res InviteResult, err error) {
|
||||
privKey, content, err := a.buildInvite()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
rawRec, err := a.buildRecord(content)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
res.InviteKey = privKey
|
||||
res.InviteRec = rawRec
|
||||
return
|
||||
}
|
||||
|
||||
func (a *aclRecordBuilder) buildInvite() (invKey crypto.PrivKey, content *aclrecordproto.AclContentValue, err error) {
|
||||
if !a.state.Permissions(a.state.pubKey).CanManageAccounts() {
|
||||
err = ErrInsufficientPermissions
|
||||
return
|
||||
|
@ -300,7 +387,37 @@ func (a *aclRecordBuilder) BuildInvite() (res InviteResult, err error) {
|
|||
return
|
||||
}
|
||||
inviteRec := &aclrecordproto.AclAccountInvite{InviteKey: invitePubKey}
|
||||
content := &aclrecordproto.AclContentValue{Value: &aclrecordproto.AclContentValue_Invite{Invite: inviteRec}}
|
||||
content = &aclrecordproto.AclContentValue{Value: &aclrecordproto.AclContentValue_Invite{Invite: inviteRec}}
|
||||
invKey = privKey
|
||||
return
|
||||
}
|
||||
|
||||
func (a *aclRecordBuilder) BuildInviteChange(inviteChange InviteChangePayload) (rawRecord *consensusproto.RawRecord, err error) {
|
||||
content, err := a.buildInviteChange(inviteChange)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return a.buildRecord(content)
|
||||
}
|
||||
|
||||
func (a *aclRecordBuilder) buildInviteChange(inviteChange InviteChangePayload) (content *aclrecordproto.AclContentValue, err error) {
|
||||
if !a.state.Permissions(a.state.pubKey).CanManageAccounts() {
|
||||
err = ErrInsufficientPermissions
|
||||
return
|
||||
}
|
||||
inviteRec := &aclrecordproto.AclAccountInviteChange{
|
||||
InviteRecordId: inviteChange.IniviteRecordId,
|
||||
Permissions: aclrecordproto.AclUserPermissions(inviteChange.Permissions),
|
||||
}
|
||||
content = &aclrecordproto.AclContentValue{Value: &aclrecordproto.AclContentValue_InviteChange{InviteChange: inviteRec}}
|
||||
return
|
||||
}
|
||||
|
||||
func (a *aclRecordBuilder) BuildInviteAnyone(permissions AclPermissions) (res InviteResult, err error) {
|
||||
privKey, content, err := a.buildInviteAnyone(permissions)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
rawRec, err := a.buildRecord(content)
|
||||
if err != nil {
|
||||
return
|
||||
|
@ -310,6 +427,42 @@ func (a *aclRecordBuilder) BuildInvite() (res InviteResult, err error) {
|
|||
return
|
||||
}
|
||||
|
||||
func (a *aclRecordBuilder) buildInviteAnyone(permissions AclPermissions) (invKey crypto.PrivKey, content *aclrecordproto.AclContentValue, err error) {
|
||||
if !a.state.Permissions(a.state.pubKey).CanManageAccounts() {
|
||||
err = ErrInsufficientPermissions
|
||||
return
|
||||
}
|
||||
privKey, pubKey, err := crypto.GenerateRandomEd25519KeyPair()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
invitePubKey, err := pubKey.Marshall()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
curReadKey, err := a.state.CurrentReadKey()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
raw, err := curReadKey.Marshall()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
encReadKey, err := pubKey.Encrypt(raw)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
inviteRec := &aclrecordproto.AclAccountInvite{
|
||||
InviteKey: invitePubKey,
|
||||
InviteType: aclrecordproto.AclInviteType_AnyoneCanJoin,
|
||||
Permissions: aclrecordproto.AclUserPermissions(permissions),
|
||||
EncryptedReadKey: encReadKey,
|
||||
}
|
||||
content = &aclrecordproto.AclContentValue{Value: &aclrecordproto.AclContentValue_Invite{Invite: inviteRec}}
|
||||
invKey = privKey
|
||||
return
|
||||
}
|
||||
|
||||
func (a *aclRecordBuilder) BuildInviteRevoke(inviteRecordId string) (rawRecord *consensusproto.RawRecord, err error) {
|
||||
content, err := a.buildInviteRevoke(inviteRecordId)
|
||||
if err != nil {
|
||||
|
@ -323,7 +476,7 @@ func (a *aclRecordBuilder) buildInviteRevoke(inviteRecordId string) (value *aclr
|
|||
err = ErrInsufficientPermissions
|
||||
return
|
||||
}
|
||||
_, exists := a.state.inviteKeys[inviteRecordId]
|
||||
_, exists := a.state.invites[inviteRecordId]
|
||||
if !exists {
|
||||
err = ErrNoSuchInvite
|
||||
return
|
||||
|
@ -334,17 +487,17 @@ func (a *aclRecordBuilder) buildInviteRevoke(inviteRecordId string) (value *aclr
|
|||
|
||||
func (a *aclRecordBuilder) BuildRequestJoin(payload RequestJoinPayload) (rawRecord *consensusproto.RawRecord, err error) {
|
||||
var inviteId string
|
||||
for id, key := range a.state.inviteKeys {
|
||||
if key.Equals(payload.InviteKey.GetPublic()) {
|
||||
for id, inv := range a.state.invites {
|
||||
if inv.Key.Equals(payload.InviteKey.GetPublic()) && inv.Type == aclrecordproto.AclInviteType_RequestToJoin {
|
||||
inviteId = id
|
||||
}
|
||||
}
|
||||
key, exists := a.state.inviteKeys[inviteId]
|
||||
invite, exists := a.state.invites[inviteId]
|
||||
if !exists {
|
||||
err = ErrNoSuchInvite
|
||||
return
|
||||
}
|
||||
if !payload.InviteKey.GetPublic().Equals(key) {
|
||||
if !payload.InviteKey.GetPublic().Equals(invite.Key) {
|
||||
err = ErrIncorrectInviteKey
|
||||
return
|
||||
}
|
||||
|
@ -385,15 +538,81 @@ func (a *aclRecordBuilder) BuildRequestJoin(payload RequestJoinPayload) (rawReco
|
|||
return a.buildRecord(content)
|
||||
}
|
||||
|
||||
func (a *aclRecordBuilder) BuildInviteJoin(payload InviteJoinPayload) (rawRecord *consensusproto.RawRecord, err error) {
|
||||
var inviteId string
|
||||
for id, inv := range a.state.invites {
|
||||
if inv.Key.Equals(payload.InviteKey.GetPublic()) && inv.Type == aclrecordproto.AclInviteType_AnyoneCanJoin {
|
||||
inviteId = id
|
||||
}
|
||||
}
|
||||
invite, exists := a.state.invites[inviteId]
|
||||
if !exists {
|
||||
err = ErrNoSuchInvite
|
||||
return
|
||||
}
|
||||
if !payload.InviteKey.GetPublic().Equals(invite.Key) {
|
||||
err = ErrIncorrectInviteKey
|
||||
return
|
||||
}
|
||||
if !a.state.Permissions(a.accountKeys.SignKey.GetPublic()).NoPermissions() {
|
||||
err = ErrInsufficientPermissions
|
||||
return
|
||||
}
|
||||
mkKey, err := a.state.CurrentMetadataKey()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
encMeta, err := mkKey.Encrypt(payload.Metadata)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(encMeta) > MaxMetadataLen {
|
||||
return nil, ErrMetadataTooLarge
|
||||
}
|
||||
rawIdentity, err := a.accountKeys.SignKey.GetPublic().Raw()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
signature, err := payload.InviteKey.Sign(rawIdentity)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
key, err := a.state.DecryptInvite(payload.InviteKey)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
readKey, err := key.Marshall()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
encReadKey, err := a.accountKeys.SignKey.GetPublic().Encrypt(readKey)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
protoIdentity, err := a.accountKeys.SignKey.GetPublic().Marshall()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
joinRec := &aclrecordproto.AclAccountInviteJoin{
|
||||
Identity: protoIdentity,
|
||||
InviteRecordId: inviteId,
|
||||
InviteIdentitySignature: signature,
|
||||
Metadata: encMeta,
|
||||
EncryptedReadKey: encReadKey,
|
||||
}
|
||||
content := &aclrecordproto.AclContentValue{Value: &aclrecordproto.AclContentValue_InviteJoin{InviteJoin: joinRec}}
|
||||
return a.buildRecord(content)
|
||||
}
|
||||
|
||||
func (a *aclRecordBuilder) BuildRequestAccept(payload RequestAcceptPayload) (rawRecord *consensusproto.RawRecord, err error) {
|
||||
content, err := a.buildRequestAccept(payload)
|
||||
content, err := a.buildRequestAccept(payload, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return a.buildRecord(content)
|
||||
}
|
||||
|
||||
func (a *aclRecordBuilder) buildRequestAccept(payload RequestAcceptPayload) (value *aclrecordproto.AclContentValue, err error) {
|
||||
func (a *aclRecordBuilder) buildRequestAccept(payload RequestAcceptPayload, readKey crypto.SymKey) (value *aclrecordproto.AclContentValue, err error) {
|
||||
if !a.state.Permissions(a.state.pubKey).CanManageAccounts() {
|
||||
err = ErrInsufficientPermissions
|
||||
return
|
||||
|
@ -403,9 +622,11 @@ func (a *aclRecordBuilder) buildRequestAccept(payload RequestAcceptPayload) (val
|
|||
err = ErrNoSuchRequest
|
||||
return
|
||||
}
|
||||
readKey, err := a.state.CurrentReadKey()
|
||||
if err != nil {
|
||||
return nil, ErrNoReadKey
|
||||
if readKey == nil {
|
||||
readKey, err = a.state.CurrentReadKey()
|
||||
if err != nil {
|
||||
return nil, ErrNoReadKey
|
||||
}
|
||||
}
|
||||
protoKey, err := readKey.Marshall()
|
||||
if err != nil {
|
||||
|
@ -506,13 +727,19 @@ func (a *aclRecordBuilder) buildReadKeyChange(payload ReadKeyChangePayload, remo
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var aclReadKeys []*aclrecordproto.AclEncryptedReadKey
|
||||
var (
|
||||
aclReadKeys []*aclrecordproto.AclEncryptedReadKey
|
||||
invites []*aclrecordproto.AclEncryptedReadKey
|
||||
)
|
||||
for identity, st := range a.state.accountStates {
|
||||
if removedIdentities != nil {
|
||||
if _, exists := removedIdentities[identity]; exists {
|
||||
continue
|
||||
}
|
||||
}
|
||||
if st.Permissions.NoPermissions() {
|
||||
continue
|
||||
}
|
||||
protoIdentity, err := st.PubKey.Marshall()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -526,6 +753,23 @@ func (a *aclRecordBuilder) buildReadKeyChange(payload ReadKeyChangePayload, remo
|
|||
EncryptedReadKey: enc,
|
||||
})
|
||||
}
|
||||
for _, invite := range a.state.invites {
|
||||
if invite.Type != aclrecordproto.AclInviteType_AnyoneCanJoin {
|
||||
continue
|
||||
}
|
||||
protoIdentity, err := invite.Key.Marshall()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
enc, err := invite.Key.Encrypt(protoKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
invites = append(invites, &aclrecordproto.AclEncryptedReadKey{
|
||||
Identity: protoIdentity,
|
||||
EncryptedReadKey: enc,
|
||||
})
|
||||
}
|
||||
// encrypting metadata key with new read key
|
||||
mkPubKey, err := payload.MetadataKey.GetPublic().Marshall()
|
||||
if err != nil {
|
||||
|
@ -557,6 +801,7 @@ func (a *aclRecordBuilder) buildReadKeyChange(payload ReadKeyChangePayload, remo
|
|||
MetadataPubKey: mkPubKey,
|
||||
EncryptedMetadataPrivKey: encPrivKey,
|
||||
EncryptedOldReadKey: encOldKey,
|
||||
InviteKeys: invites,
|
||||
}
|
||||
return readRec, nil
|
||||
}
|
||||
|
|
|
@ -5,9 +5,11 @@ import (
|
|||
|
||||
"github.com/anyproto/protobuf/proto"
|
||||
"go.uber.org/zap"
|
||||
"golang.org/x/exp/slices"
|
||||
|
||||
"github.com/anyproto/any-sync/app/logger"
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/aclrecordproto"
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/recordverifier"
|
||||
"github.com/anyproto/any-sync/util/crypto"
|
||||
)
|
||||
|
||||
|
@ -25,6 +27,7 @@ var (
|
|||
ErrNoSuchRequest = errors.New("no such request")
|
||||
ErrNoSuchInvite = errors.New("no such invite")
|
||||
ErrInsufficientPermissions = errors.New("insufficient permissions")
|
||||
ErrDuplicateInvites = errors.New("duplicate invites")
|
||||
ErrIsOwner = errors.New("can't be made by owner")
|
||||
ErrIncorrectNumberOfAccounts = errors.New("incorrect number of accounts")
|
||||
ErrDuplicateAccounts = errors.New("duplicate accounts")
|
||||
|
@ -48,6 +51,17 @@ type AclKeys struct {
|
|||
ReadKey crypto.SymKey
|
||||
MetadataPrivKey crypto.PrivKey
|
||||
MetadataPubKey crypto.PubKey
|
||||
|
||||
oldEncryptedReadKey []byte
|
||||
encMetadatKey []byte
|
||||
}
|
||||
|
||||
type Invite struct {
|
||||
Key crypto.PubKey
|
||||
Type aclrecordproto.AclInviteType
|
||||
Permissions AclPermissions
|
||||
Id string
|
||||
encryptedKey []byte
|
||||
}
|
||||
|
||||
type AclState struct {
|
||||
|
@ -57,7 +71,7 @@ type AclState struct {
|
|||
// accountStates is a map pubKey -> state which defines current account state
|
||||
accountStates map[string]AccountState
|
||||
// inviteKeys is a map recordId -> invite
|
||||
inviteKeys map[string]crypto.PubKey
|
||||
invites map[string]Invite
|
||||
// requestRecords is a map recordId -> RequestRecord
|
||||
requestRecords map[string]RequestRecord
|
||||
// pendingRequests is a map pubKey -> recordId
|
||||
|
@ -75,22 +89,20 @@ type AclState struct {
|
|||
|
||||
func newAclStateWithKeys(
|
||||
rootRecord *AclRecord,
|
||||
key crypto.PrivKey) (st *AclState, err error) {
|
||||
key crypto.PrivKey,
|
||||
verifier recordverifier.AcceptorVerifier) (st *AclState, err error) {
|
||||
st = &AclState{
|
||||
id: rootRecord.Id,
|
||||
key: key,
|
||||
pubKey: key.GetPublic(),
|
||||
keys: make(map[string]AclKeys),
|
||||
accountStates: make(map[string]AccountState),
|
||||
inviteKeys: make(map[string]crypto.PubKey),
|
||||
invites: make(map[string]Invite),
|
||||
requestRecords: make(map[string]RequestRecord),
|
||||
pendingRequests: make(map[string]string),
|
||||
keyStore: crypto.NewKeyStorage(),
|
||||
}
|
||||
st.contentValidator = &contentValidator{
|
||||
keyStore: st.keyStore,
|
||||
aclState: st,
|
||||
}
|
||||
st.contentValidator = newContentValidator(st.keyStore, st, verifier)
|
||||
err = st.applyRoot(rootRecord)
|
||||
if err != nil {
|
||||
return
|
||||
|
@ -98,20 +110,17 @@ func newAclStateWithKeys(
|
|||
return st, nil
|
||||
}
|
||||
|
||||
func newAclState(rootRecord *AclRecord) (st *AclState, err error) {
|
||||
func newAclState(rootRecord *AclRecord, verifier recordverifier.AcceptorVerifier) (st *AclState, err error) {
|
||||
st = &AclState{
|
||||
id: rootRecord.Id,
|
||||
keys: make(map[string]AclKeys),
|
||||
accountStates: make(map[string]AccountState),
|
||||
inviteKeys: make(map[string]crypto.PubKey),
|
||||
invites: make(map[string]Invite),
|
||||
requestRecords: make(map[string]RequestRecord),
|
||||
pendingRequests: make(map[string]string),
|
||||
keyStore: crypto.NewKeyStorage(),
|
||||
}
|
||||
st.contentValidator = &contentValidator{
|
||||
keyStore: st.keyStore,
|
||||
aclState: st,
|
||||
}
|
||||
st.contentValidator = newContentValidator(st.keyStore, st, verifier)
|
||||
err = st.applyRoot(rootRecord)
|
||||
if err != nil {
|
||||
return
|
||||
|
@ -209,9 +218,12 @@ func (st *AclState) HadReadPermissions(identity crypto.PubKey) (had bool) {
|
|||
return false
|
||||
}
|
||||
|
||||
func (st *AclState) Invites() []crypto.PubKey {
|
||||
var invites []crypto.PubKey
|
||||
for _, inv := range st.inviteKeys {
|
||||
func (st *AclState) Invites(inviteType ...aclrecordproto.AclInviteType) []Invite {
|
||||
var invites []Invite
|
||||
for _, inv := range st.invites {
|
||||
if len(inviteType) > 0 && !slices.Contains(inviteType, inv.Type) {
|
||||
continue
|
||||
}
|
||||
invites = append(invites, inv)
|
||||
}
|
||||
return invites
|
||||
|
@ -223,12 +235,36 @@ func (st *AclState) Key() crypto.PrivKey {
|
|||
|
||||
func (st *AclState) InviteIds() []string {
|
||||
var invites []string
|
||||
for invId := range st.inviteKeys {
|
||||
for invId := range st.invites {
|
||||
invites = append(invites, invId)
|
||||
}
|
||||
return invites
|
||||
}
|
||||
|
||||
func (st *AclState) RequestIds() []string {
|
||||
var requests []string
|
||||
for reqId := range st.requestRecords {
|
||||
requests = append(requests, reqId)
|
||||
}
|
||||
return requests
|
||||
}
|
||||
|
||||
func (st *AclState) DecryptInvite(invitePk crypto.PrivKey) (key crypto.SymKey, err error) {
|
||||
if invitePk == nil {
|
||||
return nil, ErrNoReadKey
|
||||
}
|
||||
for _, invite := range st.invites {
|
||||
if invite.Key.Equals(invitePk.GetPublic()) {
|
||||
res, err := st.unmarshallDecryptReadKey(invite.encryptedKey, invitePk.Decrypt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
}
|
||||
return nil, ErrNoSuchInvite
|
||||
}
|
||||
|
||||
func (st *AclState) ApplyRecord(record *AclRecord) (err error) {
|
||||
if st.lastRecordId != record.PrevId {
|
||||
err = ErrIncorrectRecordSequence
|
||||
|
@ -277,7 +313,10 @@ func (st *AclState) applyRoot(record *AclRecord) (err error) {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
st.keys[record.Id] = AclKeys{MetadataPubKey: mkPubKey}
|
||||
st.keys[record.Id] = AclKeys{
|
||||
MetadataPubKey: mkPubKey,
|
||||
encMetadatKey: root.EncryptedMetadataPrivKey,
|
||||
}
|
||||
} else {
|
||||
// this should be a derived acl
|
||||
st.keys[record.Id] = AclKeys{}
|
||||
|
@ -350,7 +389,7 @@ func (st *AclState) Copy() *AclState {
|
|||
pubKey: st.key.GetPublic(),
|
||||
keys: make(map[string]AclKeys),
|
||||
accountStates: make(map[string]AccountState),
|
||||
inviteKeys: make(map[string]crypto.PubKey),
|
||||
invites: make(map[string]Invite),
|
||||
requestRecords: make(map[string]RequestRecord),
|
||||
pendingRequests: make(map[string]string),
|
||||
keyStore: st.keyStore,
|
||||
|
@ -365,8 +404,8 @@ func (st *AclState) Copy() *AclState {
|
|||
accState.PermissionChanges = permChanges
|
||||
newSt.accountStates[k] = accState
|
||||
}
|
||||
for k, v := range st.inviteKeys {
|
||||
newSt.inviteKeys[k] = v
|
||||
for k, v := range st.invites {
|
||||
newSt.invites[k] = v
|
||||
}
|
||||
for k, v := range st.requestRecords {
|
||||
newSt.requestRecords[k] = v
|
||||
|
@ -377,12 +416,16 @@ func (st *AclState) Copy() *AclState {
|
|||
newSt.readKeyChanges = append(newSt.readKeyChanges, st.readKeyChanges...)
|
||||
newSt.list = st.list
|
||||
newSt.lastRecordId = st.lastRecordId
|
||||
newSt.contentValidator = newContentValidator(newSt.keyStore, newSt)
|
||||
newSt.contentValidator = newContentValidator(newSt.keyStore, newSt, st.list.verifier)
|
||||
return newSt
|
||||
}
|
||||
|
||||
func (st *AclState) applyChangeContent(ch *aclrecordproto.AclContentValue, record *AclRecord) error {
|
||||
switch {
|
||||
case ch.GetInviteChange() != nil:
|
||||
return st.applyInviteChange(ch.GetInviteChange(), record)
|
||||
case ch.GetInviteJoin() != nil:
|
||||
return st.applyInviteJoin(ch.GetInviteJoin(), record)
|
||||
case ch.GetPermissionChange() != nil:
|
||||
return st.applyPermissionChange(ch.GetPermissionChange(), record)
|
||||
case ch.GetInvite() != nil:
|
||||
|
@ -423,6 +466,17 @@ func (st *AclState) applyPermissionChanges(ch *aclrecordproto.AclAccountPermissi
|
|||
return nil
|
||||
}
|
||||
|
||||
func (st *AclState) applyInviteChange(ch *aclrecordproto.AclAccountInviteChange, record *AclRecord) (err error) {
|
||||
err = st.contentValidator.ValidateInviteChange(ch, record.Identity)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
invite := st.invites[ch.InviteRecordId]
|
||||
invite.Permissions = AclPermissions(ch.Permissions)
|
||||
st.invites[ch.InviteRecordId] = invite
|
||||
return nil
|
||||
}
|
||||
|
||||
func (st *AclState) applyPermissionChange(ch *aclrecordproto.AclAccountPermissionChange, record *AclRecord) error {
|
||||
chIdentity, err := st.keyStore.PubKeyFromProto(ch.Identity)
|
||||
if err != nil {
|
||||
|
@ -452,7 +506,13 @@ func (st *AclState) applyInvite(ch *aclrecordproto.AclAccountInvite, record *Acl
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
st.inviteKeys[record.Id] = inviteKey
|
||||
st.invites[record.Id] = Invite{
|
||||
Key: inviteKey,
|
||||
Id: record.Id,
|
||||
Type: ch.InviteType,
|
||||
Permissions: AclPermissions(ch.Permissions),
|
||||
encryptedKey: ch.EncryptedReadKey,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -461,7 +521,7 @@ func (st *AclState) applyInviteRevoke(ch *aclrecordproto.AclAccountInviteRevoke,
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
delete(st.inviteKeys, ch.InviteRecordId)
|
||||
delete(st.invites, ch.InviteRecordId)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -582,6 +642,51 @@ func (st *AclState) applyRequestAccept(ch *aclrecordproto.AclAccountRequestAccep
|
|||
return st.unpackAllKeys(ch.EncryptedReadKey)
|
||||
}
|
||||
|
||||
func (st *AclState) applyInviteJoin(ch *aclrecordproto.AclAccountInviteJoin, record *AclRecord) error {
|
||||
err := st.contentValidator.ValidateInviteJoin(ch, record.Identity)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
identity, err := st.keyStore.PubKeyFromProto(ch.Identity)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
inviteRecord, _ := st.invites[ch.InviteRecordId]
|
||||
pKeyString := mapKeyFromPubKey(identity)
|
||||
state, exists := st.accountStates[pKeyString]
|
||||
if !exists {
|
||||
st.accountStates[pKeyString] = AccountState{
|
||||
PubKey: identity,
|
||||
Permissions: inviteRecord.Permissions,
|
||||
RequestMetadata: ch.Metadata,
|
||||
KeyRecordId: st.CurrentReadKeyId(),
|
||||
Status: StatusActive,
|
||||
PermissionChanges: []PermissionChange{
|
||||
{
|
||||
Permission: inviteRecord.Permissions,
|
||||
RecordId: record.Id,
|
||||
},
|
||||
},
|
||||
}
|
||||
} else {
|
||||
st.accountStates[pKeyString] = AccountState{
|
||||
PubKey: identity,
|
||||
Permissions: inviteRecord.Permissions,
|
||||
RequestMetadata: ch.Metadata,
|
||||
KeyRecordId: st.CurrentReadKeyId(),
|
||||
Status: StatusActive,
|
||||
PermissionChanges: append(state.PermissionChanges, PermissionChange{
|
||||
Permission: inviteRecord.Permissions,
|
||||
RecordId: record.Id,
|
||||
}),
|
||||
}
|
||||
}
|
||||
if st.pubKey.Equals(identity) {
|
||||
return st.unpackAllKeys(ch.EncryptedReadKey)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (st *AclState) unpackAllKeys(rk []byte) error {
|
||||
iterReadKey, err := st.unmarshallDecryptReadKey(rk, st.key.Decrypt)
|
||||
if err != nil {
|
||||
|
@ -589,42 +694,8 @@ func (st *AclState) unpackAllKeys(rk []byte) error {
|
|||
}
|
||||
for idx := len(st.readKeyChanges) - 1; idx >= 0; idx-- {
|
||||
recId := st.readKeyChanges[idx]
|
||||
rec, err := st.list.Get(recId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// if this is a first key change
|
||||
if recId == st.id {
|
||||
ch := rec.Model.(*aclrecordproto.AclRoot)
|
||||
metadataKey, err := st.unmarshallDecryptPrivKey(ch.EncryptedMetadataPrivKey, iterReadKey.Decrypt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
aclKeys := st.keys[recId]
|
||||
aclKeys.ReadKey = iterReadKey
|
||||
aclKeys.MetadataPrivKey = metadataKey
|
||||
st.keys[recId] = aclKeys
|
||||
break
|
||||
}
|
||||
model := rec.Model.(*aclrecordproto.AclData)
|
||||
content := model.GetAclContent()
|
||||
var readKeyChange *aclrecordproto.AclReadKeyChange
|
||||
for _, ch := range content {
|
||||
switch {
|
||||
case ch.GetReadKeyChange() != nil:
|
||||
readKeyChange = ch.GetReadKeyChange()
|
||||
case ch.GetAccountRemove() != nil:
|
||||
readKeyChange = ch.GetAccountRemove().GetReadKeyChange()
|
||||
}
|
||||
}
|
||||
if readKeyChange == nil {
|
||||
return ErrIncorrectReadKey
|
||||
}
|
||||
oldReadKey, err := st.unmarshallDecryptReadKey(readKeyChange.EncryptedOldReadKey, iterReadKey.Decrypt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
metadataKey, err := st.unmarshallDecryptPrivKey(readKeyChange.EncryptedMetadataPrivKey, iterReadKey.Decrypt)
|
||||
keys := st.keys[recId]
|
||||
metadataKey, err := st.unmarshallDecryptPrivKey(keys.encMetadatKey, iterReadKey.Decrypt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -632,7 +703,16 @@ func (st *AclState) unpackAllKeys(rk []byte) error {
|
|||
aclKeys.ReadKey = iterReadKey
|
||||
aclKeys.MetadataPrivKey = metadataKey
|
||||
st.keys[recId] = aclKeys
|
||||
iterReadKey = oldReadKey
|
||||
if idx != 0 {
|
||||
if keys.oldEncryptedReadKey == nil {
|
||||
return ErrIncorrectReadKey
|
||||
}
|
||||
oldReadKey, err := st.unmarshallDecryptReadKey(keys.oldEncryptedReadKey, iterReadKey.Decrypt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
iterReadKey = oldReadKey
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -744,7 +824,9 @@ func (st *AclState) applyReadKeyChange(ch *aclrecordproto.AclReadKeyChange, reco
|
|||
return err
|
||||
}
|
||||
aclKeys := AclKeys{
|
||||
MetadataPubKey: mkPubKey,
|
||||
MetadataPubKey: mkPubKey,
|
||||
oldEncryptedReadKey: ch.EncryptedOldReadKey,
|
||||
encMetadatKey: ch.EncryptedMetadataPrivKey,
|
||||
}
|
||||
for _, accKey := range ch.AccountKeys {
|
||||
identity, _ := st.keyStore.PubKeyFromProto(accKey.Identity)
|
||||
|
@ -756,6 +838,19 @@ func (st *AclState) applyReadKeyChange(ch *aclrecordproto.AclReadKeyChange, reco
|
|||
aclKeys.ReadKey = res
|
||||
}
|
||||
}
|
||||
for _, encKey := range ch.InviteKeys {
|
||||
invKey, err := st.keyStore.PubKeyFromProto(encKey.Identity)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for key, invite := range st.invites {
|
||||
if invite.Key.Equals(invKey) {
|
||||
invite.encryptedKey = encKey.EncryptedReadKey
|
||||
st.invites[key] = invite
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if aclKeys.ReadKey != nil {
|
||||
metadataKey, err := st.unmarshallDecryptPrivKey(ch.EncryptedMetadataPrivKey, aclKeys.ReadKey.Decrypt)
|
||||
if err != nil {
|
||||
|
@ -792,8 +887,8 @@ func (st *AclState) unmarshallDecryptPrivKey(msg []byte, decryptor func(msg []by
|
|||
}
|
||||
|
||||
func (st *AclState) GetInviteIdByPrivKey(inviteKey crypto.PrivKey) (recId string, err error) {
|
||||
for id, inv := range st.inviteKeys {
|
||||
if inv.Equals(inviteKey.GetPublic()) {
|
||||
for id, inv := range st.invites {
|
||||
if inv.Key.Equals(inviteKey.GetPublic()) {
|
||||
return id, nil
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,12 +29,12 @@ func (sb *aclStateBuilder) Build(records []*AclRecord, list *aclList) (state *Ac
|
|||
return nil, ErrIncorrectRecordSequence
|
||||
}
|
||||
if sb.privKey != nil {
|
||||
state, err = newAclStateWithKeys(records[0], sb.privKey)
|
||||
state, err = newAclStateWithKeys(records[0], sb.privKey, list.verifier)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
state, err = newAclState(records[0])
|
||||
state, err = newAclState(records[0], list.verifier)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ import (
|
|||
|
||||
"github.com/anyproto/any-sync/commonspace/object/accountdata"
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/aclrecordproto"
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/recordverifier"
|
||||
"github.com/anyproto/any-sync/consensus/consensusproto"
|
||||
"github.com/anyproto/any-sync/util/crypto"
|
||||
)
|
||||
|
@ -66,6 +67,7 @@ var (
|
|||
func (a *AclTestExecutor) buildBatchRequest(args []string, acl AclList, getPerm func(perm string) AclPermissions, addRec func(rec *consensusproto.RawRecordWithId) error) (afterAll []func(), err error) {
|
||||
// remove:a,b,c;add:d,rw,m1|e,r,m2;changes:f,rw|g,r;revoke:inv1id;decline:g,h;
|
||||
batchPayload := BatchRequestPayload{}
|
||||
var inviteIds []string
|
||||
for _, arg := range args {
|
||||
parts := strings.Split(arg, ":")
|
||||
if len(parts) != 2 {
|
||||
|
@ -86,7 +88,7 @@ func (a *AclTestExecutor) buildBatchRequest(args []string, acl AclList, getPerm
|
|||
return nil, err
|
||||
}
|
||||
ownerAcl := a.actualAccounts[a.owner].Acl.(*aclList)
|
||||
accountAcl, err := BuildAclListWithIdentity(keys, ownerAcl.storage, NoOpAcceptorVerifier{})
|
||||
accountAcl, err := BuildAclListWithIdentity(keys, ownerAcl.storage, recordverifier.NewValidateFull())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -181,6 +183,24 @@ func (a *AclTestExecutor) buildBatchRequest(args []string, acl AclList, getPerm
|
|||
afterAll = append(afterAll, func() {
|
||||
a.expectedAccounts[id].status = StatusDeclined
|
||||
})
|
||||
case "invite":
|
||||
inviteParts := strings.Split(commandArgs[0], ",")
|
||||
id := inviteParts[0]
|
||||
inviteIds = append(inviteIds, id)
|
||||
batchPayload.NewInvites = append(batchPayload.NewInvites, AclPermissionsNone)
|
||||
case "invite_anyone":
|
||||
inviteParts := strings.Split(commandArgs[0], ",")
|
||||
id := inviteParts[0]
|
||||
var permissions AclPermissions
|
||||
if inviteParts[1] == "r" {
|
||||
permissions = AclPermissions(aclrecordproto.AclUserPermissions_Reader)
|
||||
} else if inviteParts[1] == "rw" {
|
||||
permissions = AclPermissions(aclrecordproto.AclUserPermissions_Writer)
|
||||
} else {
|
||||
permissions = AclPermissions(aclrecordproto.AclUserPermissions_Admin)
|
||||
}
|
||||
inviteIds = append(inviteIds, id)
|
||||
batchPayload.NewInvites = append(batchPayload.NewInvites, permissions)
|
||||
case "approve":
|
||||
recs, err := acl.AclState().JoinRecords(false)
|
||||
if err != nil {
|
||||
|
@ -211,12 +231,14 @@ func (a *AclTestExecutor) buildBatchRequest(args []string, acl AclList, getPerm
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
res, err := acl.RecordBuilder().BuildBatchRequest(batchPayload)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return afterAll, addRec(WrapAclRecord(res))
|
||||
for i, id := range inviteIds {
|
||||
a.invites[id] = res.Invites[i]
|
||||
}
|
||||
return afterAll, addRec(WrapAclRecord(res.Rec))
|
||||
}
|
||||
|
||||
func (a *AclTestExecutor) Execute(cmd string) (err error) {
|
||||
|
@ -273,7 +295,7 @@ func (a *AclTestExecutor) Execute(cmd string) (err error) {
|
|||
} else {
|
||||
ownerAcl := a.actualAccounts[a.owner].Acl.(*aclList)
|
||||
copyStorage := ownerAcl.storage.(*inMemoryStorage).Copy()
|
||||
accountAcl, err := BuildAclListWithIdentity(keys, copyStorage, NoOpAcceptorVerifier{})
|
||||
accountAcl, err := BuildAclListWithIdentity(keys, copyStorage, recordverifier.NewValidateFull())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -291,7 +313,7 @@ func (a *AclTestExecutor) Execute(cmd string) (err error) {
|
|||
keys := a.actualAccounts[account].Keys
|
||||
ownerAcl := a.actualAccounts[a.owner].Acl.(*aclList)
|
||||
copyStorage := ownerAcl.storage.(*inMemoryStorage).Copy()
|
||||
accountAcl, err := BuildAclListWithIdentity(keys, copyStorage, NoOpAcceptorVerifier{})
|
||||
accountAcl, err := BuildAclListWithIdentity(keys, copyStorage, recordverifier.NewValidateFull())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -372,6 +394,51 @@ func (a *AclTestExecutor) Execute(cmd string) (err error) {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case "invite_change":
|
||||
var permissions AclPermissions
|
||||
inviteParts := strings.Split(args[0], ",")
|
||||
if inviteParts[1] == "r" {
|
||||
permissions = AclPermissions(aclrecordproto.AclUserPermissions_Reader)
|
||||
} else if inviteParts[1] == "rw" {
|
||||
permissions = AclPermissions(aclrecordproto.AclUserPermissions_Writer)
|
||||
} else {
|
||||
permissions = AclPermissions(aclrecordproto.AclUserPermissions_Admin)
|
||||
}
|
||||
invite := a.invites[inviteParts[0]]
|
||||
invId, err := acl.AclState().GetInviteIdByPrivKey(invite)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
res, err := acl.RecordBuilder().BuildInviteChange(InviteChangePayload{
|
||||
IniviteRecordId: invId,
|
||||
Permissions: permissions,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = addRec(WrapAclRecord(res))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case "invite_anyone":
|
||||
var permissions AclPermissions
|
||||
inviteParts := strings.Split(args[0], ",")
|
||||
if inviteParts[1] == "r" {
|
||||
permissions = AclPermissions(aclrecordproto.AclUserPermissions_Reader)
|
||||
} else if inviteParts[1] == "rw" {
|
||||
permissions = AclPermissions(aclrecordproto.AclUserPermissions_Writer)
|
||||
} else {
|
||||
permissions = AclPermissions(aclrecordproto.AclUserPermissions_Admin)
|
||||
}
|
||||
res, err := acl.RecordBuilder().BuildInviteAnyone(permissions)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
a.invites[inviteParts[0]] = res.InviteKey
|
||||
err = addRec(WrapAclRecord(res.InviteRec))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case "batch":
|
||||
afterAll, err = a.buildBatchRequest(args, acl, getPerm, addRec)
|
||||
if err != nil {
|
||||
|
@ -448,7 +515,7 @@ func (a *AclTestExecutor) Execute(cmd string) (err error) {
|
|||
return err
|
||||
}
|
||||
ownerAcl := a.actualAccounts[a.owner].Acl.(*aclList)
|
||||
accountAcl, err := BuildAclListWithIdentity(keys, ownerAcl.storage, NoOpAcceptorVerifier{})
|
||||
accountAcl, err := BuildAclListWithIdentity(keys, ownerAcl.storage, recordverifier.NewValidateFull())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -488,6 +555,25 @@ func (a *AclTestExecutor) Execute(cmd string) (err error) {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case "invite_join":
|
||||
invite := a.invites[args[0]]
|
||||
inviteJoin, err := acl.RecordBuilder().BuildInviteJoin(InviteJoinPayload{
|
||||
InviteKey: invite,
|
||||
Metadata: []byte(account),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
invId, err := acl.AclState().GetInviteIdByPrivKey(invite)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = addRec(WrapAclRecord(inviteJoin))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
a.expectedAccounts[account].status = StatusActive
|
||||
a.expectedAccounts[account].perms = acl.AclState().invites[invId].Permissions
|
||||
case "remove":
|
||||
identities := strings.Split(args[0], ",")
|
||||
var pubKeys []crypto.PubKey
|
||||
|
|
|
@ -53,6 +53,7 @@ func TestAclExecutor(t *testing.T) {
|
|||
{"a.init::a", nil},
|
||||
// creating an invite
|
||||
{"a.invite::invId", nil},
|
||||
{"a.invite_anyone::oldInvId,r", nil},
|
||||
// cannot self join
|
||||
{"a.join::invId", ErrInsufficientPermissions},
|
||||
// now b can join
|
||||
|
@ -128,6 +129,19 @@ func TestAclExecutor(t *testing.T) {
|
|||
{"a.changes::guest,none", ErrInsufficientPermissions},
|
||||
// can't change permission of existing user to guest, should be only possible to create it with add
|
||||
{"a.changes::r,g", ErrInsufficientPermissions},
|
||||
{"a.invite_anyone::invAnyoneId,rw", nil},
|
||||
{"new.invite_join::invAnyoneId", nil},
|
||||
// invite keys persist after user removal
|
||||
{"a.remove::new", nil},
|
||||
{"new1.invite_join::invAnyoneId", nil},
|
||||
{"a.revoke::invAnyoneId", nil},
|
||||
{"new2.invite_join::invAnyoneId", ErrNoSuchInvite},
|
||||
{"a.invite_change::oldInvId,a", nil},
|
||||
{"new2.invite_join::oldInvId", nil},
|
||||
{"new2.add::new3,r,new3m", nil},
|
||||
{"a.batch::revoke:oldInvId;invite_anyone:someId,a", nil},
|
||||
{"new4.invite_join::someId", nil},
|
||||
{"new4.add::super,r,superm", nil},
|
||||
}
|
||||
for _, cmd := range cmds {
|
||||
err := a.Execute(cmd.cmd)
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
"sync"
|
||||
|
||||
"github.com/anyproto/any-sync/commonspace/object/accountdata"
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/recordverifier"
|
||||
"github.com/anyproto/any-sync/consensus/consensusproto"
|
||||
"github.com/anyproto/any-sync/util/cidutil"
|
||||
"github.com/anyproto/any-sync/util/crypto"
|
||||
|
@ -26,17 +27,6 @@ type RWLocker interface {
|
|||
RUnlock()
|
||||
}
|
||||
|
||||
type AcceptorVerifier interface {
|
||||
VerifyAcceptor(rec *consensusproto.RawRecord) (err error)
|
||||
}
|
||||
|
||||
type NoOpAcceptorVerifier struct {
|
||||
}
|
||||
|
||||
func (n NoOpAcceptorVerifier) VerifyAcceptor(rec *consensusproto.RawRecord) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
type AclList interface {
|
||||
RWLocker
|
||||
Id() string
|
||||
|
@ -75,6 +65,7 @@ type aclList struct {
|
|||
keyStorage crypto.KeyStorage
|
||||
aclState *AclState
|
||||
storage Storage
|
||||
verifier recordverifier.AcceptorVerifier
|
||||
|
||||
sync.RWMutex
|
||||
}
|
||||
|
@ -84,10 +75,10 @@ type internalDeps struct {
|
|||
keyStorage crypto.KeyStorage
|
||||
stateBuilder *aclStateBuilder
|
||||
recordBuilder AclRecordBuilder
|
||||
acceptorVerifier AcceptorVerifier
|
||||
acceptorVerifier recordverifier.AcceptorVerifier
|
||||
}
|
||||
|
||||
func BuildAclListWithIdentity(acc *accountdata.AccountKeys, storage Storage, verifier AcceptorVerifier) (AclList, error) {
|
||||
func BuildAclListWithIdentity(acc *accountdata.AccountKeys, storage Storage, verifier recordverifier.AcceptorVerifier) (AclList, error) {
|
||||
keyStorage := crypto.NewKeyStorage()
|
||||
deps := internalDeps{
|
||||
storage: storage,
|
||||
|
@ -159,6 +150,7 @@ func build(deps internalDeps) (list AclList, err error) {
|
|||
stateBuilder: stateBuilder,
|
||||
recordBuilder: recBuilder,
|
||||
storage: storage,
|
||||
verifier: deps.acceptorVerifier,
|
||||
id: id,
|
||||
}
|
||||
stateBuilder.Init(id)
|
||||
|
|
|
@ -13,6 +13,7 @@ import (
|
|||
"github.com/anyproto/any-sync/commonspace/headsync/headstorage"
|
||||
"github.com/anyproto/any-sync/commonspace/object/accountdata"
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/aclrecordproto"
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/recordverifier"
|
||||
"github.com/anyproto/any-sync/consensus/consensusproto"
|
||||
"github.com/anyproto/any-sync/util/crypto"
|
||||
)
|
||||
|
@ -186,8 +187,8 @@ func TestAclList_InviteRevoke(t *testing.T) {
|
|||
// checking acl state
|
||||
require.True(t, ownerState().Permissions(ownerState().pubKey).IsOwner())
|
||||
require.True(t, ownerState().Permissions(accountState().pubKey).NoPermissions())
|
||||
require.Empty(t, ownerState().inviteKeys)
|
||||
require.Empty(t, accountState().inviteKeys)
|
||||
require.Empty(t, ownerState().invites)
|
||||
require.Empty(t, accountState().invites)
|
||||
}
|
||||
|
||||
func TestAclList_RequestDecline(t *testing.T) {
|
||||
|
@ -279,7 +280,7 @@ func TestAclList_FixAcceptPanic(t *testing.T) {
|
|||
fx := newFixture(t)
|
||||
fx.inviteAccount(t, AclPermissions(aclrecordproto.AclUserPermissions_Writer))
|
||||
|
||||
_, err := BuildAclListWithIdentity(fx.accountKeys, fx.ownerAcl.storage, NoOpAcceptorVerifier{})
|
||||
_, err := BuildAclListWithIdentity(fx.accountKeys, fx.ownerAcl.storage, recordverifier.NewValidateFull())
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
package list
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/anyproto/any-sync/commonspace/object/accountdata"
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/recordverifier"
|
||||
"github.com/anyproto/any-sync/consensus/consensusproto"
|
||||
"github.com/anyproto/any-sync/util/crypto"
|
||||
)
|
||||
|
@ -17,7 +20,7 @@ func newAclWithStoreProvider(root *consensusproto.RawRecordWithId, keys *account
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return BuildAclListWithIdentity(keys, storage, NoOpAcceptorVerifier{})
|
||||
return BuildAclListWithIdentity(keys, storage, recordverifier.NewValidateFull())
|
||||
}
|
||||
|
||||
func newDerivedAclWithStoreProvider(spaceId string, keys *accountdata.AccountKeys, metadata []byte, storeProvider StorageProvider) (AclList, error) {
|
||||
|
@ -43,11 +46,11 @@ func newInMemoryAclWithRoot(keys *accountdata.AccountKeys, root *consensusproto.
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return BuildAclListWithIdentity(keys, st, NoOpAcceptorVerifier{})
|
||||
return BuildAclListWithIdentity(keys, st, recordverifier.NewValidateFull())
|
||||
}
|
||||
|
||||
func buildDerivedRoot(spaceId string, keys *accountdata.AccountKeys, metadata []byte) (root *consensusproto.RawRecordWithId, err error) {
|
||||
builder := NewAclRecordBuilder("", crypto.NewKeyStorage(), keys, NoOpAcceptorVerifier{})
|
||||
builder := NewAclRecordBuilder("", crypto.NewKeyStorage(), keys, recordverifier.NewValidateFull())
|
||||
masterKey, _, err := crypto.GenerateRandomEd25519KeyPair()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -68,3 +71,30 @@ func buildDerivedRoot(spaceId string, keys *accountdata.AccountKeys, metadata []
|
|||
Metadata: metadata,
|
||||
})
|
||||
}
|
||||
|
||||
func NewTestAclStateWithUsers(numWriters, numReaders, numInvites int) *AclState {
|
||||
st := &AclState{
|
||||
keys: make(map[string]AclKeys),
|
||||
accountStates: make(map[string]AccountState),
|
||||
invites: make(map[string]Invite),
|
||||
requestRecords: make(map[string]RequestRecord),
|
||||
pendingRequests: make(map[string]string),
|
||||
keyStore: crypto.NewKeyStorage(),
|
||||
}
|
||||
for i := 0; i < numWriters; i++ {
|
||||
st.accountStates[fmt.Sprint("w", i)] = AccountState{
|
||||
Permissions: AclPermissionsWriter,
|
||||
Status: StatusActive,
|
||||
}
|
||||
}
|
||||
for i := 0; i < numReaders; i++ {
|
||||
st.accountStates[fmt.Sprint("r", i)] = AccountState{
|
||||
Permissions: AclPermissionsReader,
|
||||
Status: StatusActive,
|
||||
}
|
||||
}
|
||||
for i := 0; i < numInvites; i++ {
|
||||
st.invites[fmt.Sprint("r", i)] = Invite{}
|
||||
}
|
||||
return st
|
||||
}
|
||||
|
|
|
@ -24,6 +24,8 @@ type AclRecord struct {
|
|||
AcceptorTimestamp int64
|
||||
Data []byte
|
||||
Identity crypto.PubKey
|
||||
AcceptorIdentity crypto.PubKey
|
||||
AcceptorSignature []byte
|
||||
Model interface{}
|
||||
Signature []byte
|
||||
}
|
||||
|
@ -83,6 +85,10 @@ func (p AclPermissions) IsOwner() bool {
|
|||
return aclrecordproto.AclUserPermissions(p) == aclrecordproto.AclUserPermissions_Owner
|
||||
}
|
||||
|
||||
func (p AclPermissions) IsGuest() bool {
|
||||
return aclrecordproto.AclUserPermissions(p) == aclrecordproto.AclUserPermissions_Guest
|
||||
}
|
||||
|
||||
func (p AclPermissions) CanWrite() bool {
|
||||
switch aclrecordproto.AclUserPermissions(p) {
|
||||
case aclrecordproto.AclUserPermissions_Admin:
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
package list
|
||||
|
||||
import (
|
||||
"golang.org/x/exp/slices"
|
||||
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/aclrecordproto"
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/recordverifier"
|
||||
"github.com/anyproto/any-sync/util/crypto"
|
||||
)
|
||||
|
||||
|
@ -11,6 +14,8 @@ type ContentValidator interface {
|
|||
ValidatePermissionChanges(ch *aclrecordproto.AclAccountPermissionChanges, authorIdentity crypto.PubKey) (err error)
|
||||
ValidateAccountsAdd(ch *aclrecordproto.AclAccountsAdd, authorIdentity crypto.PubKey) (err error)
|
||||
ValidateInvite(ch *aclrecordproto.AclAccountInvite, authorIdentity crypto.PubKey) (err error)
|
||||
ValidateInviteJoin(ch *aclrecordproto.AclAccountInviteJoin, authorIdentity crypto.PubKey) (err error)
|
||||
ValidateInviteChange(ch *aclrecordproto.AclAccountInviteChange, authorIdentity crypto.PubKey) (err error)
|
||||
ValidateInviteRevoke(ch *aclrecordproto.AclAccountInviteRevoke, authorIdentity crypto.PubKey) (err error)
|
||||
ValidateRequestJoin(ch *aclrecordproto.AclAccountRequestJoin, authorIdentity crypto.PubKey) (err error)
|
||||
ValidateRequestAccept(ch *aclrecordproto.AclAccountRequestAccept, authorIdentity crypto.PubKey) (err error)
|
||||
|
@ -24,16 +29,21 @@ type ContentValidator interface {
|
|||
type contentValidator struct {
|
||||
keyStore crypto.KeyStorage
|
||||
aclState *AclState
|
||||
verifier recordverifier.AcceptorVerifier
|
||||
}
|
||||
|
||||
func newContentValidator(keyStore crypto.KeyStorage, aclState *AclState) ContentValidator {
|
||||
func newContentValidator(keyStore crypto.KeyStorage, aclState *AclState, verifier recordverifier.AcceptorVerifier) ContentValidator {
|
||||
return &contentValidator{
|
||||
keyStore: keyStore,
|
||||
aclState: aclState,
|
||||
verifier: verifier,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *contentValidator) ValidatePermissionChanges(ch *aclrecordproto.AclAccountPermissionChanges, authorIdentity crypto.PubKey) (err error) {
|
||||
if !c.verifier.ShouldValidate() {
|
||||
return nil
|
||||
}
|
||||
for _, ch := range ch.Changes {
|
||||
err := c.ValidatePermissionChange(ch, authorIdentity)
|
||||
if err != nil {
|
||||
|
@ -44,6 +54,9 @@ func (c *contentValidator) ValidatePermissionChanges(ch *aclrecordproto.AclAccou
|
|||
}
|
||||
|
||||
func (c *contentValidator) ValidateAccountsAdd(ch *aclrecordproto.AclAccountsAdd, authorIdentity crypto.PubKey) (err error) {
|
||||
if !c.verifier.ShouldValidate() {
|
||||
return nil
|
||||
}
|
||||
if !c.aclState.Permissions(authorIdentity).CanManageAccounts() {
|
||||
return ErrInsufficientPermissions
|
||||
}
|
||||
|
@ -66,7 +79,54 @@ func (c *contentValidator) ValidateAccountsAdd(ch *aclrecordproto.AclAccountsAdd
|
|||
return nil
|
||||
}
|
||||
|
||||
func (c *contentValidator) ValidateInviteJoin(ch *aclrecordproto.AclAccountInviteJoin, authorIdentity crypto.PubKey) (err error) {
|
||||
if !c.verifier.ShouldValidate() {
|
||||
return nil
|
||||
}
|
||||
if !c.aclState.Permissions(authorIdentity).NoPermissions() {
|
||||
return ErrInsufficientPermissions
|
||||
}
|
||||
invite, exists := c.aclState.invites[ch.InviteRecordId]
|
||||
if !exists {
|
||||
return ErrNoSuchInvite
|
||||
}
|
||||
if invite.Type != aclrecordproto.AclInviteType_AnyoneCanJoin {
|
||||
return ErrNoSuchInvite
|
||||
}
|
||||
if !c.aclState.Permissions(authorIdentity).NoPermissions() {
|
||||
return ErrInsufficientPermissions
|
||||
}
|
||||
inviteIdentity, err := c.keyStore.PubKeyFromProto(ch.Identity)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if !authorIdentity.Equals(inviteIdentity) {
|
||||
return ErrIncorrectIdentity
|
||||
}
|
||||
rawInviteIdentity, err := inviteIdentity.Raw()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ok, err := invite.Key.Verify(rawInviteIdentity, ch.InviteIdentitySignature)
|
||||
if err != nil {
|
||||
return ErrInvalidSignature
|
||||
}
|
||||
if !ok {
|
||||
return ErrInvalidSignature
|
||||
}
|
||||
if len(ch.Metadata) > MaxMetadataLen {
|
||||
return ErrMetadataTooLarge
|
||||
}
|
||||
if ch.EncryptedReadKey == nil {
|
||||
return ErrIncorrectReadKey
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *contentValidator) ValidateAclRecordContents(ch *AclRecord) (err error) {
|
||||
if !c.verifier.ShouldValidate() {
|
||||
return nil
|
||||
}
|
||||
if ch.PrevId != c.aclState.lastRecordId {
|
||||
return ErrIncorrectRecordSequence
|
||||
}
|
||||
|
@ -90,6 +150,8 @@ func (c *contentValidator) validateAclRecordContent(ch *aclrecordproto.AclConten
|
|||
return c.ValidateInviteRevoke(ch.GetInviteRevoke(), authorIdentity)
|
||||
case ch.GetRequestJoin() != nil:
|
||||
return c.ValidateRequestJoin(ch.GetRequestJoin(), authorIdentity)
|
||||
case ch.GetInviteJoin() != nil:
|
||||
return c.ValidateInviteJoin(ch.GetInviteJoin(), authorIdentity)
|
||||
case ch.GetRequestAccept() != nil:
|
||||
return c.ValidateRequestAccept(ch.GetRequestAccept(), authorIdentity)
|
||||
case ch.GetRequestDecline() != nil:
|
||||
|
@ -110,6 +172,9 @@ func (c *contentValidator) validateAclRecordContent(ch *aclrecordproto.AclConten
|
|||
}
|
||||
|
||||
func (c *contentValidator) ValidatePermissionChange(ch *aclrecordproto.AclAccountPermissionChange, authorIdentity crypto.PubKey) (err error) {
|
||||
if !c.verifier.ShouldValidate() {
|
||||
return nil
|
||||
}
|
||||
if !c.aclState.Permissions(authorIdentity).CanManageAccounts() {
|
||||
return ErrInsufficientPermissions
|
||||
}
|
||||
|
@ -149,18 +214,57 @@ func (c *contentValidator) ValidatePermissionChange(ch *aclrecordproto.AclAccoun
|
|||
}
|
||||
|
||||
func (c *contentValidator) ValidateInvite(ch *aclrecordproto.AclAccountInvite, authorIdentity crypto.PubKey) (err error) {
|
||||
if !c.verifier.ShouldValidate() {
|
||||
return nil
|
||||
}
|
||||
if !c.aclState.Permissions(authorIdentity).CanManageAccounts() {
|
||||
return ErrInsufficientPermissions
|
||||
}
|
||||
permissions := AclPermissions(ch.Permissions)
|
||||
if ch.InviteType == aclrecordproto.AclInviteType_AnyoneCanJoin {
|
||||
if permissions.IsOwner() || permissions.NoPermissions() || permissions.IsGuest() {
|
||||
return ErrInsufficientPermissions
|
||||
}
|
||||
if ch.EncryptedReadKey == nil {
|
||||
return ErrIncorrectReadKey
|
||||
}
|
||||
}
|
||||
_, err = c.keyStore.PubKeyFromProto(ch.InviteKey)
|
||||
return
|
||||
}
|
||||
|
||||
func (c *contentValidator) ValidateInviteRevoke(ch *aclrecordproto.AclAccountInviteRevoke, authorIdentity crypto.PubKey) (err error) {
|
||||
func (c *contentValidator) ValidateInviteChange(ch *aclrecordproto.AclAccountInviteChange, authorIdentity crypto.PubKey) (err error) {
|
||||
if !c.verifier.ShouldValidate() {
|
||||
return nil
|
||||
}
|
||||
if !c.aclState.Permissions(authorIdentity).CanManageAccounts() {
|
||||
return ErrInsufficientPermissions
|
||||
}
|
||||
_, exists := c.aclState.inviteKeys[ch.InviteRecordId]
|
||||
invite, exists := c.aclState.invites[ch.InviteRecordId]
|
||||
if !exists {
|
||||
return ErrNoSuchInvite
|
||||
}
|
||||
permissions := AclPermissions(ch.Permissions)
|
||||
if invite.Type != aclrecordproto.AclInviteType_AnyoneCanJoin {
|
||||
return ErrNoSuchInvite
|
||||
}
|
||||
if invite.Permissions == permissions {
|
||||
return ErrInsufficientPermissions
|
||||
}
|
||||
if permissions.IsOwner() || permissions.NoPermissions() || permissions.IsGuest() {
|
||||
return ErrInsufficientPermissions
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (c *contentValidator) ValidateInviteRevoke(ch *aclrecordproto.AclAccountInviteRevoke, authorIdentity crypto.PubKey) (err error) {
|
||||
if !c.verifier.ShouldValidate() {
|
||||
return nil
|
||||
}
|
||||
if !c.aclState.Permissions(authorIdentity).CanManageAccounts() {
|
||||
return ErrInsufficientPermissions
|
||||
}
|
||||
_, exists := c.aclState.invites[ch.InviteRecordId]
|
||||
if !exists {
|
||||
return ErrNoSuchInvite
|
||||
}
|
||||
|
@ -168,7 +272,10 @@ func (c *contentValidator) ValidateInviteRevoke(ch *aclrecordproto.AclAccountInv
|
|||
}
|
||||
|
||||
func (c *contentValidator) ValidateRequestJoin(ch *aclrecordproto.AclAccountRequestJoin, authorIdentity crypto.PubKey) (err error) {
|
||||
inviteKey, exists := c.aclState.inviteKeys[ch.InviteRecordId]
|
||||
if !c.verifier.ShouldValidate() {
|
||||
return nil
|
||||
}
|
||||
invite, exists := c.aclState.invites[ch.InviteRecordId]
|
||||
if !exists {
|
||||
return ErrNoSuchInvite
|
||||
}
|
||||
|
@ -189,7 +296,7 @@ func (c *contentValidator) ValidateRequestJoin(ch *aclrecordproto.AclAccountRequ
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ok, err := inviteKey.Verify(rawInviteIdentity, ch.InviteIdentitySignature)
|
||||
ok, err := invite.Key.Verify(rawInviteIdentity, ch.InviteIdentitySignature)
|
||||
if err != nil {
|
||||
return ErrInvalidSignature
|
||||
}
|
||||
|
@ -203,6 +310,9 @@ func (c *contentValidator) ValidateRequestJoin(ch *aclrecordproto.AclAccountRequ
|
|||
}
|
||||
|
||||
func (c *contentValidator) ValidateRequestAccept(ch *aclrecordproto.AclAccountRequestAccept, authorIdentity crypto.PubKey) (err error) {
|
||||
if !c.verifier.ShouldValidate() {
|
||||
return nil
|
||||
}
|
||||
if !c.aclState.Permissions(authorIdentity).CanManageAccounts() {
|
||||
return ErrInsufficientPermissions
|
||||
}
|
||||
|
@ -224,6 +334,9 @@ func (c *contentValidator) ValidateRequestAccept(ch *aclrecordproto.AclAccountRe
|
|||
}
|
||||
|
||||
func (c *contentValidator) ValidateRequestDecline(ch *aclrecordproto.AclAccountRequestDecline, authorIdentity crypto.PubKey) (err error) {
|
||||
if !c.verifier.ShouldValidate() {
|
||||
return nil
|
||||
}
|
||||
if !c.aclState.Permissions(authorIdentity).CanManageAccounts() {
|
||||
return ErrInsufficientPermissions
|
||||
}
|
||||
|
@ -235,6 +348,9 @@ func (c *contentValidator) ValidateRequestDecline(ch *aclrecordproto.AclAccountR
|
|||
}
|
||||
|
||||
func (c *contentValidator) ValidateRequestCancel(ch *aclrecordproto.AclAccountRequestCancel, authorIdentity crypto.PubKey) (err error) {
|
||||
if !c.verifier.ShouldValidate() {
|
||||
return nil
|
||||
}
|
||||
rec, exists := c.aclState.requestRecords[ch.RecordId]
|
||||
if !exists {
|
||||
return ErrNoSuchRequest
|
||||
|
@ -246,6 +362,9 @@ func (c *contentValidator) ValidateRequestCancel(ch *aclrecordproto.AclAccountRe
|
|||
}
|
||||
|
||||
func (c *contentValidator) ValidateAccountRemove(ch *aclrecordproto.AclAccountRemove, authorIdentity crypto.PubKey) (err error) {
|
||||
if !c.verifier.ShouldValidate() {
|
||||
return nil
|
||||
}
|
||||
if !c.aclState.Permissions(authorIdentity).CanManageAccounts() {
|
||||
return ErrInsufficientPermissions
|
||||
}
|
||||
|
@ -275,6 +394,9 @@ func (c *contentValidator) ValidateAccountRemove(ch *aclrecordproto.AclAccountRe
|
|||
}
|
||||
|
||||
func (c *contentValidator) ValidateRequestRemove(ch *aclrecordproto.AclAccountRequestRemove, authorIdentity crypto.PubKey) (err error) {
|
||||
if !c.verifier.ShouldValidate() {
|
||||
return nil
|
||||
}
|
||||
if c.aclState.Permissions(authorIdentity).NoPermissions() {
|
||||
return ErrInsufficientPermissions
|
||||
}
|
||||
|
@ -288,10 +410,16 @@ func (c *contentValidator) ValidateRequestRemove(ch *aclrecordproto.AclAccountRe
|
|||
}
|
||||
|
||||
func (c *contentValidator) ValidateReadKeyChange(ch *aclrecordproto.AclReadKeyChange, authorIdentity crypto.PubKey) (err error) {
|
||||
if !c.verifier.ShouldValidate() {
|
||||
return nil
|
||||
}
|
||||
return c.validateReadKeyChange(ch, nil)
|
||||
}
|
||||
|
||||
func (c *contentValidator) validateReadKeyChange(ch *aclrecordproto.AclReadKeyChange, removedUsers map[string]struct{}) (err error) {
|
||||
if !c.verifier.ShouldValidate() {
|
||||
return nil
|
||||
}
|
||||
_, err = c.keyStore.PubKeyFromProto(ch.MetadataPubKey)
|
||||
if err != nil {
|
||||
return ErrNoMetadataKey
|
||||
|
@ -299,23 +427,55 @@ func (c *contentValidator) validateReadKeyChange(ch *aclrecordproto.AclReadKeyCh
|
|||
if ch.EncryptedMetadataPrivKey == nil || ch.EncryptedOldReadKey == nil {
|
||||
return ErrIncorrectReadKey
|
||||
}
|
||||
var (
|
||||
activeUsers []string
|
||||
updatedInvites []string
|
||||
activeInvites []string
|
||||
updatedUsers []string
|
||||
)
|
||||
for _, accState := range c.aclState.accountStates {
|
||||
if accState.Permissions.NoPermissions() {
|
||||
continue
|
||||
}
|
||||
pubKey := mapKeyFromPubKey(accState.PubKey)
|
||||
if _, exists := removedUsers[pubKey]; exists {
|
||||
continue
|
||||
}
|
||||
activeUsers = append(activeUsers, pubKey)
|
||||
}
|
||||
for _, invite := range c.aclState.invites {
|
||||
if invite.Type == aclrecordproto.AclInviteType_AnyoneCanJoin {
|
||||
activeInvites = append(activeInvites, mapKeyFromPubKey(invite.Key))
|
||||
}
|
||||
}
|
||||
for _, encKeys := range ch.AccountKeys {
|
||||
identity, err := c.keyStore.PubKeyFromProto(encKeys.Identity)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
idKey := mapKeyFromPubKey(identity)
|
||||
_, exists := c.aclState.accountStates[idKey]
|
||||
if !exists {
|
||||
return ErrNoSuchAccount
|
||||
}
|
||||
if removedUsers == nil {
|
||||
continue
|
||||
}
|
||||
_, exists = removedUsers[idKey]
|
||||
if exists {
|
||||
return ErrIncorrectNumberOfAccounts
|
||||
updatedUsers = append(updatedUsers, idKey)
|
||||
}
|
||||
for _, invKeys := range ch.InviteKeys {
|
||||
identity, err := c.keyStore.PubKeyFromProto(invKeys.Identity)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
idKey := mapKeyFromPubKey(identity)
|
||||
updatedInvites = append(updatedInvites, idKey)
|
||||
}
|
||||
if len(activeUsers) != len(updatedUsers) {
|
||||
return ErrIncorrectNumberOfAccounts
|
||||
}
|
||||
if len(activeInvites) != len(updatedInvites) {
|
||||
return ErrIncorrectNumberOfAccounts
|
||||
}
|
||||
slices.Sort(updatedUsers)
|
||||
slices.Sort(updatedInvites)
|
||||
slices.Sort(activeUsers)
|
||||
slices.Sort(activeInvites)
|
||||
if !slices.Equal(activeUsers, updatedUsers) || !slices.Equal(activeInvites, updatedInvites) {
|
||||
return ErrIncorrectNumberOfAccounts
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
58
commonspace/object/acl/recordverifier/recordverifier.go
Normal file
58
commonspace/object/acl/recordverifier/recordverifier.go
Normal file
|
@ -0,0 +1,58 @@
|
|||
package recordverifier
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/anyproto/any-sync/app"
|
||||
"github.com/anyproto/any-sync/consensus/consensusproto"
|
||||
"github.com/anyproto/any-sync/util/crypto"
|
||||
)
|
||||
|
||||
const CName = "common.acl.recordverifier"
|
||||
|
||||
type AcceptorVerifier interface {
|
||||
VerifyAcceptor(rec *consensusproto.RawRecord) (err error)
|
||||
ShouldValidate() bool
|
||||
}
|
||||
|
||||
type RecordVerifier interface {
|
||||
app.Component
|
||||
AcceptorVerifier
|
||||
}
|
||||
|
||||
func New() RecordVerifier {
|
||||
return &recordVerifier{
|
||||
store: crypto.NewKeyStorage(),
|
||||
}
|
||||
}
|
||||
|
||||
type recordVerifier struct {
|
||||
store crypto.KeyStorage
|
||||
}
|
||||
|
||||
func (r *recordVerifier) Init(a *app.App) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (r *recordVerifier) Name() (name string) {
|
||||
return CName
|
||||
}
|
||||
|
||||
func (r *recordVerifier) VerifyAcceptor(rec *consensusproto.RawRecord) (err error) {
|
||||
identity, err := r.store.PubKeyFromProto(rec.AcceptorIdentity)
|
||||
if err != nil {
|
||||
identity, err = crypto.UnmarshalEd25519PublicKey(rec.AcceptorIdentity)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get acceptor identity: %w", err)
|
||||
}
|
||||
}
|
||||
verified, err := identity.Verify(rec.Payload, rec.AcceptorSignature)
|
||||
if !verified || err != nil {
|
||||
return fmt.Errorf("failed to verify acceptor: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *recordVerifier) ShouldValidate() bool {
|
||||
return false
|
||||
}
|
106
commonspace/object/acl/recordverifier/recordverifier_test.go
Normal file
106
commonspace/object/acl/recordverifier/recordverifier_test.go
Normal file
|
@ -0,0 +1,106 @@
|
|||
package recordverifier
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/anyproto/any-sync/app"
|
||||
"github.com/anyproto/any-sync/consensus/consensusproto"
|
||||
"github.com/anyproto/any-sync/testutil/accounttest"
|
||||
"github.com/anyproto/any-sync/util/crypto"
|
||||
)
|
||||
|
||||
type fixture struct {
|
||||
*recordVerifier
|
||||
app *app.App
|
||||
networkPrivKey crypto.PrivKey
|
||||
}
|
||||
|
||||
func newFixture(t *testing.T) *fixture {
|
||||
accService := &accounttest.AccountTestService{}
|
||||
a := &app.App{}
|
||||
verifier := New().(*recordVerifier)
|
||||
a.Register(accService).
|
||||
Register(verifier)
|
||||
require.NoError(t, a.Start(context.Background()))
|
||||
return &fixture{
|
||||
recordVerifier: verifier,
|
||||
app: a,
|
||||
networkPrivKey: accService.Account().SignKey,
|
||||
}
|
||||
}
|
||||
|
||||
func TestRecordVerifier_VerifyAcceptor(t *testing.T) {
|
||||
fx := newFixture(t)
|
||||
identity, err := fx.networkPrivKey.GetPublic().Marshall()
|
||||
require.NoError(t, err)
|
||||
testPayload := []byte("test payload")
|
||||
acceptorSignature, err := fx.networkPrivKey.Sign(testPayload)
|
||||
require.NoError(t, err)
|
||||
rawRecord := &consensusproto.RawRecord{
|
||||
AcceptorIdentity: identity,
|
||||
Payload: testPayload,
|
||||
AcceptorSignature: acceptorSignature,
|
||||
}
|
||||
err = fx.VerifyAcceptor(rawRecord)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestRecordVerifier_VerifyAcceptor_InvalidSignature(t *testing.T) {
|
||||
fx := newFixture(t)
|
||||
identity, err := fx.networkPrivKey.GetPublic().Marshall()
|
||||
require.NoError(t, err)
|
||||
testPayload := []byte("test payload")
|
||||
rawRecord := &consensusproto.RawRecord{
|
||||
AcceptorIdentity: identity,
|
||||
Payload: testPayload,
|
||||
AcceptorSignature: []byte("invalid signature"),
|
||||
}
|
||||
err = fx.VerifyAcceptor(rawRecord)
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestRecordVerifier_VerifyAcceptor_ModifiedPayload(t *testing.T) {
|
||||
fx := newFixture(t)
|
||||
identity, err := fx.networkPrivKey.GetPublic().Marshall()
|
||||
require.NoError(t, err)
|
||||
testPayload := []byte("test payload")
|
||||
acceptorSignature, err := fx.networkPrivKey.Sign(testPayload)
|
||||
require.NoError(t, err)
|
||||
rawRecord := &consensusproto.RawRecord{
|
||||
AcceptorIdentity: identity,
|
||||
Payload: []byte("modified payload"),
|
||||
AcceptorSignature: acceptorSignature,
|
||||
}
|
||||
err = fx.VerifyAcceptor(rawRecord)
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestRecordVerifier_VerifyAcceptor_InvalidIdentity(t *testing.T) {
|
||||
fx := newFixture(t)
|
||||
testPayload := []byte("test payload")
|
||||
acceptorSignature, err := fx.networkPrivKey.Sign(testPayload)
|
||||
require.NoError(t, err)
|
||||
rawRecord := &consensusproto.RawRecord{
|
||||
AcceptorIdentity: []byte("invalid identity"),
|
||||
Payload: testPayload,
|
||||
AcceptorSignature: acceptorSignature,
|
||||
}
|
||||
err = fx.VerifyAcceptor(rawRecord)
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestRecordVerifier_VerifyAcceptor_EmptySignature(t *testing.T) {
|
||||
fx := newFixture(t)
|
||||
identity, err := fx.networkPrivKey.GetPublic().Marshall()
|
||||
require.NoError(t, err)
|
||||
rawRecord := &consensusproto.RawRecord{
|
||||
AcceptorIdentity: identity,
|
||||
Payload: []byte("test payload"),
|
||||
AcceptorSignature: nil,
|
||||
}
|
||||
err = fx.VerifyAcceptor(rawRecord)
|
||||
require.Error(t, err)
|
||||
}
|
28
commonspace/object/acl/recordverifier/validatefull.go
Normal file
28
commonspace/object/acl/recordverifier/validatefull.go
Normal file
|
@ -0,0 +1,28 @@
|
|||
package recordverifier
|
||||
|
||||
import (
|
||||
"github.com/anyproto/any-sync/app"
|
||||
"github.com/anyproto/any-sync/consensus/consensusproto"
|
||||
)
|
||||
|
||||
type ValidateFull struct{}
|
||||
|
||||
func NewValidateFull() RecordVerifier {
|
||||
return &ValidateFull{}
|
||||
}
|
||||
|
||||
func (a *ValidateFull) Init(_ *app.App) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *ValidateFull) Name() string {
|
||||
return CName
|
||||
}
|
||||
|
||||
func (a *ValidateFull) VerifyAcceptor(_ *consensusproto.RawRecord) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *ValidateFull) ShouldValidate() bool {
|
||||
return true
|
||||
}
|
|
@ -11,6 +11,7 @@ import (
|
|||
"github.com/anyproto/any-sync/app"
|
||||
"github.com/anyproto/any-sync/app/logger"
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/list"
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/recordverifier"
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/syncacl/headupdater"
|
||||
"github.com/anyproto/any-sync/commonspace/spacestorage"
|
||||
"github.com/anyproto/any-sync/commonspace/sync"
|
||||
|
@ -67,7 +68,8 @@ func (s *syncAcl) Init(a *app.App) (err error) {
|
|||
return err
|
||||
}
|
||||
acc := a.MustComponent(accountservice.CName).(accountservice.Service)
|
||||
s.AclList, err = list.BuildAclListWithIdentity(acc.Account(), aclStorage, list.NoOpAcceptorVerifier{})
|
||||
verifier := a.MustComponent(recordverifier.CName).(recordverifier.RecordVerifier)
|
||||
s.AclList, err = list.BuildAclListWithIdentity(acc.Account(), aclStorage, verifier)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ import (
|
|||
|
||||
"github.com/anyproto/any-sync/commonspace/object/accountdata"
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/list"
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/recordverifier"
|
||||
"github.com/anyproto/any-sync/commonspace/object/keyvalue/keyvaluestorage"
|
||||
"github.com/anyproto/any-sync/commonspace/object/keyvalue/keyvaluestorage/innerstorage"
|
||||
"github.com/anyproto/any-sync/commonspace/spacepayloads"
|
||||
|
@ -280,7 +281,7 @@ func newFixture(t *testing.T, keys *accountdata.AccountKeys, spacePayload spaces
|
|||
require.NoError(t, err)
|
||||
aclStorage, err := storage.AclStorage()
|
||||
require.NoError(t, err)
|
||||
aclList, err := list.BuildAclListWithIdentity(keys, aclStorage, list.NoOpAcceptorVerifier{})
|
||||
aclList, err := list.BuildAclListWithIdentity(keys, aclStorage, recordverifier.NewValidateFull())
|
||||
require.NoError(t, err)
|
||||
storageId := "kv.storage"
|
||||
rpcHandler := rpctest.NewTestServer()
|
||||
|
|
|
@ -19,6 +19,7 @@ import (
|
|||
"github.com/anyproto/any-sync/commonspace/headsync/headstorage"
|
||||
"github.com/anyproto/any-sync/commonspace/object/accountdata"
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/list"
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/recordverifier"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
|
||||
)
|
||||
|
@ -390,7 +391,7 @@ func TestObjectTree(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
prevId = rec.Id
|
||||
}
|
||||
beforeAcl, err := list.BuildAclListWithIdentity(account.Keys, beforeStorage, list.NoOpAcceptorVerifier{})
|
||||
beforeAcl, err := list.BuildAclListWithIdentity(account.Keys, beforeStorage, recordverifier.NewValidateFull())
|
||||
require.NoError(t, err)
|
||||
err = exec.Execute("a.invite::invId")
|
||||
require.NoError(t, err)
|
||||
|
@ -462,7 +463,7 @@ func TestObjectTree(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
storage, err := list.NewInMemoryStorage(prevAclRecs[0].Id, prevAclRecs)
|
||||
require.NoError(t, err)
|
||||
acl, err := list.BuildAclListWithIdentity(bAccount.Keys, storage, list.NoOpAcceptorVerifier{})
|
||||
acl, err := list.BuildAclListWithIdentity(bAccount.Keys, storage, recordverifier.NewValidateFull())
|
||||
require.NoError(t, err)
|
||||
// creating tree with old storage which doesn't have a new invite record
|
||||
bTree, err := BuildKeyFilterableObjectTree(bStore, acl)
|
||||
|
|
|
@ -12,6 +12,7 @@ import (
|
|||
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/aclrecordproto"
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/list"
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/recordverifier"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
||||
"github.com/anyproto/any-sync/commonspace/spacestorage"
|
||||
|
@ -96,7 +97,7 @@ func StoragePayloadForSpaceCreate(payload SpaceCreatePayload) (storagePayload sp
|
|||
|
||||
// building acl root
|
||||
keyStorage := crypto.NewKeyStorage()
|
||||
aclBuilder := list.NewAclRecordBuilder("", keyStorage, nil, list.NoOpAcceptorVerifier{})
|
||||
aclBuilder := list.NewAclRecordBuilder("", keyStorage, nil, recordverifier.NewValidateFull())
|
||||
aclRoot, err := aclBuilder.BuildRoot(list.RootContent{
|
||||
PrivKey: payload.SigningKey,
|
||||
MasterKey: payload.MasterKey,
|
||||
|
@ -187,7 +188,7 @@ func StoragePayloadForSpaceDerive(payload SpaceDerivePayload) (storagePayload sp
|
|||
|
||||
// building acl root
|
||||
keyStorage := crypto.NewKeyStorage()
|
||||
aclBuilder := list.NewAclRecordBuilder("", keyStorage, nil, list.NoOpAcceptorVerifier{})
|
||||
aclBuilder := list.NewAclRecordBuilder("", keyStorage, nil, recordverifier.NewValidateFull())
|
||||
aclRoot, err := aclBuilder.BuildRoot(list.RootContent{
|
||||
PrivKey: payload.SigningKey,
|
||||
MasterKey: payload.MasterKey,
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
|
||||
"github.com/anyproto/any-sync/accountservice"
|
||||
"github.com/anyproto/any-sync/app"
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/recordverifier"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/synctree"
|
||||
"github.com/anyproto/any-sync/commonspace/object/treemanager"
|
||||
|
@ -127,8 +128,9 @@ func (r *RpcServer) getSpace(ctx context.Context, spaceId string) (sp Space, err
|
|||
sp, ok := r.spaces[spaceId]
|
||||
if !ok {
|
||||
sp, err = r.spaceService.NewSpace(ctx, spaceId, Deps{
|
||||
TreeSyncer: NewTreeSyncer(spaceId),
|
||||
SyncStatus: syncstatus.NewNoOpSyncStatus(),
|
||||
TreeSyncer: NewTreeSyncer(spaceId),
|
||||
SyncStatus: syncstatus.NewNoOpSyncStatus(),
|
||||
recordVerifier: recordverifier.NewValidateFull(),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
|
@ -13,6 +13,7 @@ import (
|
|||
|
||||
"github.com/anyproto/any-sync/commonspace/acl/aclclient"
|
||||
"github.com/anyproto/any-sync/commonspace/deletionmanager"
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/recordverifier"
|
||||
"github.com/anyproto/any-sync/commonspace/object/keyvalue"
|
||||
"github.com/anyproto/any-sync/commonspace/object/keyvalue/keyvaluestorage"
|
||||
"github.com/anyproto/any-sync/commonspace/object/treesyncer"
|
||||
|
@ -72,6 +73,7 @@ type Deps struct {
|
|||
SyncStatus syncstatus.StatusUpdater
|
||||
TreeSyncer treesyncer.TreeSyncer
|
||||
AccountService accountservice.Service
|
||||
recordVerifier recordverifier.RecordVerifier
|
||||
Indexer keyvaluestorage.Indexer
|
||||
}
|
||||
|
||||
|
@ -188,8 +190,13 @@ func (s *spaceService) NewSpace(ctx context.Context, id string, deps Deps) (Spac
|
|||
if deps.Indexer != nil {
|
||||
keyValueIndexer = deps.Indexer
|
||||
}
|
||||
recordVerifier := recordverifier.New()
|
||||
if deps.recordVerifier != nil {
|
||||
recordVerifier = deps.recordVerifier
|
||||
}
|
||||
spaceApp.Register(state).
|
||||
Register(deps.SyncStatus).
|
||||
Register(recordVerifier).
|
||||
Register(peerManager).
|
||||
Register(st).
|
||||
Register(keyValueIndexer).
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
"github.com/anyproto/any-sync/commonspace/headsync/headstorage"
|
||||
"github.com/anyproto/any-sync/commonspace/object/accountdata"
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/list"
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/recordverifier"
|
||||
"github.com/anyproto/any-sync/commonspace/spacestorage/oldstorage"
|
||||
"github.com/anyproto/any-sync/consensus/consensusproto"
|
||||
)
|
||||
|
@ -31,7 +32,7 @@ func migrateAclList(ctx context.Context, oldStorage oldstorage.ListStorage, head
|
|||
if err != nil {
|
||||
return nil, fmt.Errorf("migration: failed to generate keys: %w", err)
|
||||
}
|
||||
aclList, err := list.BuildAclListWithIdentity(keys, aclStorage, &list.NoOpAcceptorVerifier{})
|
||||
aclList, err := list.BuildAclListWithIdentity(keys, aclStorage, recordverifier.New())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("migration: failed to build acl list: %w", err)
|
||||
}
|
||||
|
|
|
@ -10,7 +10,6 @@ import (
|
|||
"time"
|
||||
|
||||
anystore "github.com/anyproto/any-store"
|
||||
"github.com/anyproto/go-chash"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/zap"
|
||||
"storj.io/drpc"
|
||||
|
@ -44,114 +43,12 @@ import (
|
|||
"github.com/anyproto/any-sync/net/streampool/streamhandler"
|
||||
"github.com/anyproto/any-sync/node/nodeclient"
|
||||
"github.com/anyproto/any-sync/nodeconf"
|
||||
"github.com/anyproto/any-sync/nodeconf/testconf"
|
||||
"github.com/anyproto/any-sync/testutil/accounttest"
|
||||
"github.com/anyproto/any-sync/util/crypto"
|
||||
"github.com/anyproto/any-sync/util/syncqueues"
|
||||
)
|
||||
|
||||
type mockConf struct {
|
||||
id string
|
||||
networkId string
|
||||
configuration nodeconf.Configuration
|
||||
}
|
||||
|
||||
func (m *mockConf) NetworkCompatibilityStatus() nodeconf.NetworkCompatibilityStatus {
|
||||
return nodeconf.NetworkCompatibilityStatusOk
|
||||
}
|
||||
|
||||
func (m *mockConf) Init(a *app.App) (err error) {
|
||||
accountKeys := a.MustComponent(accountService.CName).(accountService.Service).Account()
|
||||
networkId := accountKeys.SignKey.GetPublic().Network()
|
||||
node := nodeconf.Node{
|
||||
PeerId: accountKeys.PeerId,
|
||||
Addresses: []string{"127.0.0.1:4430"},
|
||||
Types: []nodeconf.NodeType{nodeconf.NodeTypeTree},
|
||||
}
|
||||
m.id = networkId
|
||||
m.networkId = networkId
|
||||
m.configuration = nodeconf.Configuration{
|
||||
Id: networkId,
|
||||
NetworkId: networkId,
|
||||
Nodes: []nodeconf.Node{node},
|
||||
CreationTime: time.Now(),
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mockConf) Name() (name string) {
|
||||
return nodeconf.CName
|
||||
}
|
||||
|
||||
func (m *mockConf) Run(ctx context.Context) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mockConf) Close(ctx context.Context) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mockConf) Id() string {
|
||||
return m.id
|
||||
}
|
||||
|
||||
func (m *mockConf) Configuration() nodeconf.Configuration {
|
||||
return m.configuration
|
||||
}
|
||||
|
||||
func (m *mockConf) NodeIds(spaceId string) []string {
|
||||
var nodeIds []string
|
||||
for _, node := range m.configuration.Nodes {
|
||||
nodeIds = append(nodeIds, node.PeerId)
|
||||
}
|
||||
return nodeIds
|
||||
}
|
||||
|
||||
func (m *mockConf) IsResponsible(spaceId string) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (m *mockConf) FilePeers() []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mockConf) ConsensusPeers() []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mockConf) CoordinatorPeers() []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mockConf) NamingNodePeers() []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mockConf) PaymentProcessingNodePeers() []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mockConf) PeerAddresses(peerId string) (addrs []string, ok bool) {
|
||||
if peerId == m.configuration.Nodes[0].PeerId {
|
||||
return m.configuration.Nodes[0].Addresses, true
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func (m *mockConf) CHash() chash.CHash {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mockConf) Partition(spaceId string) (part int) {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *mockConf) NodeTypes(nodeId string) []nodeconf.NodeType {
|
||||
if nodeId == m.configuration.Nodes[0].PeerId {
|
||||
return m.configuration.Nodes[0].Types
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var _ nodeclient.NodeClient = (*mockNodeClient)(nil)
|
||||
|
||||
type mockNodeClient struct {
|
||||
|
@ -654,7 +551,7 @@ func newFixture(t *testing.T) *spaceFixture {
|
|||
app: &app.App{},
|
||||
config: &mockConfig{},
|
||||
account: &accounttest.AccountTestService{},
|
||||
configurationService: &mockConf{},
|
||||
configurationService: &testconf.StubConf{},
|
||||
streamOpener: newStreamOpener("spaceId"),
|
||||
peerManagerProvider: &testPeerManagerProvider{},
|
||||
storageProvider: &spaceStorageProvider{rootPath: t.TempDir()},
|
||||
|
@ -699,7 +596,7 @@ func newPeerFixture(t *testing.T, spaceId string, keys *accountdata.AccountKeys,
|
|||
app: &app.App{},
|
||||
config: &mockConfig{},
|
||||
account: accounttest.NewWithAcc(keys),
|
||||
configurationService: &mockConf{},
|
||||
configurationService: &testconf.StubConf{},
|
||||
storageProvider: provider,
|
||||
streamOpener: newStreamOpener(spaceId),
|
||||
peerManagerProvider: &testPeerManagerProvider{},
|
||||
|
|
12
go.mod
12
go.mod
|
@ -6,7 +6,7 @@ toolchain go1.24.1
|
|||
|
||||
require (
|
||||
filippo.io/edwards25519 v1.1.0
|
||||
github.com/anyproto/any-store v0.1.11
|
||||
github.com/anyproto/any-store v0.2.0
|
||||
github.com/anyproto/go-chash v0.1.0
|
||||
github.com/anyproto/go-slip10 v1.0.0
|
||||
github.com/anyproto/go-slip21 v1.0.0
|
||||
|
@ -38,7 +38,7 @@ require (
|
|||
go.uber.org/mock v0.5.2
|
||||
go.uber.org/zap v1.27.0
|
||||
golang.org/x/crypto v0.38.0
|
||||
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394
|
||||
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0
|
||||
golang.org/x/net v0.40.0
|
||||
golang.org/x/sys v0.33.0
|
||||
golang.org/x/time v0.11.0
|
||||
|
@ -65,7 +65,7 @@ require (
|
|||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
|
||||
github.com/google/pprof v0.0.0-20250208200701-d0013a598941 // indirect
|
||||
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e // indirect
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
|
||||
github.com/ipfs/bbloom v0.0.4 // indirect
|
||||
github.com/ipfs/go-bitfield v1.1.0 // indirect
|
||||
|
@ -112,9 +112,9 @@ require (
|
|||
golang.org/x/text v0.25.0 // indirect
|
||||
google.golang.org/protobuf v1.36.5 // indirect
|
||||
lukechampine.com/blake3 v1.4.0 // indirect
|
||||
modernc.org/libc v1.61.13 // indirect
|
||||
modernc.org/libc v1.65.0 // indirect
|
||||
modernc.org/mathutil v1.7.1 // indirect
|
||||
modernc.org/memory v1.8.2 // indirect
|
||||
modernc.org/sqlite v1.36.1 // indirect
|
||||
modernc.org/memory v1.10.0 // indirect
|
||||
modernc.org/sqlite v1.37.0 // indirect
|
||||
zombiezen.com/go/sqlite v1.4.0 // indirect
|
||||
)
|
||||
|
|
40
go.sum
40
go.sum
|
@ -6,8 +6,8 @@ github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAE
|
|||
github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII=
|
||||
github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b h1:mimo19zliBX/vSQ6PWWSL9lK8qwHozUj03+zLoEB8O0=
|
||||
github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b/go.mod h1:fvzegU4vN3H1qMT+8wDmzjAcDONcgo2/SZ/TyfdUOFs=
|
||||
github.com/anyproto/any-store v0.1.11 h1:xoaDVF8FJEI6V37fMw/R3ptBCLHj0kYiImwWxC1Ryu8=
|
||||
github.com/anyproto/any-store v0.1.11/go.mod h1:X3UkQ2zLATYNED3gFhY2VcdfDOeJvpEQ0PmDO90A9Yo=
|
||||
github.com/anyproto/any-store v0.2.0 h1:M8Eb0dxuEk62lIGZ3k1nADlaPQzmo6ilWLCCcY9WX10=
|
||||
github.com/anyproto/any-store v0.2.0/go.mod h1:N59OGYe/uXRNpr6ytfbBpbC+1viDgSbsVNXevOMxJAM=
|
||||
github.com/anyproto/go-chash v0.1.0 h1:I9meTPjXFRfXZHRJzjOHC/XF7Q5vzysKkiT/grsogXY=
|
||||
github.com/anyproto/go-chash v0.1.0/go.mod h1:0UjNQi3PDazP0fINpFYu6VKhuna+W/V+1vpXHAfNgLY=
|
||||
github.com/anyproto/go-slip10 v1.0.0 h1:uAEtSuudR3jJBOfkOXf3bErxVoxbuKwdoJN55M1i6IA=
|
||||
|
@ -100,8 +100,8 @@ github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
|||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8=
|
||||
github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo=
|
||||
github.com/google/pprof v0.0.0-20250208200701-d0013a598941 h1:43XjGa6toxLpeksjcxs1jIoIyr+vUfOqY2c6HB4bpoc=
|
||||
github.com/google/pprof v0.0.0-20250208200701-d0013a598941/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
|
||||
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs=
|
||||
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
|
@ -379,8 +379,8 @@ golang.org/x/crypto v0.0.0-20200602180216-279210d13fed/go.mod h1:LzIPMQfyMNhhGPh
|
|||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
|
||||
golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
|
||||
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw=
|
||||
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM=
|
||||
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 h1:R84qjqJb5nVJMxqWYb3np9L5ZsaDtB+a39EqjV0JSUM=
|
||||
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:S9Xr4PYopiDyqSyp5NjCrhFrqg6A5zA2E/iPHPhqnS8=
|
||||
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.21.0 h1:c5qV36ajHpdj4Qi0GnE0jUc/yuo33OLFaa0d+crTD5s=
|
||||
golang.org/x/image v0.21.0/go.mod h1:vUbsLavqK/W303ZroQQVKQ+Af3Yl6Uz1Ppu5J/cLz78=
|
||||
|
@ -457,26 +457,26 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
|||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
lukechampine.com/blake3 v1.4.0 h1:xDbKOZCVbnZsfzM6mHSYcGRHZ3YrLDzqz8XnV4uaD5w=
|
||||
lukechampine.com/blake3 v1.4.0/go.mod h1:MQJNQCTnR+kwOP/JEZSxj3MaQjp80FOFSNMMHXcSeX0=
|
||||
modernc.org/cc/v4 v4.24.4 h1:TFkx1s6dCkQpd6dKurBNmpo+G8Zl4Sq/ztJ+2+DEsh0=
|
||||
modernc.org/cc/v4 v4.24.4/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
|
||||
modernc.org/ccgo/v4 v4.23.16 h1:Z2N+kk38b7SfySC1ZkpGLN2vthNJP1+ZzGZIlH7uBxo=
|
||||
modernc.org/ccgo/v4 v4.23.16/go.mod h1:nNma8goMTY7aQZQNTyN9AIoJfxav4nvTnvKThAeMDdo=
|
||||
modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE=
|
||||
modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ=
|
||||
modernc.org/gc/v2 v2.6.3 h1:aJVhcqAte49LF+mGveZ5KPlsp4tdGdAOT4sipJXADjw=
|
||||
modernc.org/gc/v2 v2.6.3/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
|
||||
modernc.org/libc v1.61.13 h1:3LRd6ZO1ezsFiX1y+bHd1ipyEHIJKvuprv0sLTBwLW8=
|
||||
modernc.org/libc v1.61.13/go.mod h1:8F/uJWL/3nNil0Lgt1Dpz+GgkApWh04N3el3hxJcA6E=
|
||||
modernc.org/cc/v4 v4.26.0 h1:QMYvbVduUGH0rrO+5mqF/PSPPRZNpRtg2CLELy7vUpA=
|
||||
modernc.org/cc/v4 v4.26.0/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
|
||||
modernc.org/ccgo/v4 v4.26.0 h1:gVzXaDzGeBYJ2uXTOpR8FR7OlksDOe9jxnjhIKCsiTc=
|
||||
modernc.org/ccgo/v4 v4.26.0/go.mod h1:Sem8f7TFUtVXkG2fiaChQtyyfkqhJBg/zjEJBkmuAVY=
|
||||
modernc.org/fileutil v1.3.1 h1:8vq5fe7jdtEvoCf3Zf9Nm0Q05sH6kGx0Op2CPx1wTC8=
|
||||
modernc.org/fileutil v1.3.1/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc=
|
||||
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
|
||||
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
|
||||
modernc.org/libc v1.65.0 h1:e183gLDnAp9VJh6gWKdTy0CThL9Pt7MfcR/0bgb7Y1Y=
|
||||
modernc.org/libc v1.65.0/go.mod h1:7m9VzGq7APssBTydds2zBcxGREwvIGpuUBaKTXdm2Qs=
|
||||
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
|
||||
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
|
||||
modernc.org/memory v1.8.2 h1:cL9L4bcoAObu4NkxOlKWBWtNHIsnnACGF/TbqQ6sbcI=
|
||||
modernc.org/memory v1.8.2/go.mod h1:ZbjSvMO5NQ1A2i3bWeDiVMxIorXwdClKE/0SZ+BMotU=
|
||||
modernc.org/memory v1.10.0 h1:fzumd51yQ1DxcOxSO+S6X7+QTuVU+n8/Aj7swYjFfC4=
|
||||
modernc.org/memory v1.10.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
|
||||
modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8=
|
||||
modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
|
||||
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
|
||||
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
|
||||
modernc.org/sqlite v1.36.1 h1:bDa8BJUH4lg6EGkLbahKe/8QqoF8p9gArSc6fTqYhyQ=
|
||||
modernc.org/sqlite v1.36.1/go.mod h1:7MPwH7Z6bREicF9ZVUR78P1IKuxfZ8mRIDHD0iD+8TU=
|
||||
modernc.org/sqlite v1.37.0 h1:s1TMe7T3Q3ovQiK2Ouz4Jwh7dw4ZDqbebSDTlSJdfjI=
|
||||
modernc.org/sqlite v1.37.0/go.mod h1:5YiWv+YviqGMuGw4V+PNplcyaJ5v+vQd7TQOgkACoJM=
|
||||
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
|
||||
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
|
||||
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
||||
|
|
115
nodeconf/testconf/nodeconf.go
Normal file
115
nodeconf/testconf/nodeconf.go
Normal file
|
@ -0,0 +1,115 @@
|
|||
package testconf
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/anyproto/go-chash"
|
||||
|
||||
accountService "github.com/anyproto/any-sync/accountservice"
|
||||
"github.com/anyproto/any-sync/app"
|
||||
"github.com/anyproto/any-sync/nodeconf"
|
||||
)
|
||||
|
||||
type StubConf struct {
|
||||
id string
|
||||
networkId string
|
||||
configuration nodeconf.Configuration
|
||||
}
|
||||
|
||||
func (m *StubConf) NetworkCompatibilityStatus() nodeconf.NetworkCompatibilityStatus {
|
||||
return nodeconf.NetworkCompatibilityStatusOk
|
||||
}
|
||||
|
||||
func (m *StubConf) Init(a *app.App) (err error) {
|
||||
accountKeys := a.MustComponent(accountService.CName).(accountService.Service).Account()
|
||||
networkId := accountKeys.SignKey.GetPublic().Network()
|
||||
node := nodeconf.Node{
|
||||
PeerId: accountKeys.PeerId,
|
||||
Addresses: []string{"127.0.0.1:4430"},
|
||||
Types: []nodeconf.NodeType{nodeconf.NodeTypeTree},
|
||||
}
|
||||
m.id = networkId
|
||||
m.networkId = networkId
|
||||
m.configuration = nodeconf.Configuration{
|
||||
Id: networkId,
|
||||
NetworkId: networkId,
|
||||
Nodes: []nodeconf.Node{node},
|
||||
CreationTime: time.Now(),
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *StubConf) Name() (name string) {
|
||||
return nodeconf.CName
|
||||
}
|
||||
|
||||
func (m *StubConf) Run(ctx context.Context) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *StubConf) Close(ctx context.Context) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *StubConf) Id() string {
|
||||
return m.id
|
||||
}
|
||||
|
||||
func (m *StubConf) Configuration() nodeconf.Configuration {
|
||||
return m.configuration
|
||||
}
|
||||
|
||||
func (m *StubConf) NodeIds(spaceId string) []string {
|
||||
var nodeIds []string
|
||||
for _, node := range m.configuration.Nodes {
|
||||
nodeIds = append(nodeIds, node.PeerId)
|
||||
}
|
||||
return nodeIds
|
||||
}
|
||||
|
||||
func (m *StubConf) IsResponsible(spaceId string) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (m *StubConf) FilePeers() []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *StubConf) ConsensusPeers() []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *StubConf) CoordinatorPeers() []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *StubConf) NamingNodePeers() []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *StubConf) PaymentProcessingNodePeers() []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *StubConf) PeerAddresses(peerId string) (addrs []string, ok bool) {
|
||||
if peerId == m.configuration.Nodes[0].PeerId {
|
||||
return m.configuration.Nodes[0].Addresses, true
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func (m *StubConf) CHash() chash.CHash {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *StubConf) Partition(spaceId string) (part int) {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *StubConf) NodeTypes(nodeId string) []nodeconf.NodeType {
|
||||
if nodeId == m.configuration.Nodes[0].PeerId {
|
||||
return m.configuration.Nodes[0].Types
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -5,7 +5,6 @@
|
|||
//
|
||||
// mockgen -destination=mock/mock_paymentserviceclient.go -package=mock_paymentserviceclient github.com/anyproto/any-sync/paymentservice/paymentserviceclient AnyPpClientService
|
||||
//
|
||||
|
||||
// Package mock_paymentserviceclient is a generated GoMock package.
|
||||
package mock_paymentserviceclient
|
||||
|
||||
|
@ -22,7 +21,6 @@ import (
|
|||
type MockAnyPpClientService struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockAnyPpClientServiceMockRecorder
|
||||
isgomock struct{}
|
||||
}
|
||||
|
||||
// MockAnyPpClientServiceMockRecorder is the mock recorder for MockAnyPpClientService.
|
||||
|
@ -43,122 +41,122 @@ func (m *MockAnyPpClientService) EXPECT() *MockAnyPpClientServiceMockRecorder {
|
|||
}
|
||||
|
||||
// BuySubscription mocks base method.
|
||||
func (m *MockAnyPpClientService) BuySubscription(ctx context.Context, in *paymentserviceproto.BuySubscriptionRequestSigned) (*paymentserviceproto.BuySubscriptionResponse, error) {
|
||||
func (m *MockAnyPpClientService) BuySubscription(arg0 context.Context, arg1 *paymentserviceproto.BuySubscriptionRequestSigned) (*paymentserviceproto.BuySubscriptionResponse, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "BuySubscription", ctx, in)
|
||||
ret := m.ctrl.Call(m, "BuySubscription", arg0, arg1)
|
||||
ret0, _ := ret[0].(*paymentserviceproto.BuySubscriptionResponse)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// BuySubscription indicates an expected call of BuySubscription.
|
||||
func (mr *MockAnyPpClientServiceMockRecorder) BuySubscription(ctx, in any) *gomock.Call {
|
||||
func (mr *MockAnyPpClientServiceMockRecorder) BuySubscription(arg0, arg1 any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BuySubscription", reflect.TypeOf((*MockAnyPpClientService)(nil).BuySubscription), ctx, in)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BuySubscription", reflect.TypeOf((*MockAnyPpClientService)(nil).BuySubscription), arg0, arg1)
|
||||
}
|
||||
|
||||
// FinalizeSubscription mocks base method.
|
||||
func (m *MockAnyPpClientService) FinalizeSubscription(ctx context.Context, in *paymentserviceproto.FinalizeSubscriptionRequestSigned) (*paymentserviceproto.FinalizeSubscriptionResponse, error) {
|
||||
func (m *MockAnyPpClientService) FinalizeSubscription(arg0 context.Context, arg1 *paymentserviceproto.FinalizeSubscriptionRequestSigned) (*paymentserviceproto.FinalizeSubscriptionResponse, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "FinalizeSubscription", ctx, in)
|
||||
ret := m.ctrl.Call(m, "FinalizeSubscription", arg0, arg1)
|
||||
ret0, _ := ret[0].(*paymentserviceproto.FinalizeSubscriptionResponse)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// FinalizeSubscription indicates an expected call of FinalizeSubscription.
|
||||
func (mr *MockAnyPpClientServiceMockRecorder) FinalizeSubscription(ctx, in any) *gomock.Call {
|
||||
func (mr *MockAnyPpClientServiceMockRecorder) FinalizeSubscription(arg0, arg1 any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FinalizeSubscription", reflect.TypeOf((*MockAnyPpClientService)(nil).FinalizeSubscription), ctx, in)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FinalizeSubscription", reflect.TypeOf((*MockAnyPpClientService)(nil).FinalizeSubscription), arg0, arg1)
|
||||
}
|
||||
|
||||
// GetAllTiers mocks base method.
|
||||
func (m *MockAnyPpClientService) GetAllTiers(ctx context.Context, in *paymentserviceproto.GetTiersRequestSigned) (*paymentserviceproto.GetTiersResponse, error) {
|
||||
func (m *MockAnyPpClientService) GetAllTiers(arg0 context.Context, arg1 *paymentserviceproto.GetTiersRequestSigned) (*paymentserviceproto.GetTiersResponse, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetAllTiers", ctx, in)
|
||||
ret := m.ctrl.Call(m, "GetAllTiers", arg0, arg1)
|
||||
ret0, _ := ret[0].(*paymentserviceproto.GetTiersResponse)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetAllTiers indicates an expected call of GetAllTiers.
|
||||
func (mr *MockAnyPpClientServiceMockRecorder) GetAllTiers(ctx, in any) *gomock.Call {
|
||||
func (mr *MockAnyPpClientServiceMockRecorder) GetAllTiers(arg0, arg1 any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAllTiers", reflect.TypeOf((*MockAnyPpClientService)(nil).GetAllTiers), ctx, in)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAllTiers", reflect.TypeOf((*MockAnyPpClientService)(nil).GetAllTiers), arg0, arg1)
|
||||
}
|
||||
|
||||
// GetSubscriptionPortalLink mocks base method.
|
||||
func (m *MockAnyPpClientService) GetSubscriptionPortalLink(ctx context.Context, in *paymentserviceproto.GetSubscriptionPortalLinkRequestSigned) (*paymentserviceproto.GetSubscriptionPortalLinkResponse, error) {
|
||||
func (m *MockAnyPpClientService) GetSubscriptionPortalLink(arg0 context.Context, arg1 *paymentserviceproto.GetSubscriptionPortalLinkRequestSigned) (*paymentserviceproto.GetSubscriptionPortalLinkResponse, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetSubscriptionPortalLink", ctx, in)
|
||||
ret := m.ctrl.Call(m, "GetSubscriptionPortalLink", arg0, arg1)
|
||||
ret0, _ := ret[0].(*paymentserviceproto.GetSubscriptionPortalLinkResponse)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetSubscriptionPortalLink indicates an expected call of GetSubscriptionPortalLink.
|
||||
func (mr *MockAnyPpClientServiceMockRecorder) GetSubscriptionPortalLink(ctx, in any) *gomock.Call {
|
||||
func (mr *MockAnyPpClientServiceMockRecorder) GetSubscriptionPortalLink(arg0, arg1 any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSubscriptionPortalLink", reflect.TypeOf((*MockAnyPpClientService)(nil).GetSubscriptionPortalLink), ctx, in)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSubscriptionPortalLink", reflect.TypeOf((*MockAnyPpClientService)(nil).GetSubscriptionPortalLink), arg0, arg1)
|
||||
}
|
||||
|
||||
// GetSubscriptionStatus mocks base method.
|
||||
func (m *MockAnyPpClientService) GetSubscriptionStatus(ctx context.Context, in *paymentserviceproto.GetSubscriptionRequestSigned) (*paymentserviceproto.GetSubscriptionResponse, error) {
|
||||
func (m *MockAnyPpClientService) GetSubscriptionStatus(arg0 context.Context, arg1 *paymentserviceproto.GetSubscriptionRequestSigned) (*paymentserviceproto.GetSubscriptionResponse, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetSubscriptionStatus", ctx, in)
|
||||
ret := m.ctrl.Call(m, "GetSubscriptionStatus", arg0, arg1)
|
||||
ret0, _ := ret[0].(*paymentserviceproto.GetSubscriptionResponse)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetSubscriptionStatus indicates an expected call of GetSubscriptionStatus.
|
||||
func (mr *MockAnyPpClientServiceMockRecorder) GetSubscriptionStatus(ctx, in any) *gomock.Call {
|
||||
func (mr *MockAnyPpClientServiceMockRecorder) GetSubscriptionStatus(arg0, arg1 any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSubscriptionStatus", reflect.TypeOf((*MockAnyPpClientService)(nil).GetSubscriptionStatus), ctx, in)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSubscriptionStatus", reflect.TypeOf((*MockAnyPpClientService)(nil).GetSubscriptionStatus), arg0, arg1)
|
||||
}
|
||||
|
||||
// GetVerificationEmail mocks base method.
|
||||
func (m *MockAnyPpClientService) GetVerificationEmail(ctx context.Context, in *paymentserviceproto.GetVerificationEmailRequestSigned) (*paymentserviceproto.GetVerificationEmailResponse, error) {
|
||||
func (m *MockAnyPpClientService) GetVerificationEmail(arg0 context.Context, arg1 *paymentserviceproto.GetVerificationEmailRequestSigned) (*paymentserviceproto.GetVerificationEmailResponse, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetVerificationEmail", ctx, in)
|
||||
ret := m.ctrl.Call(m, "GetVerificationEmail", arg0, arg1)
|
||||
ret0, _ := ret[0].(*paymentserviceproto.GetVerificationEmailResponse)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetVerificationEmail indicates an expected call of GetVerificationEmail.
|
||||
func (mr *MockAnyPpClientServiceMockRecorder) GetVerificationEmail(ctx, in any) *gomock.Call {
|
||||
func (mr *MockAnyPpClientServiceMockRecorder) GetVerificationEmail(arg0, arg1 any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetVerificationEmail", reflect.TypeOf((*MockAnyPpClientService)(nil).GetVerificationEmail), ctx, in)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetVerificationEmail", reflect.TypeOf((*MockAnyPpClientService)(nil).GetVerificationEmail), arg0, arg1)
|
||||
}
|
||||
|
||||
// Init mocks base method.
|
||||
func (m *MockAnyPpClientService) Init(a *app.App) error {
|
||||
func (m *MockAnyPpClientService) Init(arg0 *app.App) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Init", a)
|
||||
ret := m.ctrl.Call(m, "Init", arg0)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Init indicates an expected call of Init.
|
||||
func (mr *MockAnyPpClientServiceMockRecorder) Init(a any) *gomock.Call {
|
||||
func (mr *MockAnyPpClientServiceMockRecorder) Init(arg0 any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Init", reflect.TypeOf((*MockAnyPpClientService)(nil).Init), a)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Init", reflect.TypeOf((*MockAnyPpClientService)(nil).Init), arg0)
|
||||
}
|
||||
|
||||
// IsNameValid mocks base method.
|
||||
func (m *MockAnyPpClientService) IsNameValid(ctx context.Context, in *paymentserviceproto.IsNameValidRequest) (*paymentserviceproto.IsNameValidResponse, error) {
|
||||
func (m *MockAnyPpClientService) IsNameValid(arg0 context.Context, arg1 *paymentserviceproto.IsNameValidRequest) (*paymentserviceproto.IsNameValidResponse, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "IsNameValid", ctx, in)
|
||||
ret := m.ctrl.Call(m, "IsNameValid", arg0, arg1)
|
||||
ret0, _ := ret[0].(*paymentserviceproto.IsNameValidResponse)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// IsNameValid indicates an expected call of IsNameValid.
|
||||
func (mr *MockAnyPpClientServiceMockRecorder) IsNameValid(ctx, in any) *gomock.Call {
|
||||
func (mr *MockAnyPpClientServiceMockRecorder) IsNameValid(arg0, arg1 any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsNameValid", reflect.TypeOf((*MockAnyPpClientService)(nil).IsNameValid), ctx, in)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsNameValid", reflect.TypeOf((*MockAnyPpClientService)(nil).IsNameValid), arg0, arg1)
|
||||
}
|
||||
|
||||
// Name mocks base method.
|
||||
|
@ -176,31 +174,31 @@ func (mr *MockAnyPpClientServiceMockRecorder) Name() *gomock.Call {
|
|||
}
|
||||
|
||||
// VerifyAppStoreReceipt mocks base method.
|
||||
func (m *MockAnyPpClientService) VerifyAppStoreReceipt(ctx context.Context, in *paymentserviceproto.VerifyAppStoreReceiptRequestSigned) (*paymentserviceproto.VerifyAppStoreReceiptResponse, error) {
|
||||
func (m *MockAnyPpClientService) VerifyAppStoreReceipt(arg0 context.Context, arg1 *paymentserviceproto.VerifyAppStoreReceiptRequestSigned) (*paymentserviceproto.VerifyAppStoreReceiptResponse, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "VerifyAppStoreReceipt", ctx, in)
|
||||
ret := m.ctrl.Call(m, "VerifyAppStoreReceipt", arg0, arg1)
|
||||
ret0, _ := ret[0].(*paymentserviceproto.VerifyAppStoreReceiptResponse)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// VerifyAppStoreReceipt indicates an expected call of VerifyAppStoreReceipt.
|
||||
func (mr *MockAnyPpClientServiceMockRecorder) VerifyAppStoreReceipt(ctx, in any) *gomock.Call {
|
||||
func (mr *MockAnyPpClientServiceMockRecorder) VerifyAppStoreReceipt(arg0, arg1 any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "VerifyAppStoreReceipt", reflect.TypeOf((*MockAnyPpClientService)(nil).VerifyAppStoreReceipt), ctx, in)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "VerifyAppStoreReceipt", reflect.TypeOf((*MockAnyPpClientService)(nil).VerifyAppStoreReceipt), arg0, arg1)
|
||||
}
|
||||
|
||||
// VerifyEmail mocks base method.
|
||||
func (m *MockAnyPpClientService) VerifyEmail(ctx context.Context, in *paymentserviceproto.VerifyEmailRequestSigned) (*paymentserviceproto.VerifyEmailResponse, error) {
|
||||
func (m *MockAnyPpClientService) VerifyEmail(arg0 context.Context, arg1 *paymentserviceproto.VerifyEmailRequestSigned) (*paymentserviceproto.VerifyEmailResponse, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "VerifyEmail", ctx, in)
|
||||
ret := m.ctrl.Call(m, "VerifyEmail", arg0, arg1)
|
||||
ret0, _ := ret[0].(*paymentserviceproto.VerifyEmailResponse)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// VerifyEmail indicates an expected call of VerifyEmail.
|
||||
func (mr *MockAnyPpClientServiceMockRecorder) VerifyEmail(ctx, in any) *gomock.Call {
|
||||
func (mr *MockAnyPpClientServiceMockRecorder) VerifyEmail(arg0, arg1 any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "VerifyEmail", reflect.TypeOf((*MockAnyPpClientService)(nil).VerifyEmail), ctx, in)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "VerifyEmail", reflect.TypeOf((*MockAnyPpClientService)(nil).VerifyEmail), arg0, arg1)
|
||||
}
|
||||
|
|
|
@ -2,8 +2,10 @@ package crypto
|
|||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"github.com/anyproto/any-sync/util/strkey"
|
||||
|
||||
"github.com/libp2p/go-libp2p/core/peer"
|
||||
|
||||
"github.com/anyproto/any-sync/util/strkey"
|
||||
)
|
||||
|
||||
func EncodeKeyToString[T Key](key T) (str string, err error) {
|
||||
|
@ -55,3 +57,11 @@ func DecodePeerId(peerId string) (PubKey, error) {
|
|||
}
|
||||
return UnmarshalEd25519PublicKey(raw)
|
||||
}
|
||||
|
||||
func DecodeNetworkId(networkId string) (PubKey, error) {
|
||||
pubKeyRaw, err := strkey.Decode(strkey.NetworkAddressVersionByte, networkId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return UnmarshalEd25519PublicKey(pubKeyRaw)
|
||||
}
|
||||
|
|
18
util/crypto/decode_test.go
Normal file
18
util/crypto/decode_test.go
Normal file
|
@ -0,0 +1,18 @@
|
|||
package crypto
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestDecodeNetworkId(t *testing.T) {
|
||||
_, pubKey, err := GenerateRandomEd25519KeyPair()
|
||||
require.NoError(t, err)
|
||||
|
||||
networkId := pubKey.Network()
|
||||
require.Equal(t, uint8('N'), networkId[0])
|
||||
decodedKey, err := DecodeNetworkId(networkId)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, pubKey, decodedKey)
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue