package list import ( "errors" "github.com/anyproto/any-sync/app/logger" "github.com/anyproto/any-sync/commonspace/object/acl/aclrecordproto" "github.com/anyproto/any-sync/util/crypto" "github.com/gogo/protobuf/proto" "go.uber.org/zap" ) var log = logger.NewNamedSugared("common.commonspace.acllist") var ( ErrNoSuchAccount = errors.New("no such account") ErrUnexpectedContentType = errors.New("unexpected content type") ErrIncorrectIdentity = errors.New("incorrect identity") ErrIncorrectInviteKey = errors.New("incorrect invite key") ErrFailedToDecrypt = errors.New("failed to decrypt key") ErrNoSuchRecord = errors.New("no such record") ErrNoSuchRequest = errors.New("no such request") ErrNoSuchInvite = errors.New("no such invite") ErrInsufficientPermissions = errors.New("insufficient permissions") ErrIncorrectPermissions = errors.New("incorrect permissions") ErrIncorrectNumberOfAccounts = errors.New("incorrect number of 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 { Identity crypto.PubKey Permission aclrecordproto.AclUserPermissions } type AclState struct { id string currentReadKeyId string userReadKeys map[string]crypto.SymKey userStates map[string]AclUserState statesAtRecord map[string][]AclUserState inviteKeys map[string]crypto.PubKey requestRecords map[string]RequestRecord key crypto.PrivKey pubKey crypto.PubKey keyStore crypto.KeyStorage totalReadKeys int lastRecordId string contentValidator ContentValidator } func newAclStateWithKeys( id string, key crypto.PrivKey) (*AclState, error) { st := &AclState{ id: id, key: key, pubKey: key.GetPublic(), userReadKeys: make(map[string]crypto.SymKey), userStates: make(map[string]AclUserState), statesAtRecord: make(map[string][]AclUserState), inviteKeys: make(map[string]crypto.PubKey), requestRecords: make(map[string]RequestRecord), keyStore: crypto.NewKeyStorage(), } st.contentValidator = &contentValidator{ keyStore: st.keyStore, aclState: st, } return st, nil } func newAclState(id string) *AclState { st := &AclState{ id: id, userReadKeys: make(map[string]crypto.SymKey), userStates: make(map[string]AclUserState), statesAtRecord: make(map[string][]AclUserState), inviteKeys: make(map[string]crypto.PubKey), requestRecords: make(map[string]RequestRecord), 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 { return st.currentReadKeyId } func (st *AclState) CurrentReadKey() (crypto.SymKey, error) { key, exists := st.userReadKeys[st.currentReadKeyId] if !exists { return nil, ErrNoReadKey } return key, nil } func (st *AclState) UserReadKeys() map[string]crypto.SymKey { return st.userReadKeys } func (st *AclState) StateAtRecord(id string, pubKey crypto.PubKey) (AclUserState, error) { userState, ok := st.statesAtRecord[id] if !ok { log.Errorf("missing record at id %s", id) return AclUserState{}, ErrNoSuchRecord } for _, perm := range userState { if perm.PubKey.Equals(pubKey) { return perm, nil } } return AclUserState{}, ErrNoSuchAccount } func (st *AclState) applyRecord(record *AclRecord) (err error) { defer func() { if err == nil { st.lastRecordId = record.Id } }() if st.lastRecordId != record.PrevId { err = ErrIncorrectRecordSequence return } // if the record is root record if record.Id == st.id { err = st.applyRoot(record) if err != nil { return } st.statesAtRecord[record.Id] = []AclUserState{ st.userStates[mapKeyFromPubKey(record.Identity)], } return } // if the model is not cached if record.Model == nil { aclData := &aclrecordproto.AclData{} err = proto.Unmarshal(record.Data, aclData) if err != nil { return } record.Model = aclData } // applying records contents err = st.applyChangeData(record) if err != nil { return } // getting all states for users at record and saving them var states []AclUserState for _, state := range st.userStates { states = append(states, state) } st.statesAtRecord[record.Id] = states return } func (st *AclState) applyRoot(record *AclRecord) (err error) { if st.key != nil && st.pubKey.Equals(record.Identity) { err = st.saveReadKeyFromRoot(record) if err != nil { return } } // adding user to the list userState := AclUserState{ PubKey: record.Identity, Permissions: AclPermissions(aclrecordproto.AclUserPermissions_Admin), } st.currentReadKeyId = record.Id st.userStates[mapKeyFromPubKey(record.Identity)] = userState st.totalReadKeys++ return } func (st *AclState) saveReadKeyFromRoot(record *AclRecord) (err error) { var readKey crypto.SymKey root, ok := record.Model.(*aclrecordproto.AclRoot) if !ok { return ErrIncorrectRoot } if root.EncryptedReadKey == nil { readKey, err = st.deriveKey() if err != nil { return } } else { readKey, err = st.decryptReadKey(root.EncryptedReadKey) if err != nil { return } } st.userReadKeys[record.Id] = readKey return } func (st *AclState) applyChangeData(record *AclRecord) (err error) { model := record.Model.(*aclrecordproto.AclData) for _, ch := range model.GetAclContent() { if err = st.applyChangeContent(ch, record.Id, record.Identity); err != nil { log.Info("error while applying changes: %v; ignore", zap.Error(err)) return err } } return nil } func (st *AclState) applyChangeContent(ch *aclrecordproto.AclContentValue, recordId string, authorIdentity crypto.PubKey) error { switch { case ch.GetPermissionChange() != nil: return st.applyPermissionChange(ch.GetPermissionChange(), recordId, authorIdentity) case ch.GetInvite() != nil: return st.applyInvite(ch.GetInvite(), recordId, authorIdentity) case ch.GetInviteRevoke() != nil: return st.applyInviteRevoke(ch.GetInviteRevoke(), recordId, authorIdentity) case ch.GetRequestJoin() != nil: return st.applyRequestJoin(ch.GetRequestJoin(), recordId, authorIdentity) case ch.GetRequestAccept() != nil: 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) default: return ErrUnexpectedContentType } } func (st *AclState) applyPermissionChange(ch *aclrecordproto.AclAccountPermissionChange, recordId string, authorIdentity crypto.PubKey) error { chIdentity, err := st.keyStore.PubKeyFromProto(ch.Identity) if err != nil { return err } err = st.contentValidator.ValidatePermissionChange(ch, authorIdentity) if err != nil { return err } stringKey := mapKeyFromPubKey(chIdentity) state, _ := st.userStates[stringKey] state.Permissions = AclPermissions(ch.Permissions) st.userStates[stringKey] = state return nil } func (st *AclState) applyInvite(ch *aclrecordproto.AclAccountInvite, recordId string, authorIdentity crypto.PubKey) error { 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 } 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 } 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.requestRecords[recordId] = RequestRecord{ RequestIdentity: authorIdentity, RequestMetadata: ch.Metadata, } return nil } 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, } if !st.pubKey.Equals(acceptIdentity) { return nil } res, err := st.key.Decrypt(ch.EncryptedReadKeys) if err != nil { return err } keys := &aclrecordproto.AclReadKeys{} err = proto.Unmarshal(res, keys) if err != nil { return err } for keyId, key := range keys.ReadKeys { sym, err := crypto.UnmarshallAESKey(key) if err != nil { return err } st.userReadKeys[keyId] = 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.requestRecords, ch.RequestRecordId) return nil } func (st *AclState) applyAccountRemove(ch *aclrecordproto.AclAccountRemove, recordId string, authorIdentity crypto.PubKey) error { err := st.contentValidator.ValidateRemove(ch, authorIdentity) if err != nil { return err } 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 } func (st *AclState) decryptReadKey(msg []byte) (crypto.SymKey, error) { decrypted, err := st.key.Decrypt(msg) if err != nil { return nil, ErrFailedToDecrypt } key, err := crypto.UnmarshallAESKey(decrypted) if err != nil { return nil, ErrFailedToDecrypt } return key, nil } func (st *AclState) Permissions(identity crypto.PubKey) AclPermissions { state, exists := st.userStates[mapKeyFromPubKey(identity)] if !exists { return AclPermissions(aclrecordproto.AclUserPermissions_None) } return state.Permissions } func (st *AclState) UserStates() map[string]AclUserState { return st.userStates } func (st *AclState) LastRecordId() string { return st.lastRecordId } func (st *AclState) deriveKey() (crypto.SymKey, error) { keyBytes, err := st.key.Raw() if err != nil { return nil, err } return crypto.DeriveSymmetricKey(keyBytes, crypto.AnysyncSpacePath) } func mapKeyFromPubKey(pubKey crypto.PubKey) string { return string(pubKey.Storage()) }