merge
This commit is contained in:
commit
1a23081336
@ -4,18 +4,19 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/anyproto/any-sync/app/ldiff"
|
"github.com/anyproto/any-sync/app/ldiff"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/acl/aclrecordproto"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/object/acl/liststorage/mock_liststorage"
|
"github.com/anyproto/any-sync/commonspace/object/acl/liststorage/mock_liststorage"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage/mock_treestorage"
|
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage/mock_treestorage"
|
||||||
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
||||||
|
"github.com/anyproto/any-sync/consensus/consensusproto"
|
||||||
"github.com/anyproto/any-sync/net/peer"
|
"github.com/anyproto/any-sync/net/peer"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"go.uber.org/mock/gomock"
|
"go.uber.org/mock/gomock"
|
||||||
"storj.io/drpc"
|
"storj.io/drpc"
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type pushSpaceRequestMatcher struct {
|
type pushSpaceRequestMatcher struct {
|
||||||
@ -169,7 +170,7 @@ func TestDiffSyncer(t *testing.T) {
|
|||||||
settingsStorage := mock_treestorage.NewMockTreeStorage(fx.ctrl)
|
settingsStorage := mock_treestorage.NewMockTreeStorage(fx.ctrl)
|
||||||
settingsId := "settingsId"
|
settingsId := "settingsId"
|
||||||
aclRootId := "aclRootId"
|
aclRootId := "aclRootId"
|
||||||
aclRoot := &aclrecordproto.RawAclRecordWithId{
|
aclRoot := &consensusproto.RawRecordWithId{
|
||||||
Id: aclRootId,
|
Id: aclRootId,
|
||||||
}
|
}
|
||||||
settingsRoot := &treechangeproto.RawTreeChangeWithId{
|
settingsRoot := &treechangeproto.RawTreeChangeWithId{
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -2,26 +2,7 @@ syntax = "proto3";
|
|||||||
package aclrecord;
|
package aclrecord;
|
||||||
option go_package = "commonspace/object/acl/aclrecordproto";
|
option go_package = "commonspace/object/acl/aclrecordproto";
|
||||||
|
|
||||||
message RawAclRecord {
|
// AclRoot is a root of access control list
|
||||||
bytes payload = 1;
|
|
||||||
bytes signature = 2;
|
|
||||||
bytes acceptorIdentity = 3;
|
|
||||||
bytes acceptorSignature = 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
message RawAclRecordWithId {
|
|
||||||
bytes payload = 1;
|
|
||||||
string id = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message AclRecord {
|
|
||||||
string prevId = 1;
|
|
||||||
bytes identity = 2;
|
|
||||||
bytes data = 3;
|
|
||||||
string readKeyId = 4;
|
|
||||||
int64 timestamp = 5;
|
|
||||||
}
|
|
||||||
|
|
||||||
message AclRoot {
|
message AclRoot {
|
||||||
bytes identity = 1;
|
bytes identity = 1;
|
||||||
bytes masterKey = 2;
|
bytes masterKey = 2;
|
||||||
@ -31,82 +12,95 @@ message AclRoot {
|
|||||||
bytes identitySignature = 6;
|
bytes identitySignature = 6;
|
||||||
}
|
}
|
||||||
|
|
||||||
message AclContentValue {
|
// AclAccountInvite contains the public invite key, the private part of which is sent to the user directly
|
||||||
oneof value {
|
message AclAccountInvite {
|
||||||
AclUserAdd userAdd = 1;
|
bytes inviteKey = 1;
|
||||||
AclUserRemove userRemove = 2;
|
|
||||||
AclUserPermissionChange userPermissionChange = 3;
|
|
||||||
AclUserInvite userInvite = 4;
|
|
||||||
AclUserJoin userJoin = 5;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
message AclData {
|
// AclAccountRequestJoin contains the reference to the invite record and the data of the person who wants to join, confirmed by the private invite key
|
||||||
repeated AclContentValue aclContent = 1;
|
message AclAccountRequestJoin {
|
||||||
|
bytes inviteIdentity = 1;
|
||||||
|
string inviteRecordId = 2;
|
||||||
|
bytes inviteIdentitySignature = 3;
|
||||||
|
bytes metadata = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
message AclState {
|
// AclAccountRequestAccept contains the reference to join record and all read keys, encrypted with the identity of the requestor
|
||||||
repeated string readKeyIds = 1;
|
message AclAccountRequestAccept {
|
||||||
repeated AclUserState userStates = 2;
|
|
||||||
map<string, AclUserInvite> invites = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
message AclUserState {
|
|
||||||
bytes identity = 1;
|
bytes identity = 1;
|
||||||
AclUserPermissions permissions = 2;
|
string requestRecordId = 2;
|
||||||
|
repeated AclReadKeyWithRecord encryptedReadKeys = 3;
|
||||||
|
AclUserPermissions permissions = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
message AclUserAdd {
|
// AclAccountRequestDecline contains the reference to join record
|
||||||
bytes identity = 1;
|
message AclAccountRequestDecline {
|
||||||
repeated bytes encryptedReadKeys = 2;
|
string requestRecordId = 1;
|
||||||
AclUserPermissions permissions = 3;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
message AclUserInvite {
|
// AclAccountInviteRevoke revokes the invite record
|
||||||
bytes acceptPublicKey = 1;
|
message AclAccountInviteRevoke {
|
||||||
repeated bytes encryptedReadKeys = 2;
|
string inviteRecordId = 1;
|
||||||
AclUserPermissions permissions = 3;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
message AclUserJoin {
|
// AclReadKeys are a read key with record id
|
||||||
bytes identity = 1;
|
message AclReadKeyWithRecord {
|
||||||
bytes acceptSignature = 2;
|
string recordId = 1;
|
||||||
bytes acceptPubKey = 3;
|
bytes encryptedReadKey = 2;
|
||||||
repeated bytes encryptedReadKeys = 4;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
message AclUserRemove {
|
// AclEncryptedReadKeys are new key for specific identity
|
||||||
bytes identity = 1;
|
message AclEncryptedReadKey {
|
||||||
repeated AclReadKeyReplace readKeyReplaces = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message AclReadKeyReplace {
|
|
||||||
bytes identity = 1;
|
bytes identity = 1;
|
||||||
bytes encryptedReadKey = 2;
|
bytes encryptedReadKey = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
message AclUserPermissionChange {
|
// AclAccountPermissionChange changes permissions of specific account
|
||||||
|
message AclAccountPermissionChange {
|
||||||
bytes identity = 1;
|
bytes identity = 1;
|
||||||
AclUserPermissions permissions = 2;
|
AclUserPermissions permissions = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
enum AclUserPermissions {
|
// AclReadKeyChange changes the key for a space
|
||||||
Admin = 0;
|
message AclReadKeyChange {
|
||||||
Writer = 1;
|
repeated AclEncryptedReadKey accountKeys = 1;
|
||||||
Reader = 2;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
message AclSyncMessage {
|
// AclAccountRemove removes an account and changes read key for space
|
||||||
AclSyncContentValue content = 1;
|
message AclAccountRemove {
|
||||||
|
repeated bytes identities = 1;
|
||||||
|
repeated AclEncryptedReadKey accountKeys = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
// AclSyncContentValue provides different types for acl sync
|
// AclAccountRequestRemove adds a request to remove an account
|
||||||
message AclSyncContentValue {
|
message AclAccountRequestRemove {
|
||||||
|
}
|
||||||
|
|
||||||
|
// AclContentValue contains possible values for Acl
|
||||||
|
message AclContentValue {
|
||||||
oneof value {
|
oneof value {
|
||||||
AclAddRecords addRecords = 1;
|
AclAccountInvite invite = 1;
|
||||||
|
AclAccountInviteRevoke inviteRevoke = 2;
|
||||||
|
AclAccountRequestJoin requestJoin = 3;
|
||||||
|
AclAccountRequestAccept requestAccept = 4;
|
||||||
|
AclAccountPermissionChange permissionChange = 5;
|
||||||
|
AclAccountRemove accountRemove = 6;
|
||||||
|
AclReadKeyChange readKeyChange = 7;
|
||||||
|
AclAccountRequestDecline requestDecline = 8;
|
||||||
|
AclAccountRequestRemove accountRequestRemove = 9;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
message AclAddRecords {
|
// AclData contains different acl content
|
||||||
repeated RawAclRecordWithId records = 1;
|
message AclData {
|
||||||
|
repeated AclContentValue aclContent = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// AclUserPermissions contains different possible user roles
|
||||||
|
enum AclUserPermissions {
|
||||||
|
None = 0;
|
||||||
|
Owner = 1;
|
||||||
|
Admin = 2;
|
||||||
|
Writer = 3;
|
||||||
|
Reader = 4;
|
||||||
}
|
}
|
||||||
@ -1,11 +1,14 @@
|
|||||||
package list
|
package list
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"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/aclrecordproto"
|
||||||
|
"github.com/anyproto/any-sync/consensus/consensusproto"
|
||||||
"github.com/anyproto/any-sync/util/cidutil"
|
"github.com/anyproto/any-sync/util/cidutil"
|
||||||
"github.com/anyproto/any-sync/util/crypto"
|
"github.com/anyproto/any-sync/util/crypto"
|
||||||
"github.com/gogo/protobuf/proto"
|
"github.com/gogo/protobuf/proto"
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type RootContent struct {
|
type RootContent struct {
|
||||||
@ -15,26 +18,387 @@ type RootContent struct {
|
|||||||
EncryptedReadKey []byte
|
EncryptedReadKey []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type RequestJoinPayload struct {
|
||||||
|
InviteRecordId string
|
||||||
|
InviteKey crypto.PrivKey
|
||||||
|
Metadata []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
type RequestAcceptPayload struct {
|
||||||
|
RequestRecordId string
|
||||||
|
Permissions AclPermissions
|
||||||
|
}
|
||||||
|
|
||||||
|
type PermissionChangePayload struct {
|
||||||
|
Identity crypto.PubKey
|
||||||
|
Permissions AclPermissions
|
||||||
|
}
|
||||||
|
|
||||||
|
type AccountRemovePayload struct {
|
||||||
|
Identities []crypto.PubKey
|
||||||
|
ReadKey crypto.SymKey
|
||||||
|
}
|
||||||
|
|
||||||
|
type InviteResult struct {
|
||||||
|
InviteRec *consensusproto.RawRecord
|
||||||
|
InviteKey crypto.PrivKey
|
||||||
|
}
|
||||||
|
|
||||||
type AclRecordBuilder interface {
|
type AclRecordBuilder interface {
|
||||||
Unmarshall(rawIdRecord *aclrecordproto.RawAclRecordWithId) (rec *AclRecord, err error)
|
UnmarshallWithId(rawIdRecord *consensusproto.RawRecordWithId) (rec *AclRecord, err error)
|
||||||
BuildRoot(content RootContent) (rec *aclrecordproto.RawAclRecordWithId, err error)
|
Unmarshall(rawRecord *consensusproto.RawRecord) (rec *AclRecord, err error)
|
||||||
|
|
||||||
|
BuildRoot(content RootContent) (rec *consensusproto.RawRecordWithId, err error)
|
||||||
|
BuildInvite() (res InviteResult, err error)
|
||||||
|
BuildInviteRevoke(inviteRecordId string) (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)
|
||||||
|
BuildRequestRemove() (rawRecord *consensusproto.RawRecord, err error)
|
||||||
|
BuildPermissionChange(payload PermissionChangePayload) (rawRecord *consensusproto.RawRecord, err error)
|
||||||
|
BuildReadKeyChange(newKey crypto.SymKey) (rawRecord *consensusproto.RawRecord, err error)
|
||||||
|
BuildAccountRemove(payload AccountRemovePayload) (rawRecord *consensusproto.RawRecord, err error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type aclRecordBuilder struct {
|
type aclRecordBuilder struct {
|
||||||
id string
|
id string
|
||||||
keyStorage crypto.KeyStorage
|
keyStorage crypto.KeyStorage
|
||||||
|
accountKeys *accountdata.AccountKeys
|
||||||
|
verifier AcceptorVerifier
|
||||||
|
state *AclState
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAclRecordBuilder(id string, keyStorage crypto.KeyStorage) AclRecordBuilder {
|
func NewAclRecordBuilder(id string, keyStorage crypto.KeyStorage, keys *accountdata.AccountKeys, verifier AcceptorVerifier) AclRecordBuilder {
|
||||||
return &aclRecordBuilder{
|
return &aclRecordBuilder{
|
||||||
id: id,
|
id: id,
|
||||||
keyStorage: keyStorage,
|
keyStorage: keyStorage,
|
||||||
|
accountKeys: keys,
|
||||||
|
verifier: verifier,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *aclRecordBuilder) Unmarshall(rawIdRecord *aclrecordproto.RawAclRecordWithId) (rec *AclRecord, err error) {
|
func (a *aclRecordBuilder) buildRecord(aclContent *aclrecordproto.AclContentValue) (rawRec *consensusproto.RawRecord, err error) {
|
||||||
|
aclData := &aclrecordproto.AclData{AclContent: []*aclrecordproto.AclContentValue{
|
||||||
|
aclContent,
|
||||||
|
}}
|
||||||
|
marshalledData, err := aclData.Marshal()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
protoKey, err := a.accountKeys.SignKey.GetPublic().Marshall()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
rec := &consensusproto.Record{
|
||||||
|
PrevId: a.state.lastRecordId,
|
||||||
|
Identity: protoKey,
|
||||||
|
Data: marshalledData,
|
||||||
|
Timestamp: time.Now().Unix(),
|
||||||
|
}
|
||||||
|
marshalledRec, err := rec.Marshal()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
signature, err := a.accountKeys.SignKey.Sign(marshalledRec)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
rawRec = &consensusproto.RawRecord{
|
||||||
|
Payload: marshalledRec,
|
||||||
|
Signature: signature,
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *aclRecordBuilder) BuildInvite() (res InviteResult, 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
|
||||||
|
}
|
||||||
|
inviteRec := &aclrecordproto.AclAccountInvite{InviteKey: invitePubKey}
|
||||||
|
content := &aclrecordproto.AclContentValue{Value: &aclrecordproto.AclContentValue_Invite{Invite: inviteRec}}
|
||||||
|
rawRec, err := a.buildRecord(content)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
res.InviteKey = privKey
|
||||||
|
res.InviteRec = rawRec
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *aclRecordBuilder) BuildInviteRevoke(inviteRecordId string) (rawRecord *consensusproto.RawRecord, err error) {
|
||||||
|
if !a.state.Permissions(a.state.pubKey).CanManageAccounts() {
|
||||||
|
err = ErrInsufficientPermissions
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, exists := a.state.inviteKeys[inviteRecordId]
|
||||||
|
if !exists {
|
||||||
|
err = ErrNoSuchInvite
|
||||||
|
return
|
||||||
|
}
|
||||||
|
revokeRec := &aclrecordproto.AclAccountInviteRevoke{InviteRecordId: inviteRecordId}
|
||||||
|
content := &aclrecordproto.AclContentValue{Value: &aclrecordproto.AclContentValue_InviteRevoke{InviteRevoke: revokeRec}}
|
||||||
|
return a.buildRecord(content)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *aclRecordBuilder) BuildRequestJoin(payload RequestJoinPayload) (rawRecord *consensusproto.RawRecord, err error) {
|
||||||
|
key, exists := a.state.inviteKeys[payload.InviteRecordId]
|
||||||
|
if !exists {
|
||||||
|
err = ErrNoSuchInvite
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !payload.InviteKey.GetPublic().Equals(key) {
|
||||||
|
err = ErrIncorrectInviteKey
|
||||||
|
}
|
||||||
|
rawIdentity, err := a.accountKeys.SignKey.GetPublic().Raw()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
signature, err := payload.InviteKey.Sign(rawIdentity)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
protoIdentity, err := a.accountKeys.SignKey.GetPublic().Marshall()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
joinRec := &aclrecordproto.AclAccountRequestJoin{
|
||||||
|
InviteIdentity: protoIdentity,
|
||||||
|
InviteRecordId: payload.InviteRecordId,
|
||||||
|
InviteIdentitySignature: signature,
|
||||||
|
Metadata: payload.Metadata,
|
||||||
|
}
|
||||||
|
content := &aclrecordproto.AclContentValue{Value: &aclrecordproto.AclContentValue_RequestJoin{RequestJoin: joinRec}}
|
||||||
|
return a.buildRecord(content)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *aclRecordBuilder) BuildRequestAccept(payload RequestAcceptPayload) (rawRecord *consensusproto.RawRecord, err error) {
|
||||||
|
if !a.state.Permissions(a.state.pubKey).CanManageAccounts() {
|
||||||
|
err = ErrInsufficientPermissions
|
||||||
|
return
|
||||||
|
}
|
||||||
|
request, exists := a.state.requestRecords[payload.RequestRecordId]
|
||||||
|
if !exists {
|
||||||
|
err = ErrNoSuchRequest
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var encryptedReadKeys []*aclrecordproto.AclReadKeyWithRecord
|
||||||
|
for keyId, key := range a.state.userReadKeys {
|
||||||
|
rawKey, err := key.Raw()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
enc, err := request.RequestIdentity.Encrypt(rawKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
encryptedReadKeys = append(encryptedReadKeys, &aclrecordproto.AclReadKeyWithRecord{
|
||||||
|
RecordId: keyId,
|
||||||
|
EncryptedReadKey: enc,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
requestIdentityProto, err := request.RequestIdentity.Marshall()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
acceptRec := &aclrecordproto.AclAccountRequestAccept{
|
||||||
|
Identity: requestIdentityProto,
|
||||||
|
RequestRecordId: payload.RequestRecordId,
|
||||||
|
EncryptedReadKeys: encryptedReadKeys,
|
||||||
|
Permissions: aclrecordproto.AclUserPermissions(payload.Permissions),
|
||||||
|
}
|
||||||
|
content := &aclrecordproto.AclContentValue{Value: &aclrecordproto.AclContentValue_RequestAccept{RequestAccept: acceptRec}}
|
||||||
|
return a.buildRecord(content)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *aclRecordBuilder) BuildRequestDecline(requestRecordId string) (rawRecord *consensusproto.RawRecord, err error) {
|
||||||
|
if !a.state.Permissions(a.state.pubKey).CanManageAccounts() {
|
||||||
|
err = ErrInsufficientPermissions
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, exists := a.state.requestRecords[requestRecordId]
|
||||||
|
if !exists {
|
||||||
|
err = ErrNoSuchRequest
|
||||||
|
return
|
||||||
|
}
|
||||||
|
declineRec := &aclrecordproto.AclAccountRequestDecline{RequestRecordId: requestRecordId}
|
||||||
|
content := &aclrecordproto.AclContentValue{Value: &aclrecordproto.AclContentValue_RequestDecline{RequestDecline: declineRec}}
|
||||||
|
return a.buildRecord(content)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *aclRecordBuilder) BuildPermissionChange(payload PermissionChangePayload) (rawRecord *consensusproto.RawRecord, err error) {
|
||||||
|
permissions := a.state.Permissions(a.state.pubKey)
|
||||||
|
if !permissions.CanManageAccounts() || payload.Identity.Equals(a.state.pubKey) {
|
||||||
|
err = ErrInsufficientPermissions
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if payload.Permissions.IsOwner() {
|
||||||
|
err = ErrIsOwner
|
||||||
|
return
|
||||||
|
}
|
||||||
|
protoIdentity, err := payload.Identity.Marshall()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
permissionRec := &aclrecordproto.AclAccountPermissionChange{
|
||||||
|
Identity: protoIdentity,
|
||||||
|
Permissions: aclrecordproto.AclUserPermissions(payload.Permissions),
|
||||||
|
}
|
||||||
|
content := &aclrecordproto.AclContentValue{Value: &aclrecordproto.AclContentValue_PermissionChange{PermissionChange: permissionRec}}
|
||||||
|
return a.buildRecord(content)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *aclRecordBuilder) BuildReadKeyChange(newKey crypto.SymKey) (rawRecord *consensusproto.RawRecord, err error) {
|
||||||
|
if !a.state.Permissions(a.state.pubKey).CanManageAccounts() {
|
||||||
|
err = ErrInsufficientPermissions
|
||||||
|
return
|
||||||
|
}
|
||||||
|
rawKey, err := newKey.Raw()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(rawKey) != crypto.KeyBytes {
|
||||||
|
err = ErrIncorrectReadKey
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var aclReadKeys []*aclrecordproto.AclEncryptedReadKey
|
||||||
|
for _, st := range a.state.userStates {
|
||||||
|
protoIdentity, err := st.PubKey.Marshall()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
enc, err := st.PubKey.Encrypt(rawKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
aclReadKeys = append(aclReadKeys, &aclrecordproto.AclEncryptedReadKey{
|
||||||
|
Identity: protoIdentity,
|
||||||
|
EncryptedReadKey: enc,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
readRec := &aclrecordproto.AclReadKeyChange{AccountKeys: aclReadKeys}
|
||||||
|
content := &aclrecordproto.AclContentValue{Value: &aclrecordproto.AclContentValue_ReadKeyChange{ReadKeyChange: readRec}}
|
||||||
|
return a.buildRecord(content)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *aclRecordBuilder) BuildAccountRemove(payload AccountRemovePayload) (rawRecord *consensusproto.RawRecord, err error) {
|
||||||
|
deletedMap := map[string]struct{}{}
|
||||||
|
for _, key := range payload.Identities {
|
||||||
|
permissions := a.state.Permissions(key)
|
||||||
|
if permissions.IsOwner() {
|
||||||
|
return nil, ErrInsufficientPermissions
|
||||||
|
}
|
||||||
|
if permissions.NoPermissions() {
|
||||||
|
return nil, ErrNoSuchAccount
|
||||||
|
}
|
||||||
|
deletedMap[mapKeyFromPubKey(key)] = struct{}{}
|
||||||
|
}
|
||||||
|
if !a.state.Permissions(a.state.pubKey).CanManageAccounts() {
|
||||||
|
err = ErrInsufficientPermissions
|
||||||
|
return
|
||||||
|
}
|
||||||
|
rawKey, err := payload.ReadKey.Raw()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(rawKey) != crypto.KeyBytes {
|
||||||
|
err = ErrIncorrectReadKey
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var aclReadKeys []*aclrecordproto.AclEncryptedReadKey
|
||||||
|
for _, st := range a.state.userStates {
|
||||||
|
if _, exists := deletedMap[mapKeyFromPubKey(st.PubKey)]; exists {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
protoIdentity, err := st.PubKey.Marshall()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
enc, err := st.PubKey.Encrypt(rawKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
aclReadKeys = append(aclReadKeys, &aclrecordproto.AclEncryptedReadKey{
|
||||||
|
Identity: protoIdentity,
|
||||||
|
EncryptedReadKey: enc,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
var marshalledIdentities [][]byte
|
||||||
|
for _, key := range payload.Identities {
|
||||||
|
protoIdentity, err := key.Marshall()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
marshalledIdentities = append(marshalledIdentities, protoIdentity)
|
||||||
|
}
|
||||||
|
removeRec := &aclrecordproto.AclAccountRemove{AccountKeys: aclReadKeys, Identities: marshalledIdentities}
|
||||||
|
content := &aclrecordproto.AclContentValue{Value: &aclrecordproto.AclContentValue_AccountRemove{AccountRemove: removeRec}}
|
||||||
|
return a.buildRecord(content)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *aclRecordBuilder) BuildRequestRemove() (rawRecord *consensusproto.RawRecord, err error) {
|
||||||
|
permissions := a.state.Permissions(a.state.pubKey)
|
||||||
|
if permissions.NoPermissions() {
|
||||||
|
err = ErrNoSuchAccount
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if permissions.IsOwner() {
|
||||||
|
err = ErrIsOwner
|
||||||
|
return
|
||||||
|
}
|
||||||
|
removeRec := &aclrecordproto.AclAccountRequestRemove{}
|
||||||
|
content := &aclrecordproto.AclContentValue{Value: &aclrecordproto.AclContentValue_AccountRequestRemove{AccountRequestRemove: removeRec}}
|
||||||
|
return a.buildRecord(content)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *aclRecordBuilder) Unmarshall(rawRecord *consensusproto.RawRecord) (rec *AclRecord, err error) {
|
||||||
|
aclRecord := &consensusproto.Record{}
|
||||||
|
err = proto.Unmarshal(rawRecord.Payload, aclRecord)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
pubKey, err := a.keyStorage.PubKeyFromProto(aclRecord.Identity)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
aclData := &aclrecordproto.AclData{}
|
||||||
|
err = proto.Unmarshal(aclRecord.Data, aclData)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
rec = &AclRecord{
|
||||||
|
PrevId: aclRecord.PrevId,
|
||||||
|
Timestamp: aclRecord.Timestamp,
|
||||||
|
Data: aclRecord.Data,
|
||||||
|
Signature: rawRecord.Signature,
|
||||||
|
Identity: pubKey,
|
||||||
|
Model: aclData,
|
||||||
|
}
|
||||||
|
res, err := pubKey.Verify(rawRecord.Payload, rawRecord.Signature)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !res {
|
||||||
|
err = ErrInvalidSignature
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *aclRecordBuilder) UnmarshallWithId(rawIdRecord *consensusproto.RawRecordWithId) (rec *AclRecord, err error) {
|
||||||
var (
|
var (
|
||||||
rawRec = &aclrecordproto.RawAclRecord{}
|
rawRec = &consensusproto.RawRecord{}
|
||||||
pubKey crypto.PubKey
|
pubKey crypto.PubKey
|
||||||
)
|
)
|
||||||
err = proto.Unmarshal(rawIdRecord.Payload, rawRec)
|
err = proto.Unmarshal(rawIdRecord.Payload, rawRec)
|
||||||
@ -53,14 +417,17 @@ func (a *aclRecordBuilder) Unmarshall(rawIdRecord *aclrecordproto.RawAclRecordWi
|
|||||||
}
|
}
|
||||||
rec = &AclRecord{
|
rec = &AclRecord{
|
||||||
Id: rawIdRecord.Id,
|
Id: rawIdRecord.Id,
|
||||||
ReadKeyId: rawIdRecord.Id,
|
|
||||||
Timestamp: aclRoot.Timestamp,
|
Timestamp: aclRoot.Timestamp,
|
||||||
Signature: rawRec.Signature,
|
Signature: rawRec.Signature,
|
||||||
Identity: pubKey,
|
Identity: pubKey,
|
||||||
Model: aclRoot,
|
Model: aclRoot,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
aclRecord := &aclrecordproto.AclRecord{}
|
err = a.verifier.VerifyAcceptor(rawRec)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
aclRecord := &consensusproto.Record{}
|
||||||
err = proto.Unmarshal(rawRec.Payload, aclRecord)
|
err = proto.Unmarshal(rawRec.Payload, aclRecord)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
@ -69,14 +436,19 @@ func (a *aclRecordBuilder) Unmarshall(rawIdRecord *aclrecordproto.RawAclRecordWi
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
aclData := &aclrecordproto.AclData{}
|
||||||
|
err = proto.Unmarshal(aclRecord.Data, aclData)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
rec = &AclRecord{
|
rec = &AclRecord{
|
||||||
Id: rawIdRecord.Id,
|
Id: rawIdRecord.Id,
|
||||||
PrevId: aclRecord.PrevId,
|
PrevId: aclRecord.PrevId,
|
||||||
ReadKeyId: aclRecord.ReadKeyId,
|
|
||||||
Timestamp: aclRecord.Timestamp,
|
Timestamp: aclRecord.Timestamp,
|
||||||
Data: aclRecord.Data,
|
Data: aclRecord.Data,
|
||||||
Signature: rawRec.Signature,
|
Signature: rawRec.Signature,
|
||||||
Identity: pubKey,
|
Identity: pubKey,
|
||||||
|
Model: aclData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -84,7 +456,7 @@ func (a *aclRecordBuilder) Unmarshall(rawIdRecord *aclrecordproto.RawAclRecordWi
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *aclRecordBuilder) BuildRoot(content RootContent) (rec *aclrecordproto.RawAclRecordWithId, err error) {
|
func (a *aclRecordBuilder) BuildRoot(content RootContent) (rec *consensusproto.RawRecordWithId, err error) {
|
||||||
rawIdentity, err := content.PrivKey.GetPublic().Raw()
|
rawIdentity, err := content.PrivKey.GetPublic().Raw()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
@ -118,8 +490,8 @@ func (a *aclRecordBuilder) BuildRoot(content RootContent) (rec *aclrecordproto.R
|
|||||||
|
|
||||||
func verifyRaw(
|
func verifyRaw(
|
||||||
pubKey crypto.PubKey,
|
pubKey crypto.PubKey,
|
||||||
rawRec *aclrecordproto.RawAclRecord,
|
rawRec *consensusproto.RawRecord,
|
||||||
recWithId *aclrecordproto.RawAclRecordWithId) (err error) {
|
recWithId *consensusproto.RawRecordWithId) (err error) {
|
||||||
// verifying signature
|
// verifying signature
|
||||||
res, err := pubKey.Verify(rawRec.Payload, rawRec.Signature)
|
res, err := pubKey.Verify(rawRec.Payload, rawRec.Signature)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -137,7 +509,7 @@ func verifyRaw(
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func marshalAclRoot(aclRoot *aclrecordproto.AclRoot, key crypto.PrivKey) (rawWithId *aclrecordproto.RawAclRecordWithId, err error) {
|
func marshalAclRoot(aclRoot *aclrecordproto.AclRoot, key crypto.PrivKey) (rawWithId *consensusproto.RawRecordWithId, err error) {
|
||||||
marshalledRoot, err := aclRoot.Marshal()
|
marshalledRoot, err := aclRoot.Marshal()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
@ -146,7 +518,7 @@ func marshalAclRoot(aclRoot *aclrecordproto.AclRoot, key crypto.PrivKey) (rawWit
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
raw := &aclrecordproto.RawAclRecord{
|
raw := &consensusproto.RawRecord{
|
||||||
Payload: marshalledRoot,
|
Payload: marshalledRoot,
|
||||||
Signature: signature,
|
Signature: signature,
|
||||||
}
|
}
|
||||||
@ -158,7 +530,7 @@ func marshalAclRoot(aclRoot *aclrecordproto.AclRoot, key crypto.PrivKey) (rawWit
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
rawWithId = &aclrecordproto.RawAclRecordWithId{
|
rawWithId = &consensusproto.RawRecordWithId{
|
||||||
Payload: marshalledRaw,
|
Payload: marshalledRaw,
|
||||||
Id: aclHeadId,
|
Id: aclHeadId,
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,9 +0,0 @@
|
|||||||
package list
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestAclRecordBuilder_BuildUserJoin(t *testing.T) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
@ -2,7 +2,7 @@ package list
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"github.com/anyproto/any-sync/app/logger"
|
"github.com/anyproto/any-sync/app/logger"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/acl/aclrecordproto"
|
"github.com/anyproto/any-sync/commonspace/object/acl/aclrecordproto"
|
||||||
"github.com/anyproto/any-sync/util/crypto"
|
"github.com/anyproto/any-sync/util/crypto"
|
||||||
@ -13,19 +13,24 @@ import (
|
|||||||
var log = logger.NewNamedSugared("common.commonspace.acllist")
|
var log = logger.NewNamedSugared("common.commonspace.acllist")
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrNoSuchUser = errors.New("no such user")
|
ErrNoSuchAccount = errors.New("no such account")
|
||||||
ErrFailedToDecrypt = errors.New("failed to decrypt key")
|
ErrPendingRequest = errors.New("already exists pending request")
|
||||||
ErrUserRemoved = errors.New("user was removed from the document")
|
ErrUnexpectedContentType = errors.New("unexpected content type")
|
||||||
ErrDocumentForbidden = errors.New("your user was forbidden access to the document")
|
ErrIncorrectIdentity = errors.New("incorrect identity")
|
||||||
ErrUserAlreadyExists = errors.New("user already exists")
|
ErrIncorrectInviteKey = errors.New("incorrect invite key")
|
||||||
ErrNoSuchRecord = errors.New("no such record")
|
ErrFailedToDecrypt = errors.New("failed to decrypt key")
|
||||||
ErrNoSuchInvite = errors.New("no such invite")
|
ErrNoSuchRecord = errors.New("no such record")
|
||||||
ErrOldInvite = errors.New("invite is too old")
|
ErrNoSuchRequest = errors.New("no such request")
|
||||||
ErrInsufficientPermissions = errors.New("insufficient permissions")
|
ErrNoSuchInvite = errors.New("no such invite")
|
||||||
ErrNoReadKey = errors.New("acl state doesn't have a read key")
|
ErrInsufficientPermissions = errors.New("insufficient permissions")
|
||||||
ErrInvalidSignature = errors.New("signature is invalid")
|
ErrIsOwner = errors.New("can't be made by owner")
|
||||||
ErrIncorrectRoot = errors.New("incorrect root")
|
ErrIncorrectNumberOfAccounts = errors.New("incorrect number of accounts")
|
||||||
ErrIncorrectRecordSequence = errors.New("incorrect prev id of a record")
|
ErrDuplicateAccounts = errors.New("duplicate accounts")
|
||||||
|
ErrNoReadKey = errors.New("acl state doesn't have a read key")
|
||||||
|
ErrIncorrectReadKey = errors.New("incorrect read key")
|
||||||
|
ErrInvalidSignature = errors.New("signature is invalid")
|
||||||
|
ErrIncorrectRoot = errors.New("incorrect root")
|
||||||
|
ErrIncorrectRecordSequence = errors.New("incorrect prev id of a record")
|
||||||
)
|
)
|
||||||
|
|
||||||
type UserPermissionPair struct {
|
type UserPermissionPair struct {
|
||||||
@ -36,37 +41,71 @@ type UserPermissionPair struct {
|
|||||||
type AclState struct {
|
type AclState struct {
|
||||||
id string
|
id string
|
||||||
currentReadKeyId string
|
currentReadKeyId string
|
||||||
userReadKeys map[string]crypto.SymKey
|
// userReadKeys is a map recordId -> read key which tells us about every read key
|
||||||
userStates map[string]AclUserState
|
userReadKeys map[string]crypto.SymKey
|
||||||
statesAtRecord map[string][]AclUserState
|
// userStates is a map pubKey -> state which defines current user state
|
||||||
key crypto.PrivKey
|
userStates map[string]AclUserState
|
||||||
pubKey crypto.PubKey
|
// statesAtRecord is a map recordId -> state which define user state at particular record
|
||||||
keyStore crypto.KeyStorage
|
// probably this can grow rather large at some point, so we can maybe optimise later to have:
|
||||||
totalReadKeys int
|
// - map pubKey -> []recordIds (where recordIds is an array where such identity permissions were changed)
|
||||||
|
statesAtRecord map[string][]AclUserState
|
||||||
|
// inviteKeys is a map recordId -> invite
|
||||||
|
inviteKeys map[string]crypto.PubKey
|
||||||
|
// requestRecords is a map recordId -> RequestRecord
|
||||||
|
requestRecords map[string]RequestRecord
|
||||||
|
// pendingRequests is a map pubKey -> recordId
|
||||||
|
pendingRequests map[string]string
|
||||||
|
key crypto.PrivKey
|
||||||
|
pubKey crypto.PubKey
|
||||||
|
keyStore crypto.KeyStorage
|
||||||
|
totalReadKeys int
|
||||||
|
|
||||||
lastRecordId string
|
lastRecordId string
|
||||||
|
contentValidator ContentValidator
|
||||||
}
|
}
|
||||||
|
|
||||||
func newAclStateWithKeys(
|
func newAclStateWithKeys(
|
||||||
id string,
|
id string,
|
||||||
key crypto.PrivKey) (*AclState, error) {
|
key crypto.PrivKey) (*AclState, error) {
|
||||||
return &AclState{
|
st := &AclState{
|
||||||
id: id,
|
id: id,
|
||||||
key: key,
|
key: key,
|
||||||
pubKey: key.GetPublic(),
|
pubKey: key.GetPublic(),
|
||||||
userReadKeys: make(map[string]crypto.SymKey),
|
userReadKeys: make(map[string]crypto.SymKey),
|
||||||
userStates: make(map[string]AclUserState),
|
userStates: make(map[string]AclUserState),
|
||||||
statesAtRecord: make(map[string][]AclUserState),
|
statesAtRecord: make(map[string][]AclUserState),
|
||||||
}, nil
|
inviteKeys: make(map[string]crypto.PubKey),
|
||||||
|
requestRecords: make(map[string]RequestRecord),
|
||||||
|
pendingRequests: make(map[string]string),
|
||||||
|
keyStore: crypto.NewKeyStorage(),
|
||||||
|
}
|
||||||
|
st.contentValidator = &contentValidator{
|
||||||
|
keyStore: st.keyStore,
|
||||||
|
aclState: st,
|
||||||
|
}
|
||||||
|
return st, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func newAclState(id string) *AclState {
|
func newAclState(id string) *AclState {
|
||||||
return &AclState{
|
st := &AclState{
|
||||||
id: id,
|
id: id,
|
||||||
userReadKeys: make(map[string]crypto.SymKey),
|
userReadKeys: make(map[string]crypto.SymKey),
|
||||||
userStates: make(map[string]AclUserState),
|
userStates: make(map[string]AclUserState),
|
||||||
statesAtRecord: make(map[string][]AclUserState),
|
statesAtRecord: make(map[string][]AclUserState),
|
||||||
|
inviteKeys: make(map[string]crypto.PubKey),
|
||||||
|
requestRecords: make(map[string]RequestRecord),
|
||||||
|
pendingRequests: make(map[string]string),
|
||||||
|
keyStore: crypto.NewKeyStorage(),
|
||||||
}
|
}
|
||||||
|
st.contentValidator = &contentValidator{
|
||||||
|
keyStore: st.keyStore,
|
||||||
|
aclState: st,
|
||||||
|
}
|
||||||
|
return st
|
||||||
|
}
|
||||||
|
|
||||||
|
func (st *AclState) Validator() ContentValidator {
|
||||||
|
return st.contentValidator
|
||||||
}
|
}
|
||||||
|
|
||||||
func (st *AclState) CurrentReadKeyId() string {
|
func (st *AclState) CurrentReadKeyId() string {
|
||||||
@ -74,7 +113,7 @@ func (st *AclState) CurrentReadKeyId() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (st *AclState) CurrentReadKey() (crypto.SymKey, error) {
|
func (st *AclState) CurrentReadKey() (crypto.SymKey, error) {
|
||||||
key, exists := st.userReadKeys[st.currentReadKeyId]
|
key, exists := st.userReadKeys[st.CurrentReadKeyId()]
|
||||||
if !exists {
|
if !exists {
|
||||||
return nil, ErrNoReadKey
|
return nil, ErrNoReadKey
|
||||||
}
|
}
|
||||||
@ -97,7 +136,7 @@ func (st *AclState) StateAtRecord(id string, pubKey crypto.PubKey) (AclUserState
|
|||||||
return perm, nil
|
return perm, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return AclUserState{}, ErrNoSuchUser
|
return AclUserState{}, ErrNoSuchAccount
|
||||||
}
|
}
|
||||||
|
|
||||||
func (st *AclState) applyRecord(record *AclRecord) (err error) {
|
func (st *AclState) applyRecord(record *AclRecord) (err error) {
|
||||||
@ -110,17 +149,18 @@ func (st *AclState) applyRecord(record *AclRecord) (err error) {
|
|||||||
err = ErrIncorrectRecordSequence
|
err = ErrIncorrectRecordSequence
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// if the record is root record
|
||||||
if record.Id == st.id {
|
if record.Id == st.id {
|
||||||
err = st.applyRoot(record)
|
err = st.applyRoot(record)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
st.statesAtRecord[record.Id] = []AclUserState{
|
st.statesAtRecord[record.Id] = []AclUserState{
|
||||||
{PubKey: record.Identity, Permissions: aclrecordproto.AclUserPermissions_Admin},
|
st.userStates[mapKeyFromPubKey(record.Identity)],
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// if the model is not cached
|
||||||
if record.Model == nil {
|
if record.Model == nil {
|
||||||
aclData := &aclrecordproto.AclData{}
|
aclData := &aclrecordproto.AclData{}
|
||||||
err = proto.Unmarshal(record.Data, aclData)
|
err = proto.Unmarshal(record.Data, aclData)
|
||||||
@ -129,18 +169,16 @@ func (st *AclState) applyRecord(record *AclRecord) (err error) {
|
|||||||
}
|
}
|
||||||
record.Model = aclData
|
record.Model = aclData
|
||||||
}
|
}
|
||||||
|
// applying records contents
|
||||||
err = st.applyChangeData(record)
|
err = st.applyChangeData(record)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// getting all states for users at record and saving them
|
||||||
// getting all states for users at record
|
|
||||||
var states []AclUserState
|
var states []AclUserState
|
||||||
for _, state := range st.userStates {
|
for _, state := range st.userStates {
|
||||||
states = append(states, state)
|
states = append(states, state)
|
||||||
}
|
}
|
||||||
|
|
||||||
st.statesAtRecord[record.Id] = states
|
st.statesAtRecord[record.Id] = states
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -156,9 +194,9 @@ func (st *AclState) applyRoot(record *AclRecord) (err error) {
|
|||||||
// adding user to the list
|
// adding user to the list
|
||||||
userState := AclUserState{
|
userState := AclUserState{
|
||||||
PubKey: record.Identity,
|
PubKey: record.Identity,
|
||||||
Permissions: aclrecordproto.AclUserPermissions_Admin,
|
Permissions: AclPermissions(aclrecordproto.AclUserPermissions_Owner),
|
||||||
}
|
}
|
||||||
st.currentReadKeyId = record.ReadKeyId
|
st.currentReadKeyId = record.Id
|
||||||
st.userStates[mapKeyFromPubKey(record.Identity)] = userState
|
st.userStates[mapKeyFromPubKey(record.Identity)] = userState
|
||||||
st.totalReadKeys++
|
st.totalReadKeys++
|
||||||
return
|
return
|
||||||
@ -181,92 +219,191 @@ func (st *AclState) saveReadKeyFromRoot(record *AclRecord) (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
st.userReadKeys[record.Id] = readKey
|
st.userReadKeys[record.Id] = readKey
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (st *AclState) applyChangeData(record *AclRecord) (err error) {
|
func (st *AclState) applyChangeData(record *AclRecord) (err error) {
|
||||||
defer func() {
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if record.ReadKeyId != st.currentReadKeyId {
|
|
||||||
st.totalReadKeys++
|
|
||||||
st.currentReadKeyId = record.ReadKeyId
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
model := record.Model.(*aclrecordproto.AclData)
|
model := record.Model.(*aclrecordproto.AclData)
|
||||||
if !st.isUserJoin(model) {
|
|
||||||
// we check signature when we add this to the List, so no need to do it here
|
|
||||||
if _, exists := st.userStates[mapKeyFromPubKey(record.Identity)]; !exists {
|
|
||||||
err = ErrNoSuchUser
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// only Admins can do non-user join changes
|
|
||||||
if !st.HasPermission(record.Identity, aclrecordproto.AclUserPermissions_Admin) {
|
|
||||||
// TODO: add string encoding
|
|
||||||
err = fmt.Errorf("user %s must have admin permissions", record.Identity.Account())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, ch := range model.GetAclContent() {
|
for _, ch := range model.GetAclContent() {
|
||||||
if err = st.applyChangeContent(ch, record.Id); err != nil {
|
if err = st.applyChangeContent(ch, record.Id, record.Identity); err != nil {
|
||||||
log.Info("error while applying changes: %v; ignore", zap.Error(err))
|
log.Info("error while applying changes: %v; ignore", zap.Error(err))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (st *AclState) applyChangeContent(ch *aclrecordproto.AclContentValue, recordId string) error {
|
func (st *AclState) applyChangeContent(ch *aclrecordproto.AclContentValue, recordId string, authorIdentity crypto.PubKey) error {
|
||||||
switch {
|
switch {
|
||||||
case ch.GetUserPermissionChange() != nil:
|
case ch.GetPermissionChange() != nil:
|
||||||
return st.applyUserPermissionChange(ch.GetUserPermissionChange(), recordId)
|
return st.applyPermissionChange(ch.GetPermissionChange(), recordId, authorIdentity)
|
||||||
case ch.GetUserAdd() != nil:
|
case ch.GetInvite() != nil:
|
||||||
return st.applyUserAdd(ch.GetUserAdd(), recordId)
|
return st.applyInvite(ch.GetInvite(), recordId, authorIdentity)
|
||||||
case ch.GetUserRemove() != nil:
|
case ch.GetInviteRevoke() != nil:
|
||||||
return st.applyUserRemove(ch.GetUserRemove(), recordId)
|
return st.applyInviteRevoke(ch.GetInviteRevoke(), recordId, authorIdentity)
|
||||||
case ch.GetUserInvite() != nil:
|
case ch.GetRequestJoin() != nil:
|
||||||
return st.applyUserInvite(ch.GetUserInvite(), recordId)
|
return st.applyRequestJoin(ch.GetRequestJoin(), recordId, authorIdentity)
|
||||||
case ch.GetUserJoin() != nil:
|
case ch.GetRequestAccept() != nil:
|
||||||
return st.applyUserJoin(ch.GetUserJoin(), recordId)
|
return st.applyRequestAccept(ch.GetRequestAccept(), recordId, authorIdentity)
|
||||||
|
case ch.GetRequestDecline() != nil:
|
||||||
|
return st.applyRequestDecline(ch.GetRequestDecline(), recordId, authorIdentity)
|
||||||
|
case ch.GetAccountRemove() != nil:
|
||||||
|
return st.applyAccountRemove(ch.GetAccountRemove(), recordId, authorIdentity)
|
||||||
|
case ch.GetReadKeyChange() != nil:
|
||||||
|
return st.applyReadKeyChange(ch.GetReadKeyChange(), recordId, authorIdentity)
|
||||||
|
case ch.GetAccountRequestRemove() != nil:
|
||||||
|
return st.applyRequestRemove(ch.GetAccountRequestRemove(), recordId, authorIdentity)
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("unexpected change type: %v", ch)
|
return ErrUnexpectedContentType
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (st *AclState) applyUserPermissionChange(ch *aclrecordproto.AclUserPermissionChange, recordId string) error {
|
func (st *AclState) applyPermissionChange(ch *aclrecordproto.AclAccountPermissionChange, recordId string, authorIdentity crypto.PubKey) error {
|
||||||
chIdentity, err := st.keyStore.PubKeyFromProto(ch.Identity)
|
chIdentity, err := st.keyStore.PubKeyFromProto(ch.Identity)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
state, exists := st.userStates[mapKeyFromPubKey(chIdentity)]
|
err = st.contentValidator.ValidatePermissionChange(ch, authorIdentity)
|
||||||
if !exists {
|
if err != nil {
|
||||||
return ErrNoSuchUser
|
return err
|
||||||
}
|
}
|
||||||
|
stringKey := mapKeyFromPubKey(chIdentity)
|
||||||
state.Permissions = ch.Permissions
|
state, _ := st.userStates[stringKey]
|
||||||
|
state.Permissions = AclPermissions(ch.Permissions)
|
||||||
|
st.userStates[stringKey] = state
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (st *AclState) applyUserInvite(ch *aclrecordproto.AclUserInvite, recordId string) error {
|
func (st *AclState) applyInvite(ch *aclrecordproto.AclAccountInvite, recordId string, authorIdentity crypto.PubKey) error {
|
||||||
// TODO: check old code and bring it back :-)
|
inviteKey, err := st.keyStore.PubKeyFromProto(ch.InviteKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = st.contentValidator.ValidateInvite(ch, authorIdentity)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
st.inviteKeys[recordId] = inviteKey
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (st *AclState) applyUserJoin(ch *aclrecordproto.AclUserJoin, recordId string) error {
|
func (st *AclState) applyInviteRevoke(ch *aclrecordproto.AclAccountInviteRevoke, recordId string, authorIdentity crypto.PubKey) error {
|
||||||
|
err := st.contentValidator.ValidateInviteRevoke(ch, authorIdentity)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
delete(st.inviteKeys, ch.InviteRecordId)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (st *AclState) applyUserAdd(ch *aclrecordproto.AclUserAdd, recordId string) error {
|
func (st *AclState) applyRequestJoin(ch *aclrecordproto.AclAccountRequestJoin, recordId string, authorIdentity crypto.PubKey) error {
|
||||||
|
err := st.contentValidator.ValidateRequestJoin(ch, authorIdentity)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
st.pendingRequests[mapKeyFromPubKey(authorIdentity)] = recordId
|
||||||
|
st.requestRecords[recordId] = RequestRecord{
|
||||||
|
RequestIdentity: authorIdentity,
|
||||||
|
RequestMetadata: ch.Metadata,
|
||||||
|
Type: RequestTypeJoin,
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (st *AclState) applyUserRemove(ch *aclrecordproto.AclUserRemove, recordId string) error {
|
func (st *AclState) applyRequestAccept(ch *aclrecordproto.AclAccountRequestAccept, recordId string, authorIdentity crypto.PubKey) error {
|
||||||
|
err := st.contentValidator.ValidateRequestAccept(ch, authorIdentity)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
acceptIdentity, err := st.keyStore.PubKeyFromProto(ch.Identity)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
record, _ := st.requestRecords[ch.RequestRecordId]
|
||||||
|
st.userStates[mapKeyFromPubKey(acceptIdentity)] = AclUserState{
|
||||||
|
PubKey: acceptIdentity,
|
||||||
|
Permissions: AclPermissions(ch.Permissions),
|
||||||
|
RequestMetadata: record.RequestMetadata,
|
||||||
|
}
|
||||||
|
delete(st.pendingRequests, mapKeyFromPubKey(st.requestRecords[ch.RequestRecordId].RequestIdentity))
|
||||||
|
if !st.pubKey.Equals(acceptIdentity) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
for _, key := range ch.EncryptedReadKeys {
|
||||||
|
decrypted, err := st.key.Decrypt(key.EncryptedReadKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
sym, err := crypto.UnmarshallAESKey(decrypted)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
st.userReadKeys[key.RecordId] = sym
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (st *AclState) applyRequestDecline(ch *aclrecordproto.AclAccountRequestDecline, recordId string, authorIdentity crypto.PubKey) error {
|
||||||
|
err := st.contentValidator.ValidateRequestDecline(ch, authorIdentity)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
delete(st.pendingRequests, mapKeyFromPubKey(st.requestRecords[ch.RequestRecordId].RequestIdentity))
|
||||||
|
delete(st.requestRecords, ch.RequestRecordId)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (st *AclState) applyRequestRemove(ch *aclrecordproto.AclAccountRequestRemove, recordId string, authorIdentity crypto.PubKey) error {
|
||||||
|
err := st.contentValidator.ValidateRequestRemove(ch, authorIdentity)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
st.requestRecords[recordId] = RequestRecord{
|
||||||
|
RequestIdentity: authorIdentity,
|
||||||
|
Type: RequestTypeRemove,
|
||||||
|
}
|
||||||
|
st.pendingRequests[mapKeyFromPubKey(authorIdentity)] = recordId
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (st *AclState) applyAccountRemove(ch *aclrecordproto.AclAccountRemove, recordId string, authorIdentity crypto.PubKey) error {
|
||||||
|
err := st.contentValidator.ValidateAccountRemove(ch, authorIdentity)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, rawIdentity := range ch.Identities {
|
||||||
|
identity, err := st.keyStore.PubKeyFromProto(rawIdentity)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
idKey := mapKeyFromPubKey(identity)
|
||||||
|
delete(st.userStates, idKey)
|
||||||
|
delete(st.pendingRequests, idKey)
|
||||||
|
}
|
||||||
|
return st.updateReadKey(ch.AccountKeys, recordId)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (st *AclState) applyReadKeyChange(ch *aclrecordproto.AclReadKeyChange, recordId string, authorIdentity crypto.PubKey) error {
|
||||||
|
err := st.contentValidator.ValidateReadKeyChange(ch, authorIdentity)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return st.updateReadKey(ch.AccountKeys, recordId)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (st *AclState) updateReadKey(keys []*aclrecordproto.AclEncryptedReadKey, recordId string) error {
|
||||||
|
for _, accKey := range keys {
|
||||||
|
identity, _ := st.keyStore.PubKeyFromProto(accKey.Identity)
|
||||||
|
if st.pubKey.Equals(identity) {
|
||||||
|
res, err := st.decryptReadKey(accKey.EncryptedReadKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
st.userReadKeys[recordId] = res
|
||||||
|
}
|
||||||
|
}
|
||||||
|
st.currentReadKeyId = recordId
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -275,7 +412,6 @@ func (st *AclState) decryptReadKey(msg []byte) (crypto.SymKey, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, ErrFailedToDecrypt
|
return nil, ErrFailedToDecrypt
|
||||||
}
|
}
|
||||||
|
|
||||||
key, err := crypto.UnmarshallAESKey(decrypted)
|
key, err := crypto.UnmarshallAESKey(decrypted)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, ErrFailedToDecrypt
|
return nil, ErrFailedToDecrypt
|
||||||
@ -283,29 +419,31 @@ func (st *AclState) decryptReadKey(msg []byte) (crypto.SymKey, error) {
|
|||||||
return key, nil
|
return key, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (st *AclState) HasPermission(identity crypto.PubKey, permission aclrecordproto.AclUserPermissions) bool {
|
func (st *AclState) Permissions(identity crypto.PubKey) AclPermissions {
|
||||||
state, exists := st.userStates[mapKeyFromPubKey(identity)]
|
state, exists := st.userStates[mapKeyFromPubKey(identity)]
|
||||||
if !exists {
|
if !exists {
|
||||||
return false
|
return AclPermissions(aclrecordproto.AclUserPermissions_None)
|
||||||
}
|
}
|
||||||
|
return state.Permissions
|
||||||
return state.Permissions == permission
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (st *AclState) isUserJoin(data *aclrecordproto.AclData) bool {
|
func (st *AclState) JoinRecords() (records []RequestRecord) {
|
||||||
// if we have a UserJoin, then it should always be the first one applied
|
for _, recId := range st.pendingRequests {
|
||||||
return data.GetAclContent() != nil && data.GetAclContent()[0].GetUserJoin() != nil
|
rec := st.requestRecords[recId]
|
||||||
|
if rec.Type == RequestTypeJoin {
|
||||||
|
records = append(records, rec)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (st *AclState) isUserAdd(data *aclrecordproto.AclData, identity []byte) bool {
|
func (st *AclState) RemoveRecords() (records []RequestRecord) {
|
||||||
return false
|
for _, recId := range st.pendingRequests {
|
||||||
}
|
rec := st.requestRecords[recId]
|
||||||
|
if rec.Type == RequestTypeRemove {
|
||||||
func (st *AclState) UserStates() map[string]AclUserState {
|
records = append(records, rec)
|
||||||
return st.userStates
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (st *AclState) Invite(acceptPubKey []byte) (invite *aclrecordproto.AclUserInvite, err error) {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -5,16 +5,20 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"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/liststorage"
|
|
||||||
"github.com/anyproto/any-sync/util/crypto"
|
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"github.com/anyproto/any-sync/commonspace/object/accountdata"
|
||||||
|
"github.com/anyproto/any-sync/commonspace/object/acl/liststorage"
|
||||||
|
"github.com/anyproto/any-sync/consensus/consensusproto"
|
||||||
|
"github.com/anyproto/any-sync/util/crypto"
|
||||||
)
|
)
|
||||||
|
|
||||||
type IterFunc = func(record *AclRecord) (IsContinue bool)
|
type IterFunc = func(record *AclRecord) (IsContinue bool)
|
||||||
|
|
||||||
var ErrIncorrectCID = errors.New("incorrect CID")
|
var (
|
||||||
|
ErrIncorrectCID = errors.New("incorrect CID")
|
||||||
|
ErrRecordAlreadyExists = errors.New("record already exists")
|
||||||
|
)
|
||||||
|
|
||||||
type RWLocker interface {
|
type RWLocker interface {
|
||||||
sync.Locker
|
sync.Locker
|
||||||
@ -22,26 +26,41 @@ type RWLocker interface {
|
|||||||
RUnlock()
|
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 {
|
type AclList interface {
|
||||||
RWLocker
|
RWLocker
|
||||||
Id() string
|
Id() string
|
||||||
Root() *aclrecordproto.RawAclRecordWithId
|
Root() *consensusproto.RawRecordWithId
|
||||||
Records() []*AclRecord
|
Records() []*AclRecord
|
||||||
AclState() *AclState
|
AclState() *AclState
|
||||||
IsAfter(first string, second string) (bool, error)
|
IsAfter(first string, second string) (bool, error)
|
||||||
Head() *AclRecord
|
Head() *AclRecord
|
||||||
Get(id string) (*AclRecord, error)
|
Get(id string) (*AclRecord, error)
|
||||||
|
GetIndex(idx int) (*AclRecord, error)
|
||||||
Iterate(iterFunc IterFunc)
|
Iterate(iterFunc IterFunc)
|
||||||
IterateFrom(startId string, iterFunc IterFunc)
|
IterateFrom(startId string, iterFunc IterFunc)
|
||||||
KeyStorage() crypto.KeyStorage
|
|
||||||
|
|
||||||
AddRawRecord(rawRec *aclrecordproto.RawAclRecordWithId) (added bool, err error)
|
KeyStorage() crypto.KeyStorage
|
||||||
|
RecordBuilder() AclRecordBuilder
|
||||||
|
|
||||||
|
ValidateRawRecord(record *consensusproto.RawRecord) (err error)
|
||||||
|
AddRawRecord(rawRec *consensusproto.RawRecordWithId) (err error)
|
||||||
|
|
||||||
Close() (err error)
|
Close() (err error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type aclList struct {
|
type aclList struct {
|
||||||
root *aclrecordproto.RawAclRecordWithId
|
root *consensusproto.RawRecordWithId
|
||||||
records []*AclRecord
|
records []*AclRecord
|
||||||
indexes map[string]int
|
indexes map[string]int
|
||||||
id string
|
id string
|
||||||
@ -55,18 +74,45 @@ type aclList struct {
|
|||||||
sync.RWMutex
|
sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func BuildAclListWithIdentity(acc *accountdata.AccountKeys, storage liststorage.ListStorage) (AclList, error) {
|
type internalDeps struct {
|
||||||
builder := newAclStateBuilderWithIdentity(acc)
|
storage liststorage.ListStorage
|
||||||
keyStorage := crypto.NewKeyStorage()
|
keyStorage crypto.KeyStorage
|
||||||
return build(storage.Id(), keyStorage, builder, NewAclRecordBuilder(storage.Id(), keyStorage), storage)
|
stateBuilder *aclStateBuilder
|
||||||
|
recordBuilder AclRecordBuilder
|
||||||
|
acceptorVerifier AcceptorVerifier
|
||||||
}
|
}
|
||||||
|
|
||||||
func BuildAclList(storage liststorage.ListStorage) (AclList, error) {
|
func BuildAclListWithIdentity(acc *accountdata.AccountKeys, storage liststorage.ListStorage, verifier AcceptorVerifier) (AclList, error) {
|
||||||
keyStorage := crypto.NewKeyStorage()
|
keyStorage := crypto.NewKeyStorage()
|
||||||
return build(storage.Id(), keyStorage, newAclStateBuilder(), NewAclRecordBuilder(storage.Id(), crypto.NewKeyStorage()), storage)
|
deps := internalDeps{
|
||||||
|
storage: storage,
|
||||||
|
keyStorage: keyStorage,
|
||||||
|
stateBuilder: newAclStateBuilderWithIdentity(acc),
|
||||||
|
recordBuilder: NewAclRecordBuilder(storage.Id(), keyStorage, acc, verifier),
|
||||||
|
acceptorVerifier: verifier,
|
||||||
|
}
|
||||||
|
return build(deps)
|
||||||
}
|
}
|
||||||
|
|
||||||
func build(id string, keyStorage crypto.KeyStorage, stateBuilder *aclStateBuilder, recBuilder AclRecordBuilder, storage liststorage.ListStorage) (list AclList, err error) {
|
func BuildAclList(storage liststorage.ListStorage, verifier AcceptorVerifier) (AclList, error) {
|
||||||
|
keyStorage := crypto.NewKeyStorage()
|
||||||
|
deps := internalDeps{
|
||||||
|
storage: storage,
|
||||||
|
keyStorage: keyStorage,
|
||||||
|
stateBuilder: newAclStateBuilder(),
|
||||||
|
recordBuilder: NewAclRecordBuilder(storage.Id(), keyStorage, nil, verifier),
|
||||||
|
acceptorVerifier: verifier,
|
||||||
|
}
|
||||||
|
return build(deps)
|
||||||
|
}
|
||||||
|
|
||||||
|
func build(deps internalDeps) (list AclList, err error) {
|
||||||
|
var (
|
||||||
|
storage = deps.storage
|
||||||
|
id = deps.storage.Id()
|
||||||
|
recBuilder = deps.recordBuilder
|
||||||
|
stateBuilder = deps.stateBuilder
|
||||||
|
)
|
||||||
head, err := storage.Head()
|
head, err := storage.Head()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
@ -77,7 +123,7 @@ func build(id string, keyStorage crypto.KeyStorage, stateBuilder *aclStateBuilde
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
record, err := recBuilder.Unmarshall(rawRecordWithId)
|
record, err := recBuilder.UnmarshallWithId(rawRecordWithId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -89,7 +135,7 @@ func build(id string, keyStorage crypto.KeyStorage, stateBuilder *aclStateBuilde
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
record, err = recBuilder.Unmarshall(rawRecordWithId)
|
record, err = recBuilder.UnmarshallWithId(rawRecordWithId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -119,6 +165,7 @@ func build(id string, keyStorage crypto.KeyStorage, stateBuilder *aclStateBuilde
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
recBuilder.(*aclRecordBuilder).state = state
|
||||||
list = &aclList{
|
list = &aclList{
|
||||||
root: rootWithId,
|
root: rootWithId,
|
||||||
records: records,
|
records: records,
|
||||||
@ -132,15 +179,27 @@ func build(id string, keyStorage crypto.KeyStorage, stateBuilder *aclStateBuilde
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *aclList) RecordBuilder() AclRecordBuilder {
|
||||||
|
return a.recordBuilder
|
||||||
|
}
|
||||||
|
|
||||||
func (a *aclList) Records() []*AclRecord {
|
func (a *aclList) Records() []*AclRecord {
|
||||||
return a.records
|
return a.records
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *aclList) AddRawRecord(rawRec *aclrecordproto.RawAclRecordWithId) (added bool, err error) {
|
func (a *aclList) ValidateRawRecord(rawRec *consensusproto.RawRecord) (err error) {
|
||||||
if _, ok := a.indexes[rawRec.Id]; ok {
|
record, err := a.recordBuilder.Unmarshall(rawRec)
|
||||||
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
record, err := a.recordBuilder.Unmarshall(rawRec)
|
return a.aclState.Validator().ValidateAclRecordContents(record)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *aclList) AddRawRecord(rawRec *consensusproto.RawRecordWithId) (err error) {
|
||||||
|
if _, ok := a.indexes[rawRec.Id]; ok {
|
||||||
|
return ErrRecordAlreadyExists
|
||||||
|
}
|
||||||
|
record, err := a.recordBuilder.UnmarshallWithId(rawRec)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -155,15 +214,6 @@ func (a *aclList) AddRawRecord(rawRec *aclrecordproto.RawAclRecordWithId) (added
|
|||||||
if err = a.storage.SetHead(rawRec.Id); err != nil {
|
if err = a.storage.SetHead(rawRec.Id); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *aclList) IsValidNext(rawRec *aclrecordproto.RawAclRecordWithId) (err error) {
|
|
||||||
_, err = a.recordBuilder.Unmarshall(rawRec)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// TODO: change state and add "check" method for records
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -171,7 +221,7 @@ func (a *aclList) Id() string {
|
|||||||
return a.id
|
return a.id
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *aclList) Root() *aclrecordproto.RawAclRecordWithId {
|
func (a *aclList) Root() *consensusproto.RawRecordWithId {
|
||||||
return a.root
|
return a.root
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -199,11 +249,19 @@ func (a *aclList) Head() *AclRecord {
|
|||||||
func (a *aclList) Get(id string) (*AclRecord, error) {
|
func (a *aclList) Get(id string) (*AclRecord, error) {
|
||||||
recIdx, ok := a.indexes[id]
|
recIdx, ok := a.indexes[id]
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("no such record")
|
return nil, ErrNoSuchRecord
|
||||||
}
|
}
|
||||||
return a.records[recIdx], nil
|
return a.records[recIdx], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *aclList) GetIndex(idx int) (*AclRecord, error) {
|
||||||
|
// TODO: when we add snapshots we will have to monitor record num in snapshots
|
||||||
|
if idx < 0 || idx >= len(a.records) {
|
||||||
|
return nil, ErrNoSuchRecord
|
||||||
|
}
|
||||||
|
return a.records[idx], nil
|
||||||
|
}
|
||||||
|
|
||||||
func (a *aclList) Iterate(iterFunc IterFunc) {
|
func (a *aclList) Iterate(iterFunc IterFunc) {
|
||||||
for _, rec := range a.records {
|
for _, rec := range a.records {
|
||||||
if !iterFunc(rec) {
|
if !iterFunc(rec) {
|
||||||
|
|||||||
@ -2,11 +2,114 @@ package list
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/accountdata"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/anyproto/any-sync/commonspace/object/accountdata"
|
||||||
|
"github.com/anyproto/any-sync/commonspace/object/acl/aclrecordproto"
|
||||||
|
"github.com/anyproto/any-sync/consensus/consensusproto"
|
||||||
|
"github.com/anyproto/any-sync/util/cidutil"
|
||||||
|
"github.com/anyproto/any-sync/util/crypto"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func wrapRecord(rawRec *consensusproto.RawRecord) *consensusproto.RawRecordWithId {
|
||||||
|
payload, err := rawRec.Marshal()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
id, err := cidutil.NewCidFromBytes(payload)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return &consensusproto.RawRecordWithId{
|
||||||
|
Payload: payload,
|
||||||
|
Id: id,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type aclFixture struct {
|
||||||
|
ownerKeys *accountdata.AccountKeys
|
||||||
|
accountKeys *accountdata.AccountKeys
|
||||||
|
ownerAcl *aclList
|
||||||
|
accountAcl *aclList
|
||||||
|
spaceId string
|
||||||
|
}
|
||||||
|
|
||||||
|
func newFixture(t *testing.T) *aclFixture {
|
||||||
|
ownerKeys, err := accountdata.NewRandom()
|
||||||
|
require.NoError(t, err)
|
||||||
|
accountKeys, err := accountdata.NewRandom()
|
||||||
|
require.NoError(t, err)
|
||||||
|
spaceId := "spaceId"
|
||||||
|
ownerAcl, err := NewTestDerivedAcl(spaceId, ownerKeys)
|
||||||
|
require.NoError(t, err)
|
||||||
|
accountAcl, err := NewTestAclWithRoot(accountKeys, ownerAcl.Root())
|
||||||
|
require.NoError(t, err)
|
||||||
|
return &aclFixture{
|
||||||
|
ownerKeys: ownerKeys,
|
||||||
|
accountKeys: accountKeys,
|
||||||
|
ownerAcl: ownerAcl.(*aclList),
|
||||||
|
accountAcl: accountAcl.(*aclList),
|
||||||
|
spaceId: spaceId,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fx *aclFixture) addRec(t *testing.T, rec *consensusproto.RawRecordWithId) {
|
||||||
|
err := fx.ownerAcl.AddRawRecord(rec)
|
||||||
|
require.NoError(t, err)
|
||||||
|
err = fx.accountAcl.AddRawRecord(rec)
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fx *aclFixture) inviteAccount(t *testing.T, perms AclPermissions) {
|
||||||
|
var (
|
||||||
|
ownerAcl = fx.ownerAcl
|
||||||
|
ownerState = fx.ownerAcl.aclState
|
||||||
|
accountAcl = fx.accountAcl
|
||||||
|
accountState = fx.accountAcl.aclState
|
||||||
|
)
|
||||||
|
// building invite
|
||||||
|
inv, err := ownerAcl.RecordBuilder().BuildInvite()
|
||||||
|
require.NoError(t, err)
|
||||||
|
inviteRec := wrapRecord(inv.InviteRec)
|
||||||
|
fx.addRec(t, inviteRec)
|
||||||
|
|
||||||
|
// building request join
|
||||||
|
requestJoin, err := accountAcl.RecordBuilder().BuildRequestJoin(RequestJoinPayload{
|
||||||
|
InviteRecordId: inviteRec.Id,
|
||||||
|
InviteKey: inv.InviteKey,
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
requestJoinRec := wrapRecord(requestJoin)
|
||||||
|
fx.addRec(t, requestJoinRec)
|
||||||
|
|
||||||
|
// building request accept
|
||||||
|
requestAccept, err := ownerAcl.RecordBuilder().BuildRequestAccept(RequestAcceptPayload{
|
||||||
|
RequestRecordId: requestJoinRec.Id,
|
||||||
|
Permissions: perms,
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
// validate
|
||||||
|
err = ownerAcl.ValidateRawRecord(requestAccept)
|
||||||
|
require.NoError(t, err)
|
||||||
|
requestAcceptRec := wrapRecord(requestAccept)
|
||||||
|
fx.addRec(t, requestAcceptRec)
|
||||||
|
|
||||||
|
// checking acl state
|
||||||
|
require.True(t, ownerState.Permissions(ownerState.pubKey).IsOwner())
|
||||||
|
require.True(t, ownerState.Permissions(accountState.pubKey).CanWrite())
|
||||||
|
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).CanWrite())
|
||||||
|
|
||||||
|
_, err = ownerState.StateAtRecord(requestJoinRec.Id, accountState.pubKey)
|
||||||
|
require.Equal(t, ErrNoSuchAccount, err)
|
||||||
|
stateAtRec, err := ownerState.StateAtRecord(requestAcceptRec.Id, accountState.pubKey)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.True(t, stateAtRec.Permissions == perms)
|
||||||
|
}
|
||||||
|
|
||||||
func TestAclList_BuildRoot(t *testing.T) {
|
func TestAclList_BuildRoot(t *testing.T) {
|
||||||
randomKeys, err := accountdata.NewRandom()
|
randomKeys, err := accountdata.NewRandom()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@ -14,3 +117,193 @@ func TestAclList_BuildRoot(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
fmt.Println(randomAcl.Id())
|
fmt.Println(randomAcl.Id())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAclList_InvitePipeline(t *testing.T) {
|
||||||
|
fx := newFixture(t)
|
||||||
|
fx.inviteAccount(t, AclPermissions(aclrecordproto.AclUserPermissions_Writer))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAclList_InviteRevoke(t *testing.T) {
|
||||||
|
fx := newFixture(t)
|
||||||
|
var (
|
||||||
|
ownerState = fx.ownerAcl.aclState
|
||||||
|
accountState = fx.accountAcl.aclState
|
||||||
|
)
|
||||||
|
// building invite
|
||||||
|
inv, err := fx.ownerAcl.RecordBuilder().BuildInvite()
|
||||||
|
require.NoError(t, err)
|
||||||
|
inviteRec := wrapRecord(inv.InviteRec)
|
||||||
|
fx.addRec(t, inviteRec)
|
||||||
|
|
||||||
|
// building invite revoke
|
||||||
|
inviteRevoke, err := fx.ownerAcl.RecordBuilder().BuildInviteRevoke(ownerState.lastRecordId)
|
||||||
|
require.NoError(t, err)
|
||||||
|
inviteRevokeRec := wrapRecord(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)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAclList_RequestDecline(t *testing.T) {
|
||||||
|
fx := newFixture(t)
|
||||||
|
var (
|
||||||
|
ownerAcl = fx.ownerAcl
|
||||||
|
ownerState = fx.ownerAcl.aclState
|
||||||
|
accountAcl = fx.accountAcl
|
||||||
|
accountState = fx.accountAcl.aclState
|
||||||
|
)
|
||||||
|
// building invite
|
||||||
|
inv, err := ownerAcl.RecordBuilder().BuildInvite()
|
||||||
|
require.NoError(t, err)
|
||||||
|
inviteRec := wrapRecord(inv.InviteRec)
|
||||||
|
fx.addRec(t, inviteRec)
|
||||||
|
|
||||||
|
// building request join
|
||||||
|
requestJoin, err := accountAcl.RecordBuilder().BuildRequestJoin(RequestJoinPayload{
|
||||||
|
InviteRecordId: inviteRec.Id,
|
||||||
|
InviteKey: inv.InviteKey,
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
requestJoinRec := wrapRecord(requestJoin)
|
||||||
|
fx.addRec(t, requestJoinRec)
|
||||||
|
|
||||||
|
// building request decline
|
||||||
|
requestDecline, err := ownerAcl.RecordBuilder().BuildRequestDecline(ownerState.lastRecordId)
|
||||||
|
require.NoError(t, err)
|
||||||
|
requestDeclineRec := wrapRecord(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)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAclList_Remove(t *testing.T) {
|
||||||
|
fx := newFixture(t)
|
||||||
|
var (
|
||||||
|
ownerState = fx.ownerAcl.aclState
|
||||||
|
accountState = fx.accountAcl.aclState
|
||||||
|
)
|
||||||
|
fx.inviteAccount(t, AclPermissions(aclrecordproto.AclUserPermissions_Writer))
|
||||||
|
|
||||||
|
newReadKey := crypto.NewAES()
|
||||||
|
remove, err := fx.ownerAcl.RecordBuilder().BuildAccountRemove(AccountRemovePayload{
|
||||||
|
Identities: []crypto.PubKey{fx.accountKeys.SignKey.GetPublic()},
|
||||||
|
ReadKey: newReadKey,
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
removeRec := wrapRecord(remove)
|
||||||
|
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.userReadKeys[removeRec.Id].Equals(newReadKey))
|
||||||
|
require.NotNil(t, ownerState.userReadKeys[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.Nil(t, accountState.userReadKeys[removeRec.Id])
|
||||||
|
require.NotNil(t, accountState.userReadKeys[fx.ownerAcl.Id()])
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAclList_ReadKeyChange(t *testing.T) {
|
||||||
|
fx := newFixture(t)
|
||||||
|
var (
|
||||||
|
ownerState = fx.ownerAcl.aclState
|
||||||
|
accountState = fx.accountAcl.aclState
|
||||||
|
)
|
||||||
|
fx.inviteAccount(t, AclPermissions(aclrecordproto.AclUserPermissions_Admin))
|
||||||
|
|
||||||
|
newReadKey := crypto.NewAES()
|
||||||
|
readKeyChange, err := fx.ownerAcl.RecordBuilder().BuildReadKeyChange(newReadKey)
|
||||||
|
require.NoError(t, err)
|
||||||
|
readKeyRec := wrapRecord(readKeyChange)
|
||||||
|
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.userReadKeys[readKeyRec.Id].Equals(newReadKey))
|
||||||
|
require.True(t, accountState.userReadKeys[readKeyRec.Id].Equals(newReadKey))
|
||||||
|
require.NotNil(t, ownerState.userReadKeys[fx.ownerAcl.Id()])
|
||||||
|
require.NotNil(t, accountState.userReadKeys[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))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAclList_PermissionChange(t *testing.T) {
|
||||||
|
fx := newFixture(t)
|
||||||
|
var (
|
||||||
|
ownerState = fx.ownerAcl.aclState
|
||||||
|
accountState = fx.accountAcl.aclState
|
||||||
|
)
|
||||||
|
fx.inviteAccount(t, AclPermissions(aclrecordproto.AclUserPermissions_Admin))
|
||||||
|
|
||||||
|
permissionChange, err := fx.ownerAcl.RecordBuilder().BuildPermissionChange(PermissionChangePayload{
|
||||||
|
Identity: fx.accountKeys.SignKey.GetPublic(),
|
||||||
|
Permissions: AclPermissions(aclrecordproto.AclUserPermissions_Writer),
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
permissionChangeRec := wrapRecord(permissionChange)
|
||||||
|
fx.addRec(t, permissionChangeRec)
|
||||||
|
|
||||||
|
// checking acl state
|
||||||
|
require.True(t, ownerState.Permissions(ownerState.pubKey).IsOwner())
|
||||||
|
require.True(t, ownerState.Permissions(accountState.pubKey) == AclPermissions(aclrecordproto.AclUserPermissions_Writer))
|
||||||
|
require.True(t, accountState.Permissions(ownerState.pubKey).IsOwner())
|
||||||
|
require.True(t, accountState.Permissions(accountState.pubKey) == AclPermissions(aclrecordproto.AclUserPermissions_Writer))
|
||||||
|
require.NotNil(t, ownerState.userReadKeys[fx.ownerAcl.Id()])
|
||||||
|
require.NotNil(t, accountState.userReadKeys[fx.ownerAcl.Id()])
|
||||||
|
require.Equal(t, 0, len(ownerState.pendingRequests))
|
||||||
|
require.Equal(t, 0, len(accountState.pendingRequests))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAclList_RequestRemove(t *testing.T) {
|
||||||
|
fx := newFixture(t)
|
||||||
|
var (
|
||||||
|
ownerState = fx.ownerAcl.aclState
|
||||||
|
accountState = fx.accountAcl.aclState
|
||||||
|
)
|
||||||
|
fx.inviteAccount(t, AclPermissions(aclrecordproto.AclUserPermissions_Writer))
|
||||||
|
|
||||||
|
removeRequest, err := fx.accountAcl.RecordBuilder().BuildRequestRemove()
|
||||||
|
require.NoError(t, err)
|
||||||
|
removeRequestRec := wrapRecord(removeRequest)
|
||||||
|
fx.addRec(t, removeRequestRec)
|
||||||
|
|
||||||
|
recs := fx.accountAcl.AclState().RemoveRecords()
|
||||||
|
require.Len(t, recs, 1)
|
||||||
|
require.True(t, accountState.pubKey.Equals(recs[0].RequestIdentity))
|
||||||
|
|
||||||
|
newReadKey := crypto.NewAES()
|
||||||
|
remove, err := fx.ownerAcl.RecordBuilder().BuildAccountRemove(AccountRemovePayload{
|
||||||
|
Identities: []crypto.PubKey{recs[0].RequestIdentity},
|
||||||
|
ReadKey: newReadKey,
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
removeRec := wrapRecord(remove)
|
||||||
|
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.userReadKeys[removeRec.Id].Equals(newReadKey))
|
||||||
|
require.NotNil(t, ownerState.userReadKeys[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.Nil(t, accountState.userReadKeys[removeRec.Id])
|
||||||
|
require.NotNil(t, accountState.userReadKeys[fx.ownerAcl.Id()])
|
||||||
|
}
|
||||||
|
|||||||
@ -2,13 +2,13 @@ package list
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/anyproto/any-sync/commonspace/object/accountdata"
|
"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/liststorage"
|
"github.com/anyproto/any-sync/commonspace/object/acl/liststorage"
|
||||||
|
"github.com/anyproto/any-sync/consensus/consensusproto"
|
||||||
"github.com/anyproto/any-sync/util/crypto"
|
"github.com/anyproto/any-sync/util/crypto"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewTestDerivedAcl(spaceId string, keys *accountdata.AccountKeys) (AclList, error) {
|
func NewTestDerivedAcl(spaceId string, keys *accountdata.AccountKeys) (AclList, error) {
|
||||||
builder := NewAclRecordBuilder("", crypto.NewKeyStorage())
|
builder := NewAclRecordBuilder("", crypto.NewKeyStorage(), keys, NoOpAcceptorVerifier{})
|
||||||
masterKey, _, err := crypto.GenerateRandomEd25519KeyPair()
|
masterKey, _, err := crypto.GenerateRandomEd25519KeyPair()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -21,11 +21,21 @@ func NewTestDerivedAcl(spaceId string, keys *accountdata.AccountKeys) (AclList,
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
st, err := liststorage.NewInMemoryAclListStorage(root.Id, []*aclrecordproto.RawAclRecordWithId{
|
st, err := liststorage.NewInMemoryAclListStorage(root.Id, []*consensusproto.RawRecordWithId{
|
||||||
root,
|
root,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return BuildAclListWithIdentity(keys, st)
|
return BuildAclListWithIdentity(keys, st, NoOpAcceptorVerifier{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTestAclWithRoot(keys *accountdata.AccountKeys, root *consensusproto.RawRecordWithId) (AclList, error) {
|
||||||
|
st, err := liststorage.NewInMemoryAclListStorage(root.Id, []*consensusproto.RawRecordWithId{
|
||||||
|
root,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return BuildAclListWithIdentity(keys, st, NoOpAcceptorVerifier{})
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,8 +7,8 @@ package mock_list
|
|||||||
import (
|
import (
|
||||||
reflect "reflect"
|
reflect "reflect"
|
||||||
|
|
||||||
aclrecordproto "github.com/anyproto/any-sync/commonspace/object/acl/aclrecordproto"
|
|
||||||
list "github.com/anyproto/any-sync/commonspace/object/acl/list"
|
list "github.com/anyproto/any-sync/commonspace/object/acl/list"
|
||||||
|
consensusproto "github.com/anyproto/any-sync/consensus/consensusproto"
|
||||||
crypto "github.com/anyproto/any-sync/util/crypto"
|
crypto "github.com/anyproto/any-sync/util/crypto"
|
||||||
gomock "go.uber.org/mock/gomock"
|
gomock "go.uber.org/mock/gomock"
|
||||||
)
|
)
|
||||||
@ -51,12 +51,11 @@ func (mr *MockAclListMockRecorder) AclState() *gomock.Call {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// AddRawRecord mocks base method.
|
// AddRawRecord mocks base method.
|
||||||
func (m *MockAclList) AddRawRecord(arg0 *aclrecordproto.RawAclRecordWithId) (bool, error) {
|
func (m *MockAclList) AddRawRecord(arg0 *consensusproto.RawRecordWithId) error {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "AddRawRecord", arg0)
|
ret := m.ctrl.Call(m, "AddRawRecord", arg0)
|
||||||
ret0, _ := ret[0].(bool)
|
ret0, _ := ret[0].(error)
|
||||||
ret1, _ := ret[1].(error)
|
return ret0
|
||||||
return ret0, ret1
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddRawRecord indicates an expected call of AddRawRecord.
|
// AddRawRecord indicates an expected call of AddRawRecord.
|
||||||
@ -94,6 +93,21 @@ func (mr *MockAclListMockRecorder) Get(arg0 interface{}) *gomock.Call {
|
|||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockAclList)(nil).Get), arg0)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockAclList)(nil).Get), arg0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetIndex mocks base method.
|
||||||
|
func (m *MockAclList) GetIndex(arg0 int) (*list.AclRecord, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "GetIndex", arg0)
|
||||||
|
ret0, _ := ret[0].(*list.AclRecord)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetIndex indicates an expected call of GetIndex.
|
||||||
|
func (mr *MockAclListMockRecorder) GetIndex(arg0 interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetIndex", reflect.TypeOf((*MockAclList)(nil).GetIndex), arg0)
|
||||||
|
}
|
||||||
|
|
||||||
// Head mocks base method.
|
// Head mocks base method.
|
||||||
func (m *MockAclList) Head() *list.AclRecord {
|
func (m *MockAclList) Head() *list.AclRecord {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
@ -211,6 +225,20 @@ func (mr *MockAclListMockRecorder) RUnlock() *gomock.Call {
|
|||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RUnlock", reflect.TypeOf((*MockAclList)(nil).RUnlock))
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RUnlock", reflect.TypeOf((*MockAclList)(nil).RUnlock))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RecordBuilder mocks base method.
|
||||||
|
func (m *MockAclList) RecordBuilder() list.AclRecordBuilder {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "RecordBuilder")
|
||||||
|
ret0, _ := ret[0].(list.AclRecordBuilder)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// RecordBuilder indicates an expected call of RecordBuilder.
|
||||||
|
func (mr *MockAclListMockRecorder) RecordBuilder() *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordBuilder", reflect.TypeOf((*MockAclList)(nil).RecordBuilder))
|
||||||
|
}
|
||||||
|
|
||||||
// Records mocks base method.
|
// Records mocks base method.
|
||||||
func (m *MockAclList) Records() []*list.AclRecord {
|
func (m *MockAclList) Records() []*list.AclRecord {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
@ -226,10 +254,10 @@ func (mr *MockAclListMockRecorder) Records() *gomock.Call {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Root mocks base method.
|
// Root mocks base method.
|
||||||
func (m *MockAclList) Root() *aclrecordproto.RawAclRecordWithId {
|
func (m *MockAclList) Root() *consensusproto.RawRecordWithId {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "Root")
|
ret := m.ctrl.Call(m, "Root")
|
||||||
ret0, _ := ret[0].(*aclrecordproto.RawAclRecordWithId)
|
ret0, _ := ret[0].(*consensusproto.RawRecordWithId)
|
||||||
return ret0
|
return ret0
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -250,3 +278,17 @@ func (mr *MockAclListMockRecorder) Unlock() *gomock.Call {
|
|||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Unlock", reflect.TypeOf((*MockAclList)(nil).Unlock))
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Unlock", reflect.TypeOf((*MockAclList)(nil).Unlock))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ValidateRawRecord mocks base method.
|
||||||
|
func (m *MockAclList) ValidateRawRecord(arg0 *consensusproto.RawRecord) error {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "ValidateRawRecord", arg0)
|
||||||
|
ret0, _ := ret[0].(error)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateRawRecord indicates an expected call of ValidateRawRecord.
|
||||||
|
func (mr *MockAclListMockRecorder) ValidateRawRecord(arg0 interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ValidateRawRecord", reflect.TypeOf((*MockAclList)(nil).ValidateRawRecord), arg0)
|
||||||
|
}
|
||||||
|
|||||||
@ -8,7 +8,6 @@ import (
|
|||||||
type AclRecord struct {
|
type AclRecord struct {
|
||||||
Id string
|
Id string
|
||||||
PrevId string
|
PrevId string
|
||||||
ReadKeyId string
|
|
||||||
Timestamp int64
|
Timestamp int64
|
||||||
Data []byte
|
Data []byte
|
||||||
Identity crypto.PubKey
|
Identity crypto.PubKey
|
||||||
@ -16,7 +15,55 @@ type AclRecord struct {
|
|||||||
Signature []byte
|
Signature []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
type AclUserState struct {
|
type RequestRecord struct {
|
||||||
PubKey crypto.PubKey
|
RequestIdentity crypto.PubKey
|
||||||
Permissions aclrecordproto.AclUserPermissions
|
RequestMetadata []byte
|
||||||
|
Type RequestType
|
||||||
|
}
|
||||||
|
|
||||||
|
type AclUserState struct {
|
||||||
|
PubKey crypto.PubKey
|
||||||
|
Permissions AclPermissions
|
||||||
|
RequestMetadata []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
type RequestType int
|
||||||
|
|
||||||
|
const (
|
||||||
|
RequestTypeRemove RequestType = iota
|
||||||
|
RequestTypeJoin
|
||||||
|
)
|
||||||
|
|
||||||
|
type AclPermissions aclrecordproto.AclUserPermissions
|
||||||
|
|
||||||
|
func (p AclPermissions) NoPermissions() bool {
|
||||||
|
return aclrecordproto.AclUserPermissions(p) == aclrecordproto.AclUserPermissions_None
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p AclPermissions) IsOwner() bool {
|
||||||
|
return aclrecordproto.AclUserPermissions(p) == aclrecordproto.AclUserPermissions_Owner
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p AclPermissions) CanWrite() bool {
|
||||||
|
switch aclrecordproto.AclUserPermissions(p) {
|
||||||
|
case aclrecordproto.AclUserPermissions_Admin:
|
||||||
|
return true
|
||||||
|
case aclrecordproto.AclUserPermissions_Writer:
|
||||||
|
return true
|
||||||
|
case aclrecordproto.AclUserPermissions_Owner:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p AclPermissions) CanManageAccounts() bool {
|
||||||
|
switch aclrecordproto.AclUserPermissions(p) {
|
||||||
|
case aclrecordproto.AclUserPermissions_Admin:
|
||||||
|
return true
|
||||||
|
case aclrecordproto.AclUserPermissions_Owner:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
218
commonspace/object/acl/list/validator.go
Normal file
218
commonspace/object/acl/list/validator.go
Normal file
@ -0,0 +1,218 @@
|
|||||||
|
package list
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/anyproto/any-sync/commonspace/object/acl/aclrecordproto"
|
||||||
|
"github.com/anyproto/any-sync/util/crypto"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ContentValidator interface {
|
||||||
|
ValidateAclRecordContents(ch *AclRecord) (err error)
|
||||||
|
ValidatePermissionChange(ch *aclrecordproto.AclAccountPermissionChange, authorIdentity crypto.PubKey) (err error)
|
||||||
|
ValidateInvite(ch *aclrecordproto.AclAccountInvite, 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)
|
||||||
|
ValidateRequestDecline(ch *aclrecordproto.AclAccountRequestDecline, authorIdentity crypto.PubKey) (err error)
|
||||||
|
ValidateAccountRemove(ch *aclrecordproto.AclAccountRemove, authorIdentity crypto.PubKey) (err error)
|
||||||
|
ValidateRequestRemove(ch *aclrecordproto.AclAccountRequestRemove, authorIdentity crypto.PubKey) (err error)
|
||||||
|
ValidateReadKeyChange(ch *aclrecordproto.AclReadKeyChange, authorIdentity crypto.PubKey) (err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type contentValidator struct {
|
||||||
|
keyStore crypto.KeyStorage
|
||||||
|
aclState *AclState
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *contentValidator) ValidateAclRecordContents(ch *AclRecord) (err error) {
|
||||||
|
if ch.PrevId != c.aclState.lastRecordId {
|
||||||
|
return ErrIncorrectRecordSequence
|
||||||
|
}
|
||||||
|
aclData := ch.Model.(*aclrecordproto.AclData)
|
||||||
|
for _, content := range aclData.AclContent {
|
||||||
|
err = c.validateAclRecordContent(content, ch.Identity)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *contentValidator) validateAclRecordContent(ch *aclrecordproto.AclContentValue, authorIdentity crypto.PubKey) (err error) {
|
||||||
|
switch {
|
||||||
|
case ch.GetPermissionChange() != nil:
|
||||||
|
return c.ValidatePermissionChange(ch.GetPermissionChange(), authorIdentity)
|
||||||
|
case ch.GetInvite() != nil:
|
||||||
|
return c.ValidateInvite(ch.GetInvite(), authorIdentity)
|
||||||
|
case ch.GetInviteRevoke() != nil:
|
||||||
|
return c.ValidateInviteRevoke(ch.GetInviteRevoke(), authorIdentity)
|
||||||
|
case ch.GetRequestJoin() != nil:
|
||||||
|
return c.ValidateRequestJoin(ch.GetRequestJoin(), authorIdentity)
|
||||||
|
case ch.GetRequestAccept() != nil:
|
||||||
|
return c.ValidateRequestAccept(ch.GetRequestAccept(), authorIdentity)
|
||||||
|
case ch.GetRequestDecline() != nil:
|
||||||
|
return c.ValidateRequestDecline(ch.GetRequestDecline(), authorIdentity)
|
||||||
|
case ch.GetAccountRemove() != nil:
|
||||||
|
return c.ValidateAccountRemove(ch.GetAccountRemove(), authorIdentity)
|
||||||
|
case ch.GetAccountRequestRemove() != nil:
|
||||||
|
return c.ValidateRequestRemove(ch.GetAccountRequestRemove(), authorIdentity)
|
||||||
|
case ch.GetReadKeyChange() != nil:
|
||||||
|
return c.ValidateReadKeyChange(ch.GetReadKeyChange(), authorIdentity)
|
||||||
|
default:
|
||||||
|
return ErrUnexpectedContentType
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *contentValidator) ValidatePermissionChange(ch *aclrecordproto.AclAccountPermissionChange, authorIdentity crypto.PubKey) (err error) {
|
||||||
|
if !c.aclState.Permissions(authorIdentity).CanManageAccounts() {
|
||||||
|
return ErrInsufficientPermissions
|
||||||
|
}
|
||||||
|
chIdentity, err := c.keyStore.PubKeyFromProto(ch.Identity)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, exists := c.aclState.userStates[mapKeyFromPubKey(chIdentity)]
|
||||||
|
if !exists {
|
||||||
|
return ErrNoSuchAccount
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *contentValidator) ValidateInvite(ch *aclrecordproto.AclAccountInvite, authorIdentity crypto.PubKey) (err error) {
|
||||||
|
if !c.aclState.Permissions(authorIdentity).CanManageAccounts() {
|
||||||
|
return ErrInsufficientPermissions
|
||||||
|
}
|
||||||
|
_, err = c.keyStore.PubKeyFromProto(ch.InviteKey)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *contentValidator) ValidateInviteRevoke(ch *aclrecordproto.AclAccountInviteRevoke, authorIdentity crypto.PubKey) (err error) {
|
||||||
|
if !c.aclState.Permissions(authorIdentity).CanManageAccounts() {
|
||||||
|
return ErrInsufficientPermissions
|
||||||
|
}
|
||||||
|
_, exists := c.aclState.inviteKeys[ch.InviteRecordId]
|
||||||
|
if !exists {
|
||||||
|
return ErrNoSuchInvite
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *contentValidator) ValidateRequestJoin(ch *aclrecordproto.AclAccountRequestJoin, authorIdentity crypto.PubKey) (err error) {
|
||||||
|
inviteKey, exists := c.aclState.inviteKeys[ch.InviteRecordId]
|
||||||
|
if !exists {
|
||||||
|
return ErrNoSuchInvite
|
||||||
|
}
|
||||||
|
inviteIdentity, err := c.keyStore.PubKeyFromProto(ch.InviteIdentity)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, exists := c.aclState.pendingRequests[mapKeyFromPubKey(inviteIdentity)]; exists {
|
||||||
|
return ErrPendingRequest
|
||||||
|
}
|
||||||
|
if !authorIdentity.Equals(inviteIdentity) {
|
||||||
|
return ErrIncorrectIdentity
|
||||||
|
}
|
||||||
|
rawInviteIdentity, err := inviteIdentity.Raw()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ok, err := inviteKey.Verify(rawInviteIdentity, ch.InviteIdentitySignature)
|
||||||
|
if err != nil {
|
||||||
|
return ErrInvalidSignature
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
return ErrInvalidSignature
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *contentValidator) ValidateRequestAccept(ch *aclrecordproto.AclAccountRequestAccept, authorIdentity crypto.PubKey) (err error) {
|
||||||
|
if !c.aclState.Permissions(authorIdentity).CanManageAccounts() {
|
||||||
|
return ErrInsufficientPermissions
|
||||||
|
}
|
||||||
|
record, exists := c.aclState.requestRecords[ch.RequestRecordId]
|
||||||
|
if !exists {
|
||||||
|
return ErrNoSuchRequest
|
||||||
|
}
|
||||||
|
acceptIdentity, err := c.keyStore.PubKeyFromProto(ch.Identity)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !acceptIdentity.Equals(record.RequestIdentity) {
|
||||||
|
return ErrIncorrectIdentity
|
||||||
|
}
|
||||||
|
if ch.Permissions == aclrecordproto.AclUserPermissions_Owner {
|
||||||
|
return ErrInsufficientPermissions
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *contentValidator) ValidateRequestDecline(ch *aclrecordproto.AclAccountRequestDecline, authorIdentity crypto.PubKey) (err error) {
|
||||||
|
if !c.aclState.Permissions(authorIdentity).CanManageAccounts() {
|
||||||
|
return ErrInsufficientPermissions
|
||||||
|
}
|
||||||
|
_, exists := c.aclState.requestRecords[ch.RequestRecordId]
|
||||||
|
if !exists {
|
||||||
|
return ErrNoSuchRequest
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *contentValidator) ValidateAccountRemove(ch *aclrecordproto.AclAccountRemove, authorIdentity crypto.PubKey) (err error) {
|
||||||
|
if !c.aclState.Permissions(authorIdentity).CanManageAccounts() {
|
||||||
|
return ErrInsufficientPermissions
|
||||||
|
}
|
||||||
|
seenIdentities := map[string]struct{}{}
|
||||||
|
for _, rawIdentity := range ch.Identities {
|
||||||
|
identity, err := c.keyStore.PubKeyFromProto(rawIdentity)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if identity.Equals(authorIdentity) {
|
||||||
|
return ErrInsufficientPermissions
|
||||||
|
}
|
||||||
|
permissions := c.aclState.Permissions(identity)
|
||||||
|
if permissions.NoPermissions() {
|
||||||
|
return ErrNoSuchAccount
|
||||||
|
}
|
||||||
|
if permissions.IsOwner() {
|
||||||
|
return ErrInsufficientPermissions
|
||||||
|
}
|
||||||
|
idKey := mapKeyFromPubKey(identity)
|
||||||
|
if _, exists := seenIdentities[idKey]; exists {
|
||||||
|
return ErrDuplicateAccounts
|
||||||
|
}
|
||||||
|
seenIdentities[mapKeyFromPubKey(identity)] = struct{}{}
|
||||||
|
}
|
||||||
|
return c.validateAccountReadKeys(ch.AccountKeys, len(c.aclState.userStates)-len(ch.Identities))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *contentValidator) ValidateRequestRemove(ch *aclrecordproto.AclAccountRequestRemove, authorIdentity crypto.PubKey) (err error) {
|
||||||
|
if c.aclState.Permissions(authorIdentity).NoPermissions() {
|
||||||
|
return ErrInsufficientPermissions
|
||||||
|
}
|
||||||
|
if _, exists := c.aclState.pendingRequests[mapKeyFromPubKey(authorIdentity)]; exists {
|
||||||
|
return ErrPendingRequest
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *contentValidator) ValidateReadKeyChange(ch *aclrecordproto.AclReadKeyChange, authorIdentity crypto.PubKey) (err error) {
|
||||||
|
return c.validateAccountReadKeys(ch.AccountKeys, len(c.aclState.userStates))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *contentValidator) validateAccountReadKeys(accountKeys []*aclrecordproto.AclEncryptedReadKey, usersNum int) (err error) {
|
||||||
|
if len(accountKeys) != usersNum {
|
||||||
|
return ErrIncorrectNumberOfAccounts
|
||||||
|
}
|
||||||
|
for _, encKeys := range accountKeys {
|
||||||
|
identity, err := c.keyStore.PubKeyFromProto(encKeys.Identity)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, exists := c.aclState.userStates[mapKeyFromPubKey(identity)]
|
||||||
|
if !exists {
|
||||||
|
return ErrNoSuchAccount
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
@ -3,24 +3,26 @@ package liststorage
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/acl/aclrecordproto"
|
|
||||||
|
"github.com/anyproto/any-sync/consensus/consensusproto"
|
||||||
|
|
||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
type inMemoryAclListStorage struct {
|
type inMemoryAclListStorage struct {
|
||||||
id string
|
id string
|
||||||
root *aclrecordproto.RawAclRecordWithId
|
root *consensusproto.RawRecordWithId
|
||||||
head string
|
head string
|
||||||
records map[string]*aclrecordproto.RawAclRecordWithId
|
records map[string]*consensusproto.RawRecordWithId
|
||||||
|
|
||||||
sync.RWMutex
|
sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewInMemoryAclListStorage(
|
func NewInMemoryAclListStorage(
|
||||||
id string,
|
id string,
|
||||||
records []*aclrecordproto.RawAclRecordWithId) (ListStorage, error) {
|
records []*consensusproto.RawRecordWithId) (ListStorage, error) {
|
||||||
|
|
||||||
allRecords := make(map[string]*aclrecordproto.RawAclRecordWithId)
|
allRecords := make(map[string]*consensusproto.RawRecordWithId)
|
||||||
for _, ch := range records {
|
for _, ch := range records {
|
||||||
allRecords[ch.Id] = ch
|
allRecords[ch.Id] = ch
|
||||||
}
|
}
|
||||||
@ -41,7 +43,7 @@ func (t *inMemoryAclListStorage) Id() string {
|
|||||||
return t.id
|
return t.id
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *inMemoryAclListStorage) Root() (*aclrecordproto.RawAclRecordWithId, error) {
|
func (t *inMemoryAclListStorage) Root() (*consensusproto.RawRecordWithId, error) {
|
||||||
t.RLock()
|
t.RLock()
|
||||||
defer t.RUnlock()
|
defer t.RUnlock()
|
||||||
return t.root, nil
|
return t.root, nil
|
||||||
@ -60,7 +62,7 @@ func (t *inMemoryAclListStorage) SetHead(head string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *inMemoryAclListStorage) AddRawRecord(ctx context.Context, record *aclrecordproto.RawAclRecordWithId) error {
|
func (t *inMemoryAclListStorage) AddRawRecord(ctx context.Context, record *consensusproto.RawRecordWithId) error {
|
||||||
t.Lock()
|
t.Lock()
|
||||||
defer t.Unlock()
|
defer t.Unlock()
|
||||||
// TODO: better to do deep copy
|
// TODO: better to do deep copy
|
||||||
@ -68,7 +70,7 @@ func (t *inMemoryAclListStorage) AddRawRecord(ctx context.Context, record *aclre
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *inMemoryAclListStorage) GetRawRecord(ctx context.Context, recordId string) (*aclrecordproto.RawAclRecordWithId, error) {
|
func (t *inMemoryAclListStorage) GetRawRecord(ctx context.Context, recordId string) (*consensusproto.RawRecordWithId, error) {
|
||||||
t.RLock()
|
t.RLock()
|
||||||
defer t.RUnlock()
|
defer t.RUnlock()
|
||||||
if res, exists := t.records[recordId]; exists {
|
if res, exists := t.records[recordId]; exists {
|
||||||
|
|||||||
@ -4,7 +4,8 @@ package liststorage
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/acl/aclrecordproto"
|
|
||||||
|
"github.com/anyproto/any-sync/consensus/consensusproto"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -14,15 +15,15 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Exporter interface {
|
type Exporter interface {
|
||||||
ListStorage(root *aclrecordproto.RawAclRecordWithId) (ListStorage, error)
|
ListStorage(root *consensusproto.RawRecordWithId) (ListStorage, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type ListStorage interface {
|
type ListStorage interface {
|
||||||
Id() string
|
Id() string
|
||||||
Root() (*aclrecordproto.RawAclRecordWithId, error)
|
Root() (*consensusproto.RawRecordWithId, error)
|
||||||
Head() (string, error)
|
Head() (string, error)
|
||||||
SetHead(headId string) error
|
SetHead(headId string) error
|
||||||
|
|
||||||
GetRawRecord(ctx context.Context, id string) (*aclrecordproto.RawAclRecordWithId, error)
|
GetRawRecord(ctx context.Context, id string) (*consensusproto.RawRecordWithId, error)
|
||||||
AddRawRecord(ctx context.Context, rec *aclrecordproto.RawAclRecordWithId) error
|
AddRawRecord(ctx context.Context, rec *consensusproto.RawRecordWithId) error
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,7 +8,7 @@ import (
|
|||||||
context "context"
|
context "context"
|
||||||
reflect "reflect"
|
reflect "reflect"
|
||||||
|
|
||||||
aclrecordproto "github.com/anyproto/any-sync/commonspace/object/acl/aclrecordproto"
|
consensusproto "github.com/anyproto/any-sync/consensus/consensusproto"
|
||||||
gomock "go.uber.org/mock/gomock"
|
gomock "go.uber.org/mock/gomock"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -36,7 +36,7 @@ func (m *MockListStorage) EXPECT() *MockListStorageMockRecorder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// AddRawRecord mocks base method.
|
// AddRawRecord mocks base method.
|
||||||
func (m *MockListStorage) AddRawRecord(arg0 context.Context, arg1 *aclrecordproto.RawAclRecordWithId) error {
|
func (m *MockListStorage) AddRawRecord(arg0 context.Context, arg1 *consensusproto.RawRecordWithId) error {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "AddRawRecord", arg0, arg1)
|
ret := m.ctrl.Call(m, "AddRawRecord", arg0, arg1)
|
||||||
ret0, _ := ret[0].(error)
|
ret0, _ := ret[0].(error)
|
||||||
@ -50,10 +50,10 @@ func (mr *MockListStorageMockRecorder) AddRawRecord(arg0, arg1 interface{}) *gom
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetRawRecord mocks base method.
|
// GetRawRecord mocks base method.
|
||||||
func (m *MockListStorage) GetRawRecord(arg0 context.Context, arg1 string) (*aclrecordproto.RawAclRecordWithId, error) {
|
func (m *MockListStorage) GetRawRecord(arg0 context.Context, arg1 string) (*consensusproto.RawRecordWithId, error) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "GetRawRecord", arg0, arg1)
|
ret := m.ctrl.Call(m, "GetRawRecord", arg0, arg1)
|
||||||
ret0, _ := ret[0].(*aclrecordproto.RawAclRecordWithId)
|
ret0, _ := ret[0].(*consensusproto.RawRecordWithId)
|
||||||
ret1, _ := ret[1].(error)
|
ret1, _ := ret[1].(error)
|
||||||
return ret0, ret1
|
return ret0, ret1
|
||||||
}
|
}
|
||||||
@ -94,10 +94,10 @@ func (mr *MockListStorageMockRecorder) Id() *gomock.Call {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Root mocks base method.
|
// Root mocks base method.
|
||||||
func (m *MockListStorage) Root() (*aclrecordproto.RawAclRecordWithId, error) {
|
func (m *MockListStorage) Root() (*consensusproto.RawRecordWithId, error) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "Root")
|
ret := m.ctrl.Call(m, "Root")
|
||||||
ret0, _ := ret[0].(*aclrecordproto.RawAclRecordWithId)
|
ret0, _ := ret[0].(*consensusproto.RawRecordWithId)
|
||||||
ret1, _ := ret[1].(error)
|
ret1, _ := ret[1].(error)
|
||||||
return ret0, ret1
|
return ret0, ret1
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,6 +2,7 @@ package syncacl
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"github.com/anyproto/any-sync/accountservice"
|
"github.com/anyproto/any-sync/accountservice"
|
||||||
"github.com/anyproto/any-sync/app"
|
"github.com/anyproto/any-sync/app"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/acl/list"
|
"github.com/anyproto/any-sync/commonspace/object/acl/list"
|
||||||
@ -34,7 +35,7 @@ func (s *SyncAcl) Init(a *app.App) (err error) {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
acc := a.MustComponent(accountservice.CName).(accountservice.Service)
|
acc := a.MustComponent(accountservice.CName).(accountservice.Service)
|
||||||
s.AclList, err = list.BuildAclListWithIdentity(acc.Account(), aclStorage)
|
s.AclList, err = list.BuildAclListWithIdentity(acc.Account(), aclStorage, list.NoOpAcceptorVerifier{})
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -2,8 +2,7 @@ package syncacl
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"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/list"
|
||||||
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
||||||
)
|
)
|
||||||
@ -13,19 +12,5 @@ type syncAclHandler struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *syncAclHandler) HandleMessage(ctx context.Context, senderId string, req *spacesyncproto.ObjectSyncMessage) (err error) {
|
func (s *syncAclHandler) HandleMessage(ctx context.Context, senderId string, req *spacesyncproto.ObjectSyncMessage) (err error) {
|
||||||
aclMsg := &aclrecordproto.AclSyncMessage{}
|
return nil
|
||||||
if err = aclMsg.Unmarshal(req.Payload); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
content := aclMsg.GetContent()
|
|
||||||
switch {
|
|
||||||
case content.GetAddRecords() != nil:
|
|
||||||
return s.handleAddRecords(ctx, senderId, content.GetAddRecords())
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("unexpected aclSync message: %T", content.Value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *syncAclHandler) handleAddRecords(ctx context.Context, senderId string, addRecord *aclrecordproto.AclAddRecords) (err error) {
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,7 +15,7 @@ type TreeImportParams struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func ImportHistoryTree(params TreeImportParams) (tree objecttree.ReadableObjectTree, err error) {
|
func ImportHistoryTree(params TreeImportParams) (tree objecttree.ReadableObjectTree, err error) {
|
||||||
aclList, err := list.BuildAclList(params.ListStorage)
|
aclList, err := list.BuildAclList(params.ListStorage, list.NoOpAcceptorVerifier{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,11 +4,11 @@ package objecttree
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"github.com/anyproto/any-sync/util/crypto"
|
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/anyproto/any-sync/commonspace/object/acl/aclrecordproto"
|
"github.com/anyproto/any-sync/util/crypto"
|
||||||
|
|
||||||
"github.com/anyproto/any-sync/commonspace/object/acl/list"
|
"github.com/anyproto/any-sync/commonspace/object/acl/list"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
|
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
|
||||||
@ -248,9 +248,7 @@ func (ot *objectTree) prepareBuilderContent(content SignableChangeContent) (cnt
|
|||||||
pubKey = content.Key.GetPublic()
|
pubKey = content.Key.GetPublic()
|
||||||
readKeyId string
|
readKeyId string
|
||||||
)
|
)
|
||||||
canWrite := state.HasPermission(pubKey, aclrecordproto.AclUserPermissions_Writer) ||
|
if !state.Permissions(pubKey).CanWrite() {
|
||||||
state.HasPermission(pubKey, aclrecordproto.AclUserPermissions_Admin)
|
|
||||||
if !canWrite {
|
|
||||||
err = list.ErrInsufficientPermissions
|
err = list.ErrInsufficientPermissions
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,7 +3,7 @@ package objecttree
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"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/list"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
|
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
|
||||||
"github.com/anyproto/any-sync/util/slice"
|
"github.com/anyproto/any-sync/util/slice"
|
||||||
@ -52,20 +52,18 @@ func (v *objectTreeValidator) ValidateNewChanges(tree *Tree, aclList list.AclLis
|
|||||||
|
|
||||||
func (v *objectTreeValidator) validateChange(tree *Tree, aclList list.AclList, c *Change) (err error) {
|
func (v *objectTreeValidator) validateChange(tree *Tree, aclList list.AclList, c *Change) (err error) {
|
||||||
var (
|
var (
|
||||||
perm list.AclUserState
|
userState list.AclUserState
|
||||||
state = aclList.AclState()
|
state = aclList.AclState()
|
||||||
)
|
)
|
||||||
// checking if the user could write
|
// checking if the user could write
|
||||||
perm, err = state.StateAtRecord(c.AclHeadId, c.Identity)
|
userState, err = state.StateAtRecord(c.AclHeadId, c.Identity)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if !userState.Permissions.CanWrite() {
|
||||||
if perm.Permissions != aclrecordproto.AclUserPermissions_Writer && perm.Permissions != aclrecordproto.AclUserPermissions_Admin {
|
|
||||||
err = list.ErrInsufficientPermissions
|
err = list.ErrInsufficientPermissions
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.Id == tree.RootId() {
|
if c.Id == tree.RootId() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@ -149,9 +149,6 @@ func (s *objectSync) processHandleMessage(msg HandleMessage) {
|
|||||||
err = context.DeadlineExceeded
|
err = context.DeadlineExceeded
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var cancel context.CancelFunc
|
|
||||||
ctx, cancel = context.WithDeadline(ctx, msg.Deadline)
|
|
||||||
defer cancel()
|
|
||||||
}
|
}
|
||||||
if err = s.handleMessage(ctx, msg.SenderId, msg.Message); err != nil {
|
if err = s.handleMessage(ctx, msg.SenderId, msg.Message); err != nil {
|
||||||
if msg.Message.ObjectId != "" {
|
if msg.Message.ObjectId != "" {
|
||||||
|
|||||||
@ -2,20 +2,22 @@ package commonspace
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"hash/fnv"
|
||||||
|
"math/rand"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/anyproto/any-sync/commonspace/object/acl/aclrecordproto"
|
"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/list"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
||||||
"github.com/anyproto/any-sync/commonspace/spacestorage"
|
"github.com/anyproto/any-sync/commonspace/spacestorage"
|
||||||
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
||||||
|
"github.com/anyproto/any-sync/consensus/consensusproto"
|
||||||
"github.com/anyproto/any-sync/util/cidutil"
|
"github.com/anyproto/any-sync/util/cidutil"
|
||||||
"github.com/anyproto/any-sync/util/crypto"
|
"github.com/anyproto/any-sync/util/crypto"
|
||||||
"github.com/gogo/protobuf/proto"
|
"github.com/gogo/protobuf/proto"
|
||||||
"hash/fnv"
|
|
||||||
"math/rand"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -71,7 +73,7 @@ func storagePayloadForSpaceCreate(payload SpaceCreatePayload) (storagePayload sp
|
|||||||
|
|
||||||
// building acl root
|
// building acl root
|
||||||
keyStorage := crypto.NewKeyStorage()
|
keyStorage := crypto.NewKeyStorage()
|
||||||
aclBuilder := list.NewAclRecordBuilder("", keyStorage)
|
aclBuilder := list.NewAclRecordBuilder("", keyStorage, nil, list.NoOpAcceptorVerifier{})
|
||||||
aclRoot, err := aclBuilder.BuildRoot(list.RootContent{
|
aclRoot, err := aclBuilder.BuildRoot(list.RootContent{
|
||||||
PrivKey: payload.SigningKey,
|
PrivKey: payload.SigningKey,
|
||||||
MasterKey: payload.MasterKey,
|
MasterKey: payload.MasterKey,
|
||||||
@ -158,7 +160,7 @@ func storagePayloadForSpaceDerive(payload SpaceDerivePayload) (storagePayload sp
|
|||||||
|
|
||||||
// building acl root
|
// building acl root
|
||||||
keyStorage := crypto.NewKeyStorage()
|
keyStorage := crypto.NewKeyStorage()
|
||||||
aclBuilder := list.NewAclRecordBuilder("", keyStorage)
|
aclBuilder := list.NewAclRecordBuilder("", keyStorage, nil, list.NoOpAcceptorVerifier{})
|
||||||
aclRoot, err := aclBuilder.BuildRoot(list.RootContent{
|
aclRoot, err := aclBuilder.BuildRoot(list.RootContent{
|
||||||
PrivKey: payload.SigningKey,
|
PrivKey: payload.SigningKey,
|
||||||
MasterKey: payload.MasterKey,
|
MasterKey: payload.MasterKey,
|
||||||
@ -254,12 +256,12 @@ func ValidateSpaceHeader(rawHeaderWithId *spacesyncproto.RawSpaceHeaderWithId, i
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateCreateSpaceAclPayload(rawWithId *aclrecordproto.RawAclRecordWithId) (spaceId string, err error) {
|
func validateCreateSpaceAclPayload(rawWithId *consensusproto.RawRecordWithId) (spaceId string, err error) {
|
||||||
if !cidutil.VerifyCid(rawWithId.Payload, rawWithId.Id) {
|
if !cidutil.VerifyCid(rawWithId.Payload, rawWithId.Id) {
|
||||||
err = objecttree.ErrIncorrectCid
|
err = objecttree.ErrIncorrectCid
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var rawAcl aclrecordproto.RawAclRecord
|
var rawAcl consensusproto.RawRecord
|
||||||
err = proto.Unmarshal(rawWithId.Payload, &rawAcl)
|
err = proto.Unmarshal(rawWithId.Payload, &rawAcl)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
|
|||||||
@ -2,20 +2,22 @@ package commonspace
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
|
"strconv"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/anyproto/any-sync/commonspace/object/accountdata"
|
"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/aclrecordproto"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
||||||
"github.com/anyproto/any-sync/commonspace/spacestorage"
|
"github.com/anyproto/any-sync/commonspace/spacestorage"
|
||||||
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
||||||
|
"github.com/anyproto/any-sync/consensus/consensusproto"
|
||||||
"github.com/anyproto/any-sync/util/cidutil"
|
"github.com/anyproto/any-sync/util/cidutil"
|
||||||
"github.com/anyproto/any-sync/util/crypto"
|
"github.com/anyproto/any-sync/util/crypto"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"math/rand"
|
|
||||||
"strconv"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestSuccessHeaderPayloadForSpaceCreate(t *testing.T) {
|
func TestSuccessHeaderPayloadForSpaceCreate(t *testing.T) {
|
||||||
@ -188,14 +190,14 @@ func TestFailAclPayloadSpace_IncorrectCid(t *testing.T) {
|
|||||||
marshalled, err := aclRoot.Marshal()
|
marshalled, err := aclRoot.Marshal()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
signature, err := accountKeys.SignKey.Sign(marshalled)
|
signature, err := accountKeys.SignKey.Sign(marshalled)
|
||||||
rawAclRecord := &aclrecordproto.RawAclRecord{
|
rawAclRecord := &consensusproto.RawRecord{
|
||||||
Payload: marshalled,
|
Payload: marshalled,
|
||||||
Signature: signature,
|
Signature: signature,
|
||||||
}
|
}
|
||||||
marshalledRaw, err := rawAclRecord.Marshal()
|
marshalledRaw, err := rawAclRecord.Marshal()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
aclHeadId := "rand"
|
aclHeadId := "rand"
|
||||||
rawWithId := &aclrecordproto.RawAclRecordWithId{
|
rawWithId := &consensusproto.RawRecordWithId{
|
||||||
Payload: marshalledRaw,
|
Payload: marshalledRaw,
|
||||||
Id: aclHeadId,
|
Id: aclHeadId,
|
||||||
}
|
}
|
||||||
@ -230,7 +232,7 @@ func TestFailedAclPayloadSpace_IncorrectSignature(t *testing.T) {
|
|||||||
}
|
}
|
||||||
marshalled, err := aclRoot.Marshal()
|
marshalled, err := aclRoot.Marshal()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
rawAclRecord := &aclrecordproto.RawAclRecord{
|
rawAclRecord := &consensusproto.RawRecord{
|
||||||
Payload: marshalled,
|
Payload: marshalled,
|
||||||
Signature: marshalled,
|
Signature: marshalled,
|
||||||
}
|
}
|
||||||
@ -238,7 +240,7 @@ func TestFailedAclPayloadSpace_IncorrectSignature(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
aclHeadId, err := cidutil.NewCidFromBytes(marshalledRaw)
|
aclHeadId, err := cidutil.NewCidFromBytes(marshalledRaw)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
rawWithId := &aclrecordproto.RawAclRecordWithId{
|
rawWithId := &consensusproto.RawRecordWithId{
|
||||||
Payload: marshalledRaw,
|
Payload: marshalledRaw,
|
||||||
Id: aclHeadId,
|
Id: aclHeadId,
|
||||||
}
|
}
|
||||||
@ -286,7 +288,7 @@ func TestFailedAclPayloadSpace_IncorrectIdentitySignature(t *testing.T) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
signature, err := accountKeys.SignKey.Sign(marshalled)
|
signature, err := accountKeys.SignKey.Sign(marshalled)
|
||||||
rawAclRecord := &aclrecordproto.RawAclRecord{
|
rawAclRecord := &consensusproto.RawRecord{
|
||||||
Payload: marshalled,
|
Payload: marshalled,
|
||||||
Signature: signature,
|
Signature: signature,
|
||||||
}
|
}
|
||||||
@ -298,7 +300,7 @@ func TestFailedAclPayloadSpace_IncorrectIdentitySignature(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
rawWithId := &aclrecordproto.RawAclRecordWithId{
|
rawWithId := &consensusproto.RawRecordWithId{
|
||||||
Payload: marshalledRaw,
|
Payload: marshalledRaw,
|
||||||
Id: aclHeadId,
|
Id: aclHeadId,
|
||||||
}
|
}
|
||||||
@ -540,7 +542,7 @@ func rawSettingsPayload(accountKeys *accountdata.AccountKeys, spaceId, aclHeadId
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func rawAclWithId(accountKeys *accountdata.AccountKeys, spaceId string) (aclHeadId string, rawWithId *aclrecordproto.RawAclRecordWithId, err error) {
|
func rawAclWithId(accountKeys *accountdata.AccountKeys, spaceId string) (aclHeadId string, rawWithId *consensusproto.RawRecordWithId, err error) {
|
||||||
// TODO: use same storage creation methods as we use in spaces
|
// TODO: use same storage creation methods as we use in spaces
|
||||||
readKeyBytes := make([]byte, 32)
|
readKeyBytes := make([]byte, 32)
|
||||||
_, err = rand.Read(readKeyBytes)
|
_, err = rand.Read(readKeyBytes)
|
||||||
@ -582,7 +584,7 @@ func rawAclWithId(accountKeys *accountdata.AccountKeys, spaceId string) (aclHead
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
signature, err := accountKeys.SignKey.Sign(marshalled)
|
signature, err := accountKeys.SignKey.Sign(marshalled)
|
||||||
rawAclRecord := &aclrecordproto.RawAclRecord{
|
rawAclRecord := &consensusproto.RawRecord{
|
||||||
Payload: marshalled,
|
Payload: marshalled,
|
||||||
Signature: signature,
|
Signature: signature,
|
||||||
}
|
}
|
||||||
@ -594,7 +596,7 @@ func rawAclWithId(accountKeys *accountdata.AccountKeys, spaceId string) (aclHead
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
rawWithId = &aclrecordproto.RawAclRecordWithId{
|
rawWithId = &consensusproto.RawRecordWithId{
|
||||||
Payload: marshalledRaw,
|
Payload: marshalledRaw,
|
||||||
Id: aclHeadId,
|
Id: aclHeadId,
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,6 +2,9 @@ package requestmanager
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"sync"
|
||||||
|
"testing"
|
||||||
|
|
||||||
"github.com/anyproto/any-sync/commonspace/objectsync"
|
"github.com/anyproto/any-sync/commonspace/objectsync"
|
||||||
"github.com/anyproto/any-sync/commonspace/objectsync/mock_objectsync"
|
"github.com/anyproto/any-sync/commonspace/objectsync/mock_objectsync"
|
||||||
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
||||||
@ -13,9 +16,6 @@ import (
|
|||||||
"go.uber.org/mock/gomock"
|
"go.uber.org/mock/gomock"
|
||||||
"storj.io/drpc"
|
"storj.io/drpc"
|
||||||
"storj.io/drpc/drpcconn"
|
"storj.io/drpc/drpcconn"
|
||||||
"sync"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type fixture struct {
|
type fixture struct {
|
||||||
@ -146,6 +146,7 @@ func TestRequestManager_QueueRequest(t *testing.T) {
|
|||||||
_, ok = msgs.Load("otherId1")
|
_, ok = msgs.Load("otherId1")
|
||||||
require.True(t, ok)
|
require.True(t, ok)
|
||||||
close(msgRelease)
|
close(msgRelease)
|
||||||
|
fx.requestManager.Close(context.Background())
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("no requests after close", func(t *testing.T) {
|
t.Run("no requests after close", func(t *testing.T) {
|
||||||
@ -179,11 +180,7 @@ func TestRequestManager_QueueRequest(t *testing.T) {
|
|||||||
|
|
||||||
fx.requestManager.Close(context.Background())
|
fx.requestManager.Close(context.Background())
|
||||||
close(msgRelease)
|
close(msgRelease)
|
||||||
// waiting to know if the second one is not taken
|
|
||||||
// because the manager is now closed
|
|
||||||
time.Sleep(100 * time.Millisecond)
|
|
||||||
_, ok = msgs.Load("id2")
|
_, ok = msgs.Load("id2")
|
||||||
require.False(t, ok)
|
require.False(t, ok)
|
||||||
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,6 +2,8 @@ package commonspace
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"sync/atomic"
|
||||||
|
|
||||||
"github.com/anyproto/any-sync/accountservice"
|
"github.com/anyproto/any-sync/accountservice"
|
||||||
"github.com/anyproto/any-sync/app"
|
"github.com/anyproto/any-sync/app"
|
||||||
"github.com/anyproto/any-sync/app/logger"
|
"github.com/anyproto/any-sync/app/logger"
|
||||||
@ -9,7 +11,6 @@ import (
|
|||||||
"github.com/anyproto/any-sync/commonspace/credentialprovider"
|
"github.com/anyproto/any-sync/commonspace/credentialprovider"
|
||||||
"github.com/anyproto/any-sync/commonspace/deletionstate"
|
"github.com/anyproto/any-sync/commonspace/deletionstate"
|
||||||
"github.com/anyproto/any-sync/commonspace/headsync"
|
"github.com/anyproto/any-sync/commonspace/headsync"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/acl/aclrecordproto"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/object/acl/syncacl"
|
"github.com/anyproto/any-sync/commonspace/object/acl/syncacl"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
||||||
@ -24,13 +25,13 @@ import (
|
|||||||
"github.com/anyproto/any-sync/commonspace/spacestorage"
|
"github.com/anyproto/any-sync/commonspace/spacestorage"
|
||||||
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
||||||
"github.com/anyproto/any-sync/commonspace/syncstatus"
|
"github.com/anyproto/any-sync/commonspace/syncstatus"
|
||||||
|
"github.com/anyproto/any-sync/consensus/consensusproto"
|
||||||
"github.com/anyproto/any-sync/metric"
|
"github.com/anyproto/any-sync/metric"
|
||||||
"github.com/anyproto/any-sync/net/peer"
|
"github.com/anyproto/any-sync/net/peer"
|
||||||
"github.com/anyproto/any-sync/net/pool"
|
"github.com/anyproto/any-sync/net/pool"
|
||||||
"github.com/anyproto/any-sync/net/rpc/rpcerr"
|
"github.com/anyproto/any-sync/net/rpc/rpcerr"
|
||||||
"github.com/anyproto/any-sync/nodeconf"
|
"github.com/anyproto/any-sync/nodeconf"
|
||||||
"storj.io/drpc"
|
"storj.io/drpc"
|
||||||
"sync/atomic"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const CName = "common.commonspace"
|
const CName = "common.commonspace"
|
||||||
@ -193,7 +194,7 @@ func (s *spaceService) NewSpace(ctx context.Context, id string) (Space, error) {
|
|||||||
|
|
||||||
func (s *spaceService) addSpaceStorage(ctx context.Context, spaceDescription SpaceDescription) (st spacestorage.SpaceStorage, err error) {
|
func (s *spaceService) addSpaceStorage(ctx context.Context, spaceDescription SpaceDescription) (st spacestorage.SpaceStorage, err error) {
|
||||||
payload := spacestorage.SpaceStorageCreatePayload{
|
payload := spacestorage.SpaceStorageCreatePayload{
|
||||||
AclWithId: &aclrecordproto.RawAclRecordWithId{
|
AclWithId: &consensusproto.RawRecordWithId{
|
||||||
Payload: spaceDescription.AclPayload,
|
Payload: spaceDescription.AclPayload,
|
||||||
Id: spaceDescription.AclId,
|
Id: spaceDescription.AclId,
|
||||||
},
|
},
|
||||||
@ -240,7 +241,7 @@ func (s *spaceService) getSpaceStorageFromRemote(ctx context.Context, id string)
|
|||||||
}
|
}
|
||||||
|
|
||||||
st, err = s.createSpaceStorage(spacestorage.SpaceStorageCreatePayload{
|
st, err = s.createSpaceStorage(spacestorage.SpaceStorageCreatePayload{
|
||||||
AclWithId: &aclrecordproto.RawAclRecordWithId{
|
AclWithId: &consensusproto.RawRecordWithId{
|
||||||
Payload: res.Payload.AclPayload,
|
Payload: res.Payload.AclPayload,
|
||||||
Id: res.Payload.AclPayloadId,
|
Id: res.Payload.AclPayloadId,
|
||||||
},
|
},
|
||||||
|
|||||||
@ -2,12 +2,14 @@ package spacestorage
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"github.com/anyproto/any-sync/app"
|
"github.com/anyproto/any-sync/app"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/acl/aclrecordproto"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/object/acl/liststorage"
|
"github.com/anyproto/any-sync/commonspace/object/acl/liststorage"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
|
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
|
||||||
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
||||||
|
"github.com/anyproto/any-sync/consensus/consensusproto"
|
||||||
|
|
||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -40,7 +42,7 @@ func (i *InMemorySpaceStorage) Name() (name string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewInMemorySpaceStorage(payload SpaceStorageCreatePayload) (SpaceStorage, error) {
|
func NewInMemorySpaceStorage(payload SpaceStorageCreatePayload) (SpaceStorage, error) {
|
||||||
aclStorage, err := liststorage.NewInMemoryAclListStorage(payload.AclWithId.Id, []*aclrecordproto.RawAclRecordWithId{payload.AclWithId})
|
aclStorage, err := liststorage.NewInMemoryAclListStorage(payload.AclWithId.Id, []*consensusproto.RawRecordWithId{payload.AclWithId})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,12 +4,13 @@ package spacestorage
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
|
||||||
"github.com/anyproto/any-sync/app"
|
"github.com/anyproto/any-sync/app"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/acl/aclrecordproto"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/object/acl/liststorage"
|
"github.com/anyproto/any-sync/commonspace/object/acl/liststorage"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
|
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
|
||||||
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
||||||
|
"github.com/anyproto/any-sync/consensus/consensusproto"
|
||||||
)
|
)
|
||||||
|
|
||||||
const CName = "common.commonspace.spacestorage"
|
const CName = "common.commonspace.spacestorage"
|
||||||
@ -47,7 +48,7 @@ type SpaceStorage interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type SpaceStorageCreatePayload struct {
|
type SpaceStorageCreatePayload struct {
|
||||||
AclWithId *aclrecordproto.RawAclRecordWithId
|
AclWithId *consensusproto.RawRecordWithId
|
||||||
SpaceHeaderWithId *spacesyncproto.RawSpaceHeaderWithId
|
SpaceHeaderWithId *spacesyncproto.RawSpaceHeaderWithId
|
||||||
SpaceSettingsWithId *treechangeproto.RawTreeChangeWithId
|
SpaceSettingsWithId *treechangeproto.RawTreeChangeWithId
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,11 +3,12 @@ package syncstatus
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/anyproto/any-sync/app"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/spacestate"
|
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/anyproto/any-sync/app"
|
||||||
|
"github.com/anyproto/any-sync/commonspace/spacestate"
|
||||||
|
|
||||||
"github.com/anyproto/any-sync/app/logger"
|
"github.com/anyproto/any-sync/app/logger"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
|
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
|
||||||
"github.com/anyproto/any-sync/commonspace/spacestorage"
|
"github.com/anyproto/any-sync/commonspace/spacestorage"
|
||||||
@ -178,8 +179,9 @@ func (s *syncStatusService) update(ctx context.Context) (err error) {
|
|||||||
}
|
}
|
||||||
s.treeStatusBuf = append(s.treeStatusBuf, treeStatus{treeId, treeHeads.syncStatus, treeHeads.heads})
|
s.treeStatusBuf = append(s.treeStatusBuf, treeStatus{treeId, treeHeads.syncStatus, treeHeads.heads})
|
||||||
}
|
}
|
||||||
|
nodesOnline := s.nodesOnline
|
||||||
s.Unlock()
|
s.Unlock()
|
||||||
s.updateReceiver.UpdateNodeConnection(s.nodesOnline)
|
s.updateReceiver.UpdateNodeConnection(nodesOnline)
|
||||||
for _, entry := range s.treeStatusBuf {
|
for _, entry := range s.treeStatusBuf {
|
||||||
err = s.updateReceiver.UpdateTree(ctx, entry.treeId, entry.status)
|
err = s.updateReceiver.UpdateTree(ctx, entry.treeId, entry.status)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@ -3,6 +3,12 @@ package peer
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/anyproto/any-sync/app/logger"
|
"github.com/anyproto/any-sync/app/logger"
|
||||||
"github.com/anyproto/any-sync/app/ocache"
|
"github.com/anyproto/any-sync/app/ocache"
|
||||||
"github.com/anyproto/any-sync/net/connutil"
|
"github.com/anyproto/any-sync/net/connutil"
|
||||||
@ -11,16 +17,11 @@ import (
|
|||||||
"github.com/anyproto/any-sync/net/secureservice/handshake/handshakeproto"
|
"github.com/anyproto/any-sync/net/secureservice/handshake/handshakeproto"
|
||||||
"github.com/anyproto/any-sync/net/transport"
|
"github.com/anyproto/any-sync/net/transport"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
"storj.io/drpc"
|
"storj.io/drpc"
|
||||||
"storj.io/drpc/drpcconn"
|
"storj.io/drpc/drpcconn"
|
||||||
"storj.io/drpc/drpcmanager"
|
"storj.io/drpc/drpcmanager"
|
||||||
"storj.io/drpc/drpcstream"
|
"storj.io/drpc/drpcstream"
|
||||||
"storj.io/drpc/drpcwire"
|
"storj.io/drpc/drpcwire"
|
||||||
"sync"
|
|
||||||
"sync/atomic"
|
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var log = logger.NewNamed("common.net.peer")
|
var log = logger.NewNamed("common.net.peer")
|
||||||
@ -188,7 +189,7 @@ func (p *peer) openDrpcConn(ctx context.Context) (dconn *subConn, err error) {
|
|||||||
tconn := connutil.NewLastUsageConn(conn)
|
tconn := connutil.NewLastUsageConn(conn)
|
||||||
bufSize := p.ctrl.DrpcConfig().Stream.MaxMsgSizeMb * (1 << 20)
|
bufSize := p.ctrl.DrpcConfig().Stream.MaxMsgSizeMb * (1 << 20)
|
||||||
return &subConn{
|
return &subConn{
|
||||||
Conn: drpcconn.NewWithOptions(conn, drpcconn.Options{
|
Conn: drpcconn.NewWithOptions(tconn, drpcconn.Options{
|
||||||
Manager: drpcmanager.Options{
|
Manager: drpcmanager.Options{
|
||||||
Reader: drpcwire.ReaderOptions{MaximumBufferSize: bufSize},
|
Reader: drpcwire.ReaderOptions{MaximumBufferSize: bufSize},
|
||||||
Stream: drpcstream.Options{MaximumBufferSize: bufSize},
|
Stream: drpcstream.Options{MaximumBufferSize: bufSize},
|
||||||
@ -295,7 +296,7 @@ func (p *peer) gc(ttl time.Duration) (aliveCount int) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return len(p.active) + len(p.inactive)
|
return len(p.active) + len(p.inactive) + int(p.incomingCount.Load())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *peer) Close() (err error) {
|
func (p *peer) Close() (err error) {
|
||||||
|
|||||||
@ -2,8 +2,9 @@ package crypto
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Test_EncryptDecrypt(t *testing.T) {
|
func Test_EncryptDecrypt(t *testing.T) {
|
||||||
|
|||||||
@ -1,9 +1,10 @@
|
|||||||
package strkey
|
package strkey
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"testing"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestDecode(t *testing.T) {
|
func TestDecode(t *testing.T) {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user