diff --git a/commonspace/acl/aclclient/aclspaceclient.go b/commonspace/acl/aclclient/aclspaceclient.go index 2efe5799..d51be42c 100644 --- a/commonspace/acl/aclclient/aclspaceclient.go +++ b/commonspace/acl/aclclient/aclspaceclient.go @@ -27,6 +27,7 @@ type InviteSaveFunc func() type AclSpaceClient interface { app.Component GenerateInvite() (list.InviteResult, 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 AcceptRequest(ctx context.Context, payload list.RequestAcceptPayload) error @@ -114,6 +115,39 @@ func (c *aclSpaceClient) RemoveAccounts(ctx context.Context, payload list.Accoun return c.sendRecordAndUpdate(ctx, c.spaceId, res) } +func (c *aclSpaceClient) StopSharing(ctx context.Context, readKeyChange list.ReadKeyChangePayload) (err error) { + c.acl.Lock() + var ( + identities []crypto.PubKey + recIds []string + ) + for _, state := range c.acl.AclState().CurrentAccounts() { + if state.Permissions.NoPermissions() || state.Permissions.IsOwner() { + continue + } + identities = append(identities, state.PubKey) + } + recs, _ := c.acl.AclState().JoinRecords(false) + for _, rec := range recs { + recIds = append(recIds, rec.RecordId) + } + payload := list.BatchRequestPayload{ + Removals: list.AccountRemovePayload{ + Identities: identities, + Change: readKeyChange, + }, + Declines: recIds, + InviteRevokes: c.acl.AclState().InviteIds(), + } + res, err := c.acl.RecordBuilder().BuildBatchRequest(payload) + if err != nil { + c.acl.Unlock() + return + } + c.acl.Unlock() + return c.sendRecordAndUpdate(ctx, c.spaceId, res) +} + func (c *aclSpaceClient) DeclineRequest(ctx context.Context, identity crypto.PubKey) (err error) { c.acl.Lock() pendingReq, err := c.acl.AclState().JoinRecord(identity, false) diff --git a/commonspace/object/acl/list/aclrecordbuilder.go b/commonspace/object/acl/list/aclrecordbuilder.go index 6305fd20..14a5c400 100644 --- a/commonspace/object/acl/list/aclrecordbuilder.go +++ b/commonspace/object/acl/list/aclrecordbuilder.go @@ -55,6 +55,15 @@ type AccountAdd struct { Metadata []byte } +type BatchRequestPayload struct { + Additions []AccountAdd + Changes []PermissionChangePayload + Removals AccountRemovePayload + Approvals []RequestAcceptPayload + Declines []string + InviteRevokes []string +} + type AccountRemovePayload struct { Identities []crypto.PubKey Change ReadKeyChangePayload @@ -70,6 +79,7 @@ type AclRecordBuilder interface { Unmarshall(rawRecord *consensusproto.RawRecord) (rec *AclRecord, err error) BuildRoot(content RootContent) (rec *consensusproto.RawRecordWithId, err error) + BuildBatchRequest(payload BatchRequestPayload) (rawRecord *consensusproto.RawRecord, err error) BuildInvite() (res InviteResult, err error) BuildInviteRevoke(inviteRecordId string) (rawRecord *consensusproto.RawRecord, err error) BuildRequestJoin(payload RequestJoinPayload) (rawRecord *consensusproto.RawRecord, err error) @@ -101,10 +111,59 @@ 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}) + if err != nil { + return nil, err + } + aclContent = append(aclContent, content) + } + if len(payload.Changes) > 0 { + content, err := a.buildPermissionChanges(PermissionChangesPayload{Changes: payload.Changes}) + if err != nil { + return nil, err + } + aclContent = append(aclContent, content) + } + for _, acc := range payload.Approvals { + content, err := a.buildRequestAccept(acc) + if err != nil { + return nil, err + } + aclContent = append(aclContent, content) + } + for _, id := range payload.Declines { + content, err := a.buildRequestDecline(id) + if err != nil { + return nil, err + } + aclContent = append(aclContent, content) + } + for _, id := range payload.InviteRevokes { + content, err := a.buildInviteRevoke(id) + if err != nil { + return nil, err + } + aclContent = append(aclContent, content) + } + if len(payload.Removals.Identities) > 0 { + content, err := a.buildAccountRemove(payload.Removals) + if err != nil { + return nil, err + } + aclContent = append(aclContent, content) + } + return a.buildRecords(aclContent) +} + func (a *aclRecordBuilder) buildRecord(aclContent *aclrecordproto.AclContentValue) (rawRec *consensusproto.RawRecord, err error) { - aclData := &aclrecordproto.AclData{AclContent: []*aclrecordproto.AclContentValue{ - aclContent, - }} + return a.buildRecords([]*aclrecordproto.AclContentValue{aclContent}) +} + +func (a *aclRecordBuilder) buildRecords(aclContent []*aclrecordproto.AclContentValue) (rawRec *consensusproto.RawRecord, err error) { + aclData := &aclrecordproto.AclData{AclContent: aclContent} marshalledData, err := aclData.Marshal() if err != nil { return @@ -135,6 +194,14 @@ func (a *aclRecordBuilder) buildRecord(aclContent *aclrecordproto.AclContentValu } func (a *aclRecordBuilder) BuildPermissionChanges(payload PermissionChangesPayload) (rawRecord *consensusproto.RawRecord, err error) { + content, err := a.buildPermissionChanges(payload) + if err != nil { + return + } + return a.buildRecord(content) +} + +func (a *aclRecordBuilder) buildPermissionChanges(payload PermissionChangesPayload) (content *aclrecordproto.AclContentValue, err error) { if !a.state.Permissions(a.state.pubKey).CanManageAccounts() { err = ErrInsufficientPermissions return @@ -158,13 +225,20 @@ func (a *aclRecordBuilder) BuildPermissionChanges(payload PermissionChangesPaylo Permissions: aclrecordproto.AclUserPermissions(perm.Permissions), }) } - content := &aclrecordproto.AclContentValue{Value: &aclrecordproto.AclContentValue_PermissionChanges{ + return &aclrecordproto.AclContentValue{Value: &aclrecordproto.AclContentValue_PermissionChanges{ &aclrecordproto.AclAccountPermissionChanges{changes}, - }} - return a.buildRecord(content) + }}, nil } func (a *aclRecordBuilder) BuildAccountsAdd(payload AccountsAddPayload) (rawRecord *consensusproto.RawRecord, err error) { + content, err := a.buildAccountsAdd(payload) + if err != nil { + return + } + return a.buildRecord(content) +} + +func (a *aclRecordBuilder) buildAccountsAdd(payload AccountsAddPayload) (value *aclrecordproto.AclContentValue, err error) { var accs []*aclrecordproto.AclAccountAdd for _, acc := range payload.Additions { if !a.state.Permissions(acc.Identity).NoPermissions() { @@ -207,10 +281,9 @@ func (a *aclRecordBuilder) BuildAccountsAdd(payload AccountsAddPayload) (rawReco EncryptedReadKey: enc, }) } - content := &aclrecordproto.AclContentValue{Value: &aclrecordproto.AclContentValue_AccountsAdd{ + return &aclrecordproto.AclContentValue{Value: &aclrecordproto.AclContentValue_AccountsAdd{ &aclrecordproto.AclAccountsAdd{accs}, - }} - return a.buildRecord(content) + }}, nil } func (a *aclRecordBuilder) BuildInvite() (res InviteResult, err error) { @@ -238,6 +311,14 @@ func (a *aclRecordBuilder) BuildInvite() (res InviteResult, err error) { } func (a *aclRecordBuilder) BuildInviteRevoke(inviteRecordId string) (rawRecord *consensusproto.RawRecord, err error) { + content, err := a.buildInviteRevoke(inviteRecordId) + if err != nil { + return + } + return a.buildRecord(content) +} + +func (a *aclRecordBuilder) buildInviteRevoke(inviteRecordId string) (value *aclrecordproto.AclContentValue, err error) { if !a.state.Permissions(a.state.pubKey).CanManageAccounts() { err = ErrInsufficientPermissions return @@ -248,8 +329,7 @@ func (a *aclRecordBuilder) BuildInviteRevoke(inviteRecordId string) (rawRecord * return } revokeRec := &aclrecordproto.AclAccountInviteRevoke{InviteRecordId: inviteRecordId} - content := &aclrecordproto.AclContentValue{Value: &aclrecordproto.AclContentValue_InviteRevoke{InviteRevoke: revokeRec}} - return a.buildRecord(content) + return &aclrecordproto.AclContentValue{Value: &aclrecordproto.AclContentValue_InviteRevoke{InviteRevoke: revokeRec}}, nil } func (a *aclRecordBuilder) BuildRequestJoin(payload RequestJoinPayload) (rawRecord *consensusproto.RawRecord, err error) { @@ -306,6 +386,14 @@ func (a *aclRecordBuilder) BuildRequestJoin(payload RequestJoinPayload) (rawReco } func (a *aclRecordBuilder) BuildRequestAccept(payload RequestAcceptPayload) (rawRecord *consensusproto.RawRecord, err error) { + content, err := a.buildRequestAccept(payload) + if err != nil { + return + } + return a.buildRecord(content) +} + +func (a *aclRecordBuilder) buildRequestAccept(payload RequestAcceptPayload) (value *aclrecordproto.AclContentValue, err error) { if !a.state.Permissions(a.state.pubKey).CanManageAccounts() { err = ErrInsufficientPermissions return @@ -337,11 +425,18 @@ func (a *aclRecordBuilder) BuildRequestAccept(payload RequestAcceptPayload) (raw EncryptedReadKey: enc, Permissions: aclrecordproto.AclUserPermissions(payload.Permissions), } - content := &aclrecordproto.AclContentValue{Value: &aclrecordproto.AclContentValue_RequestAccept{RequestAccept: acceptRec}} - return a.buildRecord(content) + return &aclrecordproto.AclContentValue{Value: &aclrecordproto.AclContentValue_RequestAccept{RequestAccept: acceptRec}}, nil } func (a *aclRecordBuilder) BuildRequestDecline(requestRecordId string) (rawRecord *consensusproto.RawRecord, err error) { + content, err := a.buildRequestDecline(requestRecordId) + if err != nil { + return + } + return a.buildRecord(content) +} + +func (a *aclRecordBuilder) buildRequestDecline(requestRecordId string) (value *aclrecordproto.AclContentValue, err error) { if !a.state.Permissions(a.state.pubKey).CanManageAccounts() { err = ErrInsufficientPermissions return @@ -352,8 +447,7 @@ func (a *aclRecordBuilder) BuildRequestDecline(requestRecordId string) (rawRecor return } declineRec := &aclrecordproto.AclAccountRequestDecline{RequestRecordId: requestRecordId} - content := &aclrecordproto.AclContentValue{Value: &aclrecordproto.AclContentValue_RequestDecline{RequestDecline: declineRec}} - return a.buildRecord(content) + return &aclrecordproto.AclContentValue{Value: &aclrecordproto.AclContentValue_RequestDecline{RequestDecline: declineRec}}, nil } func (a *aclRecordBuilder) BuildRequestCancel(requestRecordId string) (rawRecord *consensusproto.RawRecord, err error) { @@ -468,6 +562,14 @@ func (a *aclRecordBuilder) buildReadKeyChange(payload ReadKeyChangePayload, remo } func (a *aclRecordBuilder) BuildAccountRemove(payload AccountRemovePayload) (rawRecord *consensusproto.RawRecord, err error) { + content, err := a.buildAccountRemove(payload) + if err != nil { + return + } + return a.buildRecord(content) +} + +func (a *aclRecordBuilder) buildAccountRemove(payload AccountRemovePayload) (value *aclrecordproto.AclContentValue, err error) { deletedMap := map[string]struct{}{} for _, key := range payload.Identities { permissions := a.state.Permissions(key) @@ -496,8 +598,7 @@ func (a *aclRecordBuilder) BuildAccountRemove(payload AccountRemovePayload) (raw return nil, err } removeRec := &aclrecordproto.AclAccountRemove{ReadKeyChange: rkChange, Identities: marshalledIdentities} - content := &aclrecordproto.AclContentValue{Value: &aclrecordproto.AclContentValue_AccountRemove{AccountRemove: removeRec}} - return a.buildRecord(content) + return &aclrecordproto.AclContentValue{Value: &aclrecordproto.AclContentValue_AccountRemove{AccountRemove: removeRec}}, nil } func (a *aclRecordBuilder) BuildRequestRemove() (rawRecord *consensusproto.RawRecord, err error) { diff --git a/commonspace/object/acl/list/aclstate.go b/commonspace/object/acl/list/aclstate.go index 74152e9d..1a45dfce 100644 --- a/commonspace/object/acl/list/aclstate.go +++ b/commonspace/object/acl/list/aclstate.go @@ -192,7 +192,15 @@ func (st *AclState) Invites() []crypto.PubKey { return invites } -func (st *AclState) applyRecord(record *AclRecord) (err error) { +func (st *AclState) InviteIds() []string { + var invites []string + for invId := range st.inviteKeys { + invites = append(invites, invId) + } + return invites +} + +func (st *AclState) ApplyRecord(record *AclRecord) (err error) { if st.lastRecordId != record.PrevId { err = ErrIncorrectRecordSequence return @@ -296,6 +304,44 @@ func (st *AclState) applyChangeData(record *AclRecord) (err error) { return nil } +func (st *AclState) Copy() *AclState { + newSt := &AclState{ + id: st.id, + key: st.key, + pubKey: st.key.GetPublic(), + keys: make(map[string]AclKeys), + accountStates: make(map[string]AccountState), + inviteKeys: make(map[string]crypto.PubKey), + requestRecords: make(map[string]RequestRecord), + pendingRequests: make(map[string]string), + keyStore: st.keyStore, + } + for k, v := range st.keys { + newSt.keys[k] = v + } + for k, v := range st.accountStates { + var permChanges []PermissionChange + permChanges = append(permChanges, v.PermissionChanges...) + accState := v + accState.PermissionChanges = permChanges + newSt.accountStates[k] = accState + } + for k, v := range st.inviteKeys { + newSt.inviteKeys[k] = v + } + for k, v := range st.requestRecords { + newSt.requestRecords[k] = v + } + for k, v := range st.pendingRequests { + newSt.pendingRequests[k] = v + } + newSt.readKeyChanges = append(newSt.readKeyChanges, st.readKeyChanges...) + newSt.list = st.list + newSt.lastRecordId = st.lastRecordId + newSt.contentValidator = newContentValidator(newSt.keyStore, newSt) + return newSt +} + func (st *AclState) applyChangeContent(ch *aclrecordproto.AclContentValue, record *AclRecord) error { switch { case ch.GetPermissionChange() != nil: @@ -522,16 +568,18 @@ func (st *AclState) unpackAllKeys(rk []byte) error { break } model := rec.Model.(*aclrecordproto.AclData) - if len(model.GetAclContent()) != 1 { - return ErrIncorrectReadKey - } - ch := model.GetAclContent()[0] + content := model.GetAclContent() var readKeyChange *aclrecordproto.AclReadKeyChange - switch { - case ch.GetReadKeyChange() != nil: - readKeyChange = ch.GetReadKeyChange() - case ch.GetAccountRemove() != nil: - readKeyChange = ch.GetAccountRemove().GetReadKeyChange() + 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 { diff --git a/commonspace/object/acl/list/aclstatebuilder.go b/commonspace/object/acl/list/aclstatebuilder.go index acba848c..2b7317f7 100644 --- a/commonspace/object/acl/list/aclstatebuilder.go +++ b/commonspace/object/acl/list/aclstatebuilder.go @@ -41,7 +41,7 @@ func (sb *aclStateBuilder) Build(records []*AclRecord, list *aclList) (state *Ac } state.list = list for _, rec := range records[1:] { - err = state.applyRecord(rec) + err = state.ApplyRecord(rec) if err != nil { return nil, err } @@ -52,7 +52,7 @@ func (sb *aclStateBuilder) Build(records []*AclRecord, list *aclList) (state *Ac func (sb *aclStateBuilder) Append(state *AclState, records []*AclRecord) (err error) { for _, rec := range records { - err = state.applyRecord(rec) + err = state.ApplyRecord(rec) if err != nil { return } diff --git a/commonspace/object/acl/list/acltestsuite.go b/commonspace/object/acl/list/acltestsuite.go index 6a5f7a8b..fd1cfab2 100644 --- a/commonspace/object/acl/list/acltestsuite.go +++ b/commonspace/object/acl/list/acltestsuite.go @@ -47,8 +47,164 @@ var ( errIncorrectParts = errors.New("incorrect parts") ) +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{} + for _, arg := range args { + parts := strings.Split(arg, ":") + if len(parts) != 2 { + return nil, errIncorrectParts + } + command := parts[0] + commandArgs := strings.Split(parts[1], "|") + switch command { + case "add": + var payloads []AccountAdd + for _, arg := range commandArgs { + argParts := strings.Split(arg, ",") + if len(argParts) != 3 { + return nil, errIncorrectParts + } + keys, err := accountdata.NewRandom() + if err != nil { + return nil, err + } + ownerAcl := a.actualAccounts[a.owner].Acl.(*aclList) + accountAcl, err := BuildAclListWithIdentity(keys, ownerAcl.storage, NoOpAcceptorVerifier{}) + if err != nil { + return nil, err + } + state := &TestAclState{ + Keys: keys, + Acl: accountAcl, + } + account := argParts[0] + a.actualAccounts[account] = state + a.expectedAccounts[account] = &accountExpectedState{ + perms: getPerm(argParts[1]), + status: StatusActive, + metadata: []byte(argParts[2]), + pseudoId: account, + } + payloads = append(payloads, AccountAdd{ + Identity: keys.SignKey.GetPublic(), + Permissions: getPerm(argParts[1]), + Metadata: []byte(argParts[2]), + }) + } + defer func() { + if err != nil { + for _, arg := range args { + argParts := strings.Split(arg, ",") + account := argParts[0] + delete(a.expectedAccounts, account) + delete(a.actualAccounts, account) + } + } + }() + batchPayload.Additions = payloads + case "remove": + identities := strings.Split(commandArgs[0], ",") + var pubKeys []crypto.PubKey + for _, id := range identities { + pk := a.actualAccounts[id].Keys.SignKey.GetPublic() + pubKeys = append(pubKeys, pk) + } + priv, _, err := crypto.GenerateRandomEd25519KeyPair() + if err != nil { + return nil, err + } + sym := crypto.NewAES() + payload := AccountRemovePayload{ + Identities: pubKeys, + Change: ReadKeyChangePayload{ + MetadataKey: priv, + ReadKey: sym, + }, + } + batchPayload.Removals = payload + afterAll = append(afterAll, func() { + for _, id := range identities { + a.expectedAccounts[id].status = StatusRemoved + a.expectedAccounts[id].perms = AclPermissionsNone + } + }) + case "changes": + var payloads []PermissionChangePayload + for _, arg := range commandArgs { + argParts := strings.Split(arg, ",") + if len(argParts) != 2 { + return nil, errIncorrectParts + } + changed := a.actualAccounts[argParts[0]].Keys.SignKey.GetPublic() + perms := getPerm(argParts[1]) + payloads = append(payloads, PermissionChangePayload{ + Identity: changed, + Permissions: perms, + }) + afterAll = append(afterAll, func() { + a.expectedAccounts[argParts[0]].perms = perms + }) + } + batchPayload.Changes = payloads + case "revoke": + invite := a.invites[commandArgs[0]] + invId, err := acl.AclState().GetInviteIdByPrivKey(invite) + if err != nil { + return nil, err + } + batchPayload.InviteRevokes = append(batchPayload.InviteRevokes, invId) + case "decline": + id := commandArgs[0] + pk := a.actualAccounts[id].Keys.SignKey.GetPublic() + rec, err := acl.AclState().JoinRecord(pk, false) + if err != nil { + return nil, err + } + batchPayload.Declines = append(batchPayload.Declines, rec.RecordId) + afterAll = append(afterAll, func() { + a.expectedAccounts[id].status = StatusDeclined + }) + case "approve": + recs, err := acl.AclState().JoinRecords(false) + if err != nil { + return nil, err + } + argParts := strings.Split(commandArgs[0], ",") + if len(argParts) != 2 { + return nil, errIncorrectParts + } + approved := a.actualAccounts[argParts[0]].Keys.SignKey.GetPublic() + var recId string + for _, rec := range recs { + if rec.RequestIdentity.Equals(approved) { + recId = rec.RecordId + } + } + if recId == "" { + return nil, fmt.Errorf("no join records for approve") + } + perms := getPerm(argParts[1]) + afterAll = append(afterAll, func() { + a.expectedAccounts[argParts[0]].status = StatusActive + a.expectedAccounts[argParts[0]].perms = perms + }) + batchPayload.Approvals = append(batchPayload.Approvals, RequestAcceptPayload{ + RequestRecordId: recId, + Permissions: perms, + }) + } + } + + res, err := acl.RecordBuilder().BuildBatchRequest(batchPayload) + if err != nil { + return nil, err + } + return afterAll, addRec(WrapAclRecord(res)) +} + func (a *AclTestExecutor) Execute(cmd string) (err error) { - parts := strings.Split(cmd, ":") + parts := strings.Split(cmd, "::") if len(parts) != 2 { return errIncorrectParts } @@ -181,6 +337,11 @@ func (a *AclTestExecutor) Execute(cmd string) (err error) { if err != nil { return err } + case "batch": + afterAll, err = a.buildBatchRequest(args, acl, getPerm, addRec) + if err != nil { + return err + } case "approve": recs, err := acl.AclState().JoinRecords(false) if err != nil { diff --git a/commonspace/object/acl/list/acltestsuite_test.go b/commonspace/object/acl/list/acltestsuite_test.go index 026cded0..f5b4dd6e 100644 --- a/commonspace/object/acl/list/acltestsuite_test.go +++ b/commonspace/object/acl/list/acltestsuite_test.go @@ -50,61 +50,67 @@ func TestAclExecutor(t *testing.T) { err error } cmds := []cmdErr{ - {"a.init:a", nil}, + {"a.init::a", nil}, // creating an invite - {"a.invite:invId", nil}, + {"a.invite::invId", nil}, // cannot self join - {"a.join:invId", ErrInsufficientPermissions}, + {"a.join::invId", ErrInsufficientPermissions}, // now b can join - {"b.join:invId", nil}, + {"b.join::invId", nil}, // a approves b, it can write now - {"a.approve:b,r", nil}, + {"a.approve::b,r", nil}, // c joins with the same invite - {"c.join:invId", nil}, + {"c.join::invId", nil}, // a approves c - {"a.approve:c,r", nil}, + {"a.approve::c,r", nil}, // a removes c - {"a.remove:c", nil}, + {"a.remove::c", nil}, // e also joins as an admin - {"e.join:invId", nil}, - {"a.approve:e,adm", nil}, + {"e.join::invId", nil}, + {"a.approve::e,adm", nil}, // now e can remove other users - {"e.remove:b", nil}, - {"e.revoke:invId", nil}, - {"z.join:invId", ErrNoSuchInvite}, + {"e.remove::b", nil}, + {"e.revoke::invId", nil}, + {"z.join::invId", ErrNoSuchInvite}, // e can't revoke the same id - {"e.revoke:invId", ErrNoSuchRecord}, + {"e.revoke::invId", ErrNoSuchRecord}, // e can't remove a, because a is the owner - {"e.remove:a", ErrInsufficientPermissions}, + {"e.remove::a", ErrInsufficientPermissions}, // e can add new users - {"e.add:x,r,m1;y,adm,m2", nil}, + {"e.add::x,r,m1;y,adm,m2", nil}, // now y can also change permission as an admin - {"y.changes:x,rw", nil}, + {"y.changes::x,rw", nil}, // e can generate another invite - {"e.invite:inv1Id", nil}, + {"e.invite::inv1Id", nil}, // b tries to join again - {"b.join:inv1Id", nil}, + {"b.join::inv1Id", nil}, // e approves b - {"e.approve:b,rw", nil}, - {"g.join:inv1Id", nil}, - {"g.cancel:g", nil}, + {"e.approve::b,rw", nil}, + {"g.join::inv1Id", nil}, + {"g.cancel::g", nil}, // e cannot approve cancelled request - {"e.approve:g,rw", fmt.Errorf("no join records for approve")}, - {"g.join:inv1Id", nil}, - {"e.decline:g", nil}, + {"e.approve::g,rw", fmt.Errorf("no join records for approve")}, + {"g.join::inv1Id", nil}, + {"e.decline::g", nil}, // g cannot cancel declined request - {"g.cancel:g", ErrNoSuchRecord}, - {"g.join:inv1Id", nil}, - {"e.approve:g,r", nil}, + {"g.cancel::g", ErrNoSuchRecord}, + {"g.join::inv1Id", nil}, + {"e.approve::g,r", nil}, // g can request remove - {"g.request_remove:g", nil}, + {"g.request_remove::g", nil}, // g can cancel request to remove - {"g.cancel:g", nil}, - {"g.request_remove:g", nil}, - {"g.request_remove:g", ErrPendingRequest}, - {"a.remove:g", nil}, + {"g.cancel::g", nil}, + {"g.request_remove::g", nil}, + {"g.request_remove::g", ErrPendingRequest}, + {"a.remove::g", nil}, // g cannot cancel not existing request to remove - {"g.cancel:g", ErrNoSuchRecord}, + {"g.cancel::g", ErrNoSuchRecord}, + {"l.join::inv1Id", nil}, + {"p.join::inv1Id", nil}, + {"s.join::inv1Id", nil}, + {"a.batch::remove:e,y;add:z,rw,mz|u,r,mu;revoke:inv1Id;approve:l,r;approve:p,adm;decline:s", nil}, + {"p.remove::l", nil}, + {"s.join::inv1Id", ErrNoSuchInvite}, } for _, cmd := range cmds { err := a.Execute(cmd.cmd) diff --git a/commonspace/object/acl/list/list.go b/commonspace/object/acl/list/list.go index e93295bc..a3c3cd11 100644 --- a/commonspace/object/acl/list/list.go +++ b/commonspace/object/acl/list/list.go @@ -58,7 +58,7 @@ type AclList interface { KeyStorage() crypto.KeyStorage RecordBuilder() AclRecordBuilder - ValidateRawRecord(record *consensusproto.RawRecord) (err error) + ValidateRawRecord(rawRec *consensusproto.RawRecord, afterValid func(state *AclState) error) (err error) AddRawRecord(rawRec *consensusproto.RawRecordWithId) (err error) AddRawRecords(rawRecords []*consensusproto.RawRecordWithId) (err error) @@ -192,12 +192,17 @@ func (a *aclList) Records() []*AclRecord { return a.records } -func (a *aclList) ValidateRawRecord(rawRec *consensusproto.RawRecord) (err error) { +func (a *aclList) ValidateRawRecord(rawRec *consensusproto.RawRecord, afterValid func(state *AclState) error) (err error) { record, err := a.recordBuilder.Unmarshall(rawRec) if err != nil { return } - return a.aclState.Validator().ValidateAclRecordContents(record) + stateCopy := a.aclState.Copy() + err = stateCopy.ApplyRecord(record) + if err != nil || afterValid == nil { + return + } + return afterValid(stateCopy) } func (a *aclList) AddRawRecords(rawRecords []*consensusproto.RawRecordWithId) error { @@ -218,9 +223,11 @@ func (a *aclList) AddRawRecord(rawRec *consensusproto.RawRecordWithId) (err erro if err != nil { return } - if err = a.aclState.applyRecord(record); err != nil { + copyState := a.aclState.Copy() + if err = copyState.ApplyRecord(record); err != nil { return } + a.setState(copyState) a.records = append(a.records, record) a.indexes[record.Id] = len(a.records) - 1 if err = a.storage.AddRawRecord(context.Background(), rawRec); err != nil { @@ -232,6 +239,11 @@ func (a *aclList) AddRawRecord(rawRec *consensusproto.RawRecordWithId) (err erro return } +func (a *aclList) setState(state *AclState) { + a.aclState = state + a.recordBuilder.(*aclRecordBuilder).state = state +} + func (a *aclList) Id() string { return a.id } diff --git a/commonspace/object/acl/list/list_test.go b/commonspace/object/acl/list/list_test.go index b5bf8666..4084baa0 100644 --- a/commonspace/object/acl/list/list_test.go +++ b/commonspace/object/acl/list/list_test.go @@ -56,10 +56,14 @@ func (fx *aclFixture) addRec(t *testing.T, rec *consensusproto.RawRecordWithId) func (fx *aclFixture) inviteAccount(t *testing.T, perms AclPermissions) { var ( - ownerAcl = fx.ownerAcl - ownerState = fx.ownerAcl.aclState + ownerAcl = fx.ownerAcl + ownerState = func() *AclState { + return ownerAcl.aclState + } accountAcl = fx.accountAcl - accountState = fx.accountAcl.aclState + accountState = func() *AclState { + return accountAcl.aclState + } ) // building invite inv, err := ownerAcl.RecordBuilder().BuildInvite() @@ -83,7 +87,7 @@ func (fx *aclFixture) inviteAccount(t *testing.T, perms AclPermissions) { }) require.NoError(t, err) // validate - err = ownerAcl.ValidateRawRecord(requestAccept) + err = ownerAcl.ValidateRawRecord(requestAccept, nil) require.NoError(t, err) requestAcceptRec := WrapAclRecord(requestAccept) fx.addRec(t, requestAcceptRec) @@ -95,10 +99,10 @@ func (fx *aclFixture) inviteAccount(t *testing.T, perms AclPermissions) { require.Equal(t, 0, len(acl.AclState().pendingRequests)) } - permsAtJoinRec, err := ownerState.PermissionsAtRecord(requestJoinRec.Id, accountState.pubKey) + permsAtJoinRec, err := ownerState().PermissionsAtRecord(requestJoinRec.Id, accountState().pubKey) require.NoError(t, err) require.Equal(t, AclPermissionsNone, permsAtJoinRec) - permsAtRec, err := ownerState.PermissionsAtRecord(requestAcceptRec.Id, accountState.pubKey) + permsAtRec, err := ownerState().PermissionsAtRecord(requestAcceptRec.Id, accountState().pubKey) require.NoError(t, err) require.True(t, permsAtRec == perms) require.Equal(t, ownerAcl.AclState().lastRecordId, requestAcceptRec.Id) @@ -122,8 +126,14 @@ func TestAclList_InvitePipeline(t *testing.T) { func TestAclList_InviteRevoke(t *testing.T) { fx := newFixture(t) var ( - ownerState = fx.ownerAcl.aclState - accountState = fx.accountAcl.aclState + ownerAcl = fx.ownerAcl + ownerState = func() *AclState { + return ownerAcl.aclState + } + accountAcl = fx.accountAcl + accountState = func() *AclState { + return accountAcl.aclState + } ) // building invite inv, err := fx.ownerAcl.RecordBuilder().BuildInvite() @@ -132,25 +142,29 @@ func TestAclList_InviteRevoke(t *testing.T) { fx.addRec(t, inviteRec) // building invite revoke - inviteRevoke, err := fx.ownerAcl.RecordBuilder().BuildInviteRevoke(ownerState.lastRecordId) + inviteRevoke, err := fx.ownerAcl.RecordBuilder().BuildInviteRevoke(ownerState().lastRecordId) require.NoError(t, err) inviteRevokeRec := WrapAclRecord(inviteRevoke) fx.addRec(t, inviteRevokeRec) // 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.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) } func TestAclList_RequestDecline(t *testing.T) { fx := newFixture(t) var ( - ownerAcl = fx.ownerAcl - ownerState = fx.ownerAcl.aclState + ownerAcl = fx.ownerAcl + ownerState = func() *AclState { + return ownerAcl.aclState + } accountAcl = fx.accountAcl - accountState = fx.accountAcl.aclState + accountState = func() *AclState { + return accountAcl.aclState + } ) // building invite inv, err := ownerAcl.RecordBuilder().BuildInvite() @@ -167,23 +181,29 @@ func TestAclList_RequestDecline(t *testing.T) { fx.addRec(t, requestJoinRec) // building request decline - requestDecline, err := ownerAcl.RecordBuilder().BuildRequestDecline(ownerState.lastRecordId) + requestDecline, err := ownerAcl.RecordBuilder().BuildRequestDecline(ownerState().lastRecordId) require.NoError(t, err) requestDeclineRec := WrapAclRecord(requestDecline) fx.addRec(t, requestDeclineRec) // checking acl state - require.True(t, ownerState.Permissions(ownerState.pubKey).IsOwner()) - require.True(t, ownerState.Permissions(accountState.pubKey).NoPermissions()) - require.Empty(t, ownerState.pendingRequests) - require.Empty(t, accountState.pendingRequests) + require.True(t, ownerState().Permissions(ownerState().pubKey).IsOwner()) + require.True(t, ownerState().Permissions(accountState().pubKey).NoPermissions()) + require.Empty(t, ownerState().pendingRequests) + require.Empty(t, accountState().pendingRequests) } func TestAclList_Remove(t *testing.T) { fx := newFixture(t) var ( - ownerState = fx.ownerAcl.aclState - accountState = fx.accountAcl.aclState + ownerAcl = fx.ownerAcl + ownerState = func() *AclState { + return ownerAcl.aclState + } + accountAcl = fx.accountAcl + accountState = func() *AclState { + return accountAcl.aclState + } ) fx.inviteAccount(t, AclPermissions(aclrecordproto.AclUserPermissions_Writer)) @@ -202,21 +222,21 @@ func TestAclList_Remove(t *testing.T) { fx.addRec(t, removeRec) // checking acl state - require.True(t, ownerState.Permissions(ownerState.pubKey).IsOwner()) - require.True(t, ownerState.Permissions(accountState.pubKey).NoPermissions()) - require.True(t, ownerState.keys[removeRec.Id].ReadKey.Equals(newReadKey)) - require.True(t, ownerState.keys[removeRec.Id].MetadataPrivKey.Equals(privKey)) - require.True(t, ownerState.keys[removeRec.Id].MetadataPubKey.Equals(pubKey)) - require.NotEmpty(t, ownerState.keys[fx.ownerAcl.Id()]) - require.Equal(t, 0, len(ownerState.pendingRequests)) - require.Equal(t, 0, len(accountState.pendingRequests)) - require.True(t, accountState.Permissions(ownerState.pubKey).IsOwner()) - require.True(t, accountState.Permissions(accountState.pubKey).NoPermissions()) - require.NotEmpty(t, accountState.keys[removeRec.Id]) - require.Nil(t, accountState.keys[removeRec.Id].MetadataPrivKey) - require.NotNil(t, accountState.keys[removeRec.Id].MetadataPubKey) - require.Nil(t, accountState.keys[removeRec.Id].ReadKey) - require.NotEmpty(t, accountState.keys[fx.ownerAcl.Id()]) + require.True(t, ownerState().Permissions(ownerState().pubKey).IsOwner()) + require.True(t, ownerState().Permissions(accountState().pubKey).NoPermissions()) + require.True(t, ownerState().keys[removeRec.Id].ReadKey.Equals(newReadKey)) + require.True(t, ownerState().keys[removeRec.Id].MetadataPrivKey.Equals(privKey)) + require.True(t, ownerState().keys[removeRec.Id].MetadataPubKey.Equals(pubKey)) + require.NotEmpty(t, ownerState().keys[fx.ownerAcl.Id()]) + require.Equal(t, 0, len(ownerState().pendingRequests)) + require.Equal(t, 0, len(accountState().pendingRequests)) + require.True(t, accountState().Permissions(ownerState().pubKey).IsOwner()) + require.True(t, accountState().Permissions(accountState().pubKey).NoPermissions()) + require.NotEmpty(t, accountState().keys[removeRec.Id]) + require.Nil(t, accountState().keys[removeRec.Id].MetadataPrivKey) + require.NotNil(t, accountState().keys[removeRec.Id].MetadataPubKey) + require.Nil(t, accountState().keys[removeRec.Id].ReadKey) + require.NotEmpty(t, accountState().keys[fx.ownerAcl.Id()]) } func TestAclList_FixAcceptPanic(t *testing.T) { @@ -256,8 +276,14 @@ func TestAclList_MetadataDecrypt(t *testing.T) { func TestAclList_ReadKeyChange(t *testing.T) { fx := newFixture(t) var ( - ownerState = fx.ownerAcl.aclState - accountState = fx.accountAcl.aclState + ownerAcl = fx.ownerAcl + ownerState = func() *AclState { + return ownerAcl.aclState + } + accountAcl = fx.accountAcl + accountState = func() *AclState { + return accountAcl.aclState + } ) fx.inviteAccount(t, AclPermissions(aclrecordproto.AclUserPermissions_Admin)) @@ -273,26 +299,32 @@ func TestAclList_ReadKeyChange(t *testing.T) { fx.addRec(t, readKeyRec) // checking acl state - require.True(t, ownerState.Permissions(ownerState.pubKey).IsOwner()) - require.True(t, ownerState.Permissions(accountState.pubKey).CanManageAccounts()) - require.True(t, ownerState.keys[readKeyRec.Id].ReadKey.Equals(newReadKey)) - require.True(t, ownerState.keys[readKeyRec.Id].MetadataPrivKey.Equals(privKey)) - require.True(t, ownerState.keys[readKeyRec.Id].MetadataPubKey.Equals(pubKey)) - require.True(t, accountState.keys[readKeyRec.Id].ReadKey.Equals(newReadKey)) - require.NotEmpty(t, ownerState.keys[fx.ownerAcl.Id()]) - require.NotEmpty(t, accountState.keys[fx.ownerAcl.Id()]) - readKey, err := ownerState.CurrentReadKey() + require.True(t, ownerState().Permissions(ownerState().pubKey).IsOwner()) + require.True(t, ownerState().Permissions(accountState().pubKey).CanManageAccounts()) + require.True(t, ownerState().keys[readKeyRec.Id].ReadKey.Equals(newReadKey)) + require.True(t, ownerState().keys[readKeyRec.Id].MetadataPrivKey.Equals(privKey)) + require.True(t, ownerState().keys[readKeyRec.Id].MetadataPubKey.Equals(pubKey)) + require.True(t, accountState().keys[readKeyRec.Id].ReadKey.Equals(newReadKey)) + require.NotEmpty(t, ownerState().keys[fx.ownerAcl.Id()]) + require.NotEmpty(t, accountState().keys[fx.ownerAcl.Id()]) + readKey, err := ownerState().CurrentReadKey() require.NoError(t, err) require.True(t, newReadKey.Equals(readKey)) - require.Equal(t, 0, len(ownerState.pendingRequests)) - require.Equal(t, 0, len(accountState.pendingRequests)) + require.Equal(t, 0, len(ownerState().pendingRequests)) + require.Equal(t, 0, len(accountState().pendingRequests)) } func TestAclList_PermissionChange(t *testing.T) { fx := newFixture(t) var ( - ownerState = fx.ownerAcl.aclState - accountState = fx.accountAcl.aclState + ownerAcl = fx.ownerAcl + ownerState = func() *AclState { + return ownerAcl.aclState + } + accountAcl = fx.accountAcl + accountState = func() *AclState { + return accountAcl.aclState + } ) fx.inviteAccount(t, AclPermissions(aclrecordproto.AclUserPermissions_Admin)) @@ -306,8 +338,8 @@ func TestAclList_PermissionChange(t *testing.T) { // checking acl state for _, acl := range []*aclList{fx.ownerAcl, fx.accountAcl} { - require.True(t, acl.AclState().Permissions(ownerState.pubKey).IsOwner()) - require.True(t, acl.AclState().Permissions(accountState.pubKey).CanWrite()) + require.True(t, acl.AclState().Permissions(ownerState().pubKey).IsOwner()) + require.True(t, acl.AclState().Permissions(accountState().pubKey).CanWrite()) require.Equal(t, 0, len(acl.AclState().pendingRequests)) require.NotEmpty(t, acl.AclState().keys[fx.ownerAcl.Id()]) } @@ -316,8 +348,14 @@ func TestAclList_PermissionChange(t *testing.T) { func TestAclList_RequestRemove(t *testing.T) { fx := newFixture(t) var ( - ownerState = fx.ownerAcl.aclState - accountState = fx.accountAcl.aclState + ownerAcl = fx.ownerAcl + ownerState = func() *AclState { + return ownerAcl.aclState + } + accountAcl = fx.accountAcl + accountState = func() *AclState { + return accountAcl.aclState + } ) fx.inviteAccount(t, AclPermissions(aclrecordproto.AclUserPermissions_Writer)) @@ -328,7 +366,7 @@ func TestAclList_RequestRemove(t *testing.T) { recs := fx.accountAcl.AclState().RemoveRecords() require.Len(t, recs, 1) - require.True(t, accountState.pubKey.Equals(recs[0].RequestIdentity)) + require.True(t, accountState().pubKey.Equals(recs[0].RequestIdentity)) newReadKey := crypto.NewAES() privKey, _, err := crypto.GenerateRandomEd25519KeyPair() @@ -346,14 +384,14 @@ func TestAclList_RequestRemove(t *testing.T) { // checking acl state for _, acl := range []*aclList{fx.ownerAcl, fx.accountAcl} { - require.True(t, acl.AclState().Permissions(ownerState.pubKey).IsOwner()) - require.True(t, acl.AclState().Permissions(accountState.pubKey).NoPermissions()) + require.True(t, acl.AclState().Permissions(ownerState().pubKey).IsOwner()) + require.True(t, acl.AclState().Permissions(accountState().pubKey).NoPermissions()) require.Equal(t, 0, len(acl.AclState().pendingRequests)) } - require.True(t, ownerState.keys[removeRec.Id].ReadKey.Equals(newReadKey)) - require.NotEmpty(t, ownerState.keys[fx.ownerAcl.Id()]) - require.Nil(t, accountState.keys[removeRec.Id].MetadataPrivKey) - require.NotNil(t, accountState.keys[removeRec.Id].MetadataPubKey) - require.Nil(t, accountState.keys[removeRec.Id].ReadKey) - require.NotEmpty(t, accountState.keys[fx.ownerAcl.Id()]) + require.True(t, ownerState().keys[removeRec.Id].ReadKey.Equals(newReadKey)) + require.NotEmpty(t, ownerState().keys[fx.ownerAcl.Id()]) + require.Nil(t, accountState().keys[removeRec.Id].MetadataPrivKey) + require.NotNil(t, accountState().keys[removeRec.Id].MetadataPubKey) + require.Nil(t, accountState().keys[removeRec.Id].ReadKey) + require.NotEmpty(t, accountState().keys[fx.ownerAcl.Id()]) } diff --git a/commonspace/object/acl/list/mock_list/mock_list.go b/commonspace/object/acl/list/mock_list/mock_list.go index e005ad71..4f965735 100644 --- a/commonspace/object/acl/list/mock_list/mock_list.go +++ b/commonspace/object/acl/list/mock_list/mock_list.go @@ -5,7 +5,6 @@ // // mockgen -destination mock_list/mock_list.go github.com/anyproto/any-sync/commonspace/object/acl/list AclList // - // Package mock_list is a generated GoMock package. package mock_list @@ -344,15 +343,15 @@ func (mr *MockAclListMockRecorder) Unlock() *gomock.Call { } // ValidateRawRecord mocks base method. -func (m *MockAclList) ValidateRawRecord(arg0 *consensusproto.RawRecord) error { +func (m *MockAclList) ValidateRawRecord(arg0 *consensusproto.RawRecord, arg1 func(*list.AclState) error) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ValidateRawRecord", arg0) + ret := m.ctrl.Call(m, "ValidateRawRecord", arg0, arg1) ret0, _ := ret[0].(error) return ret0 } // ValidateRawRecord indicates an expected call of ValidateRawRecord. -func (mr *MockAclListMockRecorder) ValidateRawRecord(arg0 any) *gomock.Call { +func (mr *MockAclListMockRecorder) ValidateRawRecord(arg0, arg1 any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ValidateRawRecord", reflect.TypeOf((*MockAclList)(nil).ValidateRawRecord), arg0) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ValidateRawRecord", reflect.TypeOf((*MockAclList)(nil).ValidateRawRecord), arg0, arg1) } diff --git a/commonspace/object/acl/list/validator.go b/commonspace/object/acl/list/validator.go index 61dd18cb..ddd4ef50 100644 --- a/commonspace/object/acl/list/validator.go +++ b/commonspace/object/acl/list/validator.go @@ -26,6 +26,13 @@ type contentValidator struct { aclState *AclState } +func newContentValidator(keyStore crypto.KeyStorage, aclState *AclState) ContentValidator { + return &contentValidator{ + keyStore: keyStore, + aclState: aclState, + } +} + func (c *contentValidator) ValidatePermissionChanges(ch *aclrecordproto.AclAccountPermissionChanges, authorIdentity crypto.PubKey) (err error) { for _, ch := range ch.Changes { err := c.ValidatePermissionChange(ch, authorIdentity) diff --git a/commonspace/object/acl/syncacl/mock_syncacl/mock_syncacl.go b/commonspace/object/acl/syncacl/mock_syncacl/mock_syncacl.go index 19603ad1..8ff0c633 100644 --- a/commonspace/object/acl/syncacl/mock_syncacl/mock_syncacl.go +++ b/commonspace/object/acl/syncacl/mock_syncacl/mock_syncacl.go @@ -5,7 +5,6 @@ // // mockgen -destination mock_syncacl/mock_syncacl.go github.com/anyproto/any-sync/commonspace/object/acl/syncacl SyncAcl,SyncClient,RequestFactory,AclSyncProtocol // - // Package mock_syncacl is a generated GoMock package. package mock_syncacl @@ -456,17 +455,17 @@ func (mr *MockSyncAclMockRecorder) Unlock() *gomock.Call { } // ValidateRawRecord mocks base method. -func (m *MockSyncAcl) ValidateRawRecord(arg0 *consensusproto.RawRecord) error { +func (m *MockSyncAcl) ValidateRawRecord(arg0 *consensusproto.RawRecord, arg1 func(*list.AclState) error) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ValidateRawRecord", arg0) + ret := m.ctrl.Call(m, "ValidateRawRecord", arg0, arg1) ret0, _ := ret[0].(error) return ret0 } // ValidateRawRecord indicates an expected call of ValidateRawRecord. -func (mr *MockSyncAclMockRecorder) ValidateRawRecord(arg0 any) *gomock.Call { +func (mr *MockSyncAclMockRecorder) ValidateRawRecord(arg0, arg1 any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ValidateRawRecord", reflect.TypeOf((*MockSyncAcl)(nil).ValidateRawRecord), arg0) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ValidateRawRecord", reflect.TypeOf((*MockSyncAcl)(nil).ValidateRawRecord), arg0, arg1) } // MockSyncClient is a mock of SyncClient interface. diff --git a/commonspace/object/tree/objecttree/objecttree_test.go b/commonspace/object/tree/objecttree/objecttree_test.go index d65d15fc..a93176e6 100644 --- a/commonspace/object/tree/objecttree/objecttree_test.go +++ b/commonspace/object/tree/objecttree/objecttree_test.go @@ -128,10 +128,10 @@ func TestObjectTree(t *testing.T) { err error } cmds := []cmdErr{ - {"a.init:a", nil}, - {"a.invite:invId", nil}, - {"b.join:invId", nil}, - {"a.approve:b,r", nil}, + {"a.init::a", nil}, + {"a.invite::invId", nil}, + {"b.join::invId", nil}, + {"a.approve::b,r", nil}, } for _, cmd := range cmds { err := exec.Execute(cmd.cmd) @@ -161,7 +161,7 @@ func TestObjectTree(t *testing.T) { bStore := aTree.Storage().(*treestorage.InMemoryTreeStorage).Copy() bTree, err := BuildKeyFilterableObjectTree(bStore, bAccount.Acl) require.NoError(t, err) - err = exec.Execute("a.remove:b") + err = exec.Execute("a.remove::b") require.NoError(t, err) res, err := aTree.AddContent(ctx, SignableChangeContent{ Data: []byte("some"),