1
0
Fork 0
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:
Mikhail Rakhmanov 2025-05-19 13:39:58 +02:00 committed by GitHub
commit c37a139fba
Signed by: github
GPG key ID: B5690EEEBB952194
36 changed files with 2437 additions and 438 deletions

View file

@ -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 {

View file

@ -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 {

View file

@ -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) {

View file

@ -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.

View file

@ -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
}

View file

@ -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{})

View file

@ -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

View file

@ -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;
}
}

View file

@ -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
}

View file

@ -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
}
}

View file

@ -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
}

View file

@ -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

View file

@ -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)

View file

@ -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)

View file

@ -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)
}

View file

@ -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
}

View file

@ -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:

View file

@ -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
}

View 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
}

View 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)
}

View 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
}

View file

@ -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
}

View file

@ -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()

View file

@ -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)

View file

@ -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,

View file

@ -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

View file

@ -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).

View file

@ -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)
}

View file

@ -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
View file

@ -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
View file

@ -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=

View 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
}

View file

@ -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)
}

View file

@ -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)
}

View 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)
}