mirror of
https://github.com/anyproto/any-sync.git
synced 2025-06-07 21:47:02 +09:00
1000 lines
28 KiB
Go
1000 lines
28 KiB
Go
package list
|
|
|
|
import (
|
|
"errors"
|
|
|
|
"github.com/anyproto/protobuf/proto"
|
|
"go.uber.org/zap"
|
|
"golang.org/x/exp/slices"
|
|
|
|
"github.com/anyproto/any-sync/app/logger"
|
|
"github.com/anyproto/any-sync/commonspace/object/acl/aclrecordproto"
|
|
"github.com/anyproto/any-sync/commonspace/object/acl/recordverifier"
|
|
"github.com/anyproto/any-sync/util/crypto"
|
|
)
|
|
|
|
var log = logger.NewNamedSugared("common.commonspace.acllist")
|
|
|
|
var (
|
|
ErrNoSuchAccount = errors.New("no such account")
|
|
ErrPendingRequest = errors.New("already exists pending request")
|
|
ErrUnexpectedContentType = errors.New("unexpected content type")
|
|
ErrIncorrectIdentity = errors.New("incorrect identity")
|
|
ErrIncorrectInviteKey = errors.New("incorrect invite key")
|
|
ErrFailedToDecrypt = errors.New("failed to decrypt key")
|
|
ErrNoMetadataKey = errors.New("no metadata key")
|
|
ErrNoSuchRecord = errors.New("no such record")
|
|
ErrNoSuchRequest = errors.New("no such request")
|
|
ErrNoSuchInvite = errors.New("no such invite")
|
|
ErrInsufficientPermissions = errors.New("insufficient permissions")
|
|
ErrIsOwner = errors.New("can't be made by owner")
|
|
ErrIncorrectNumberOfAccounts = errors.New("incorrect number of accounts")
|
|
ErrDuplicateAccounts = errors.New("duplicate accounts")
|
|
ErrNoReadKey = errors.New("no 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")
|
|
ErrMetadataTooLarge = errors.New("metadata size too large")
|
|
ErrOwnerNotFound = errors.New("owner not found")
|
|
)
|
|
|
|
const MaxMetadataLen = 1024
|
|
|
|
type UserPermissionPair struct {
|
|
Identity crypto.PubKey
|
|
Permission aclrecordproto.AclUserPermissions
|
|
}
|
|
|
|
type AclKeys struct {
|
|
ReadKey crypto.SymKey
|
|
MetadataPrivKey crypto.PrivKey
|
|
MetadataPubKey crypto.PubKey
|
|
|
|
oldEncryptedReadKey []byte
|
|
encMetadatKey []byte
|
|
}
|
|
|
|
type Invite struct {
|
|
Key crypto.PubKey
|
|
Type aclrecordproto.AclInviteType
|
|
Permissions AclPermissions
|
|
encryptedKey []byte
|
|
}
|
|
|
|
type AclState struct {
|
|
id string
|
|
// keys represent current keys of the acl
|
|
keys map[string]AclKeys
|
|
// accountStates is a map pubKey -> state which defines current account state
|
|
accountStates map[string]AccountState
|
|
// inviteKeys is a map recordId -> invite
|
|
invites map[string]Invite
|
|
// requestRecords is a map recordId -> RequestRecord
|
|
requestRecords map[string]RequestRecord
|
|
// pendingRequests is a map pubKey -> recordId
|
|
pendingRequests map[string]string
|
|
// readKeyChanges is a list of records containing read key changes
|
|
readKeyChanges []string
|
|
key crypto.PrivKey
|
|
pubKey crypto.PubKey
|
|
keyStore crypto.KeyStorage
|
|
|
|
lastRecordId string
|
|
contentValidator ContentValidator
|
|
list *aclList
|
|
}
|
|
|
|
func newAclStateWithKeys(
|
|
rootRecord *AclRecord,
|
|
key crypto.PrivKey,
|
|
verifier recordverifier.AcceptorVerifier) (st *AclState, err error) {
|
|
st = &AclState{
|
|
id: rootRecord.Id,
|
|
key: key,
|
|
pubKey: key.GetPublic(),
|
|
keys: make(map[string]AclKeys),
|
|
accountStates: make(map[string]AccountState),
|
|
invites: make(map[string]Invite),
|
|
requestRecords: make(map[string]RequestRecord),
|
|
pendingRequests: make(map[string]string),
|
|
keyStore: crypto.NewKeyStorage(),
|
|
}
|
|
st.contentValidator = newContentValidator(st.keyStore, st, verifier)
|
|
err = st.applyRoot(rootRecord)
|
|
if err != nil {
|
|
return
|
|
}
|
|
return st, nil
|
|
}
|
|
|
|
func newAclState(rootRecord *AclRecord, verifier recordverifier.AcceptorVerifier) (st *AclState, err error) {
|
|
st = &AclState{
|
|
id: rootRecord.Id,
|
|
keys: make(map[string]AclKeys),
|
|
accountStates: make(map[string]AccountState),
|
|
invites: make(map[string]Invite),
|
|
requestRecords: make(map[string]RequestRecord),
|
|
pendingRequests: make(map[string]string),
|
|
keyStore: crypto.NewKeyStorage(),
|
|
}
|
|
st.contentValidator = newContentValidator(st.keyStore, st, verifier)
|
|
err = st.applyRoot(rootRecord)
|
|
if err != nil {
|
|
return
|
|
}
|
|
return st, nil
|
|
}
|
|
|
|
func (st *AclState) Identity() crypto.PubKey {
|
|
return st.pubKey
|
|
}
|
|
|
|
func (st *AclState) Validator() ContentValidator {
|
|
return st.contentValidator
|
|
}
|
|
|
|
func (st *AclState) CurrentReadKeyId() string {
|
|
return st.readKeyChanges[len(st.readKeyChanges)-1]
|
|
}
|
|
|
|
func (st *AclState) ReadKeyForAclId(id string) (string, error) {
|
|
recIdx, ok := st.list.indexes[id]
|
|
if !ok {
|
|
return "", ErrNoSuchRecord
|
|
}
|
|
for i := len(st.readKeyChanges) - 1; i >= 0; i-- {
|
|
recId := st.readKeyChanges[i]
|
|
if recIdx >= st.list.indexes[recId] {
|
|
return recId, nil
|
|
}
|
|
}
|
|
return "", ErrNoSuchRecord
|
|
}
|
|
|
|
func (st *AclState) AccountKey() crypto.PrivKey {
|
|
return st.key
|
|
}
|
|
|
|
func (st *AclState) CurrentReadKey() (crypto.SymKey, error) {
|
|
curKeys, exists := st.keys[st.CurrentReadKeyId()]
|
|
if !exists {
|
|
return nil, ErrNoReadKey
|
|
}
|
|
return curKeys.ReadKey, nil
|
|
}
|
|
|
|
func (st *AclState) CurrentMetadataKey() (crypto.PubKey, error) {
|
|
curKeys, exists := st.keys[st.CurrentReadKeyId()]
|
|
if !exists {
|
|
return nil, ErrNoMetadataKey
|
|
}
|
|
return curKeys.MetadataPubKey, nil
|
|
}
|
|
|
|
func (st *AclState) FirstMetadataKey() (crypto.PrivKey, error) {
|
|
if firstKey, ok := st.keys[st.id]; ok && firstKey.MetadataPrivKey != nil {
|
|
return firstKey.MetadataPrivKey, nil
|
|
}
|
|
return nil, ErrNoMetadataKey
|
|
}
|
|
|
|
func (st *AclState) Keys() map[string]AclKeys {
|
|
return st.keys
|
|
}
|
|
|
|
func (st *AclState) PermissionsAtRecord(id string, pubKey crypto.PubKey) (AclPermissions, error) {
|
|
if !st.list.HasHead(id) {
|
|
return AclPermissionsNone, ErrNoSuchRecord
|
|
}
|
|
accountState, ok := st.accountStates[mapKeyFromPubKey(pubKey)]
|
|
if !ok {
|
|
return AclPermissionsNone, ErrNoSuchAccount
|
|
}
|
|
perms := closestPermissions(accountState, id, st.list.isAfterNoCheck)
|
|
return perms, nil
|
|
}
|
|
|
|
func (st *AclState) CurrentAccounts() []AccountState {
|
|
var res []AccountState
|
|
for _, state := range st.accountStates {
|
|
res = append(res, state)
|
|
}
|
|
return res
|
|
}
|
|
|
|
func (st *AclState) HadReadPermissions(identity crypto.PubKey) (had bool) {
|
|
state, exists := st.accountStates[mapKeyFromPubKey(identity)]
|
|
if !exists {
|
|
return false
|
|
}
|
|
for _, perm := range state.PermissionChanges {
|
|
if !perm.Permission.NoPermissions() {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (st *AclState) Invites(inviteType ...aclrecordproto.AclInviteType) []Invite {
|
|
var invites []Invite
|
|
for _, inv := range st.invites {
|
|
if len(inviteType) > 0 && !slices.Contains(inviteType, inv.Type) {
|
|
continue
|
|
}
|
|
invites = append(invites, inv)
|
|
}
|
|
return invites
|
|
}
|
|
|
|
func (st *AclState) Key() crypto.PrivKey {
|
|
return st.key
|
|
}
|
|
|
|
func (st *AclState) InviteIds() []string {
|
|
var invites []string
|
|
for invId := range st.invites {
|
|
invites = append(invites, invId)
|
|
}
|
|
return invites
|
|
}
|
|
|
|
func (st *AclState) RequestIds() []string {
|
|
var requests []string
|
|
for reqId := range st.requestRecords {
|
|
requests = append(requests, reqId)
|
|
}
|
|
return requests
|
|
}
|
|
|
|
func (st *AclState) DecryptInvite(invitePk crypto.PrivKey) (key crypto.SymKey, err error) {
|
|
if invitePk == nil {
|
|
return nil, ErrNoReadKey
|
|
}
|
|
for _, invite := range st.invites {
|
|
if invite.Key.Equals(invitePk.GetPublic()) {
|
|
res, err := st.unmarshallDecryptReadKey(invite.encryptedKey, invitePk.Decrypt)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return res, nil
|
|
}
|
|
}
|
|
return nil, ErrNoSuchInvite
|
|
}
|
|
|
|
func (st *AclState) ApplyRecord(record *AclRecord) (err error) {
|
|
if st.lastRecordId != record.PrevId {
|
|
err = ErrIncorrectRecordSequence
|
|
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 []AccountState
|
|
for _, state := range st.accountStates {
|
|
states = append(states, state)
|
|
}
|
|
st.lastRecordId = record.Id
|
|
return
|
|
}
|
|
|
|
func (st *AclState) IsEmpty() bool {
|
|
users := 0
|
|
for _, acc := range st.CurrentAccounts() {
|
|
if !acc.Permissions.NoPermissions() {
|
|
users++
|
|
}
|
|
}
|
|
return users == 1 && len(st.Invites()) == 0 && len(st.pendingRequests) == 0
|
|
}
|
|
|
|
func (st *AclState) applyRoot(record *AclRecord) (err error) {
|
|
root, ok := record.Model.(*aclrecordproto.AclRoot)
|
|
if !ok {
|
|
return ErrIncorrectRoot
|
|
}
|
|
if root.EncryptedReadKey != nil {
|
|
mkPubKey, err := st.keyStore.PubKeyFromProto(root.MetadataPubKey)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
st.keys[record.Id] = AclKeys{
|
|
MetadataPubKey: mkPubKey,
|
|
encMetadatKey: root.EncryptedMetadataPrivKey,
|
|
}
|
|
} else {
|
|
// this should be a derived acl
|
|
st.keys[record.Id] = AclKeys{}
|
|
}
|
|
if st.key != nil && st.pubKey.Equals(record.Identity) {
|
|
err = st.saveKeysFromRoot(record.Id, root)
|
|
if err != nil {
|
|
return
|
|
}
|
|
}
|
|
// adding an account to the list
|
|
accountState := AccountState{
|
|
PubKey: record.Identity,
|
|
Permissions: AclPermissions(aclrecordproto.AclUserPermissions_Owner),
|
|
KeyRecordId: record.Id,
|
|
RequestMetadata: root.EncryptedOwnerMetadata,
|
|
Status: StatusActive,
|
|
PermissionChanges: []PermissionChange{
|
|
{
|
|
RecordId: record.Id,
|
|
Permission: AclPermissionsOwner,
|
|
},
|
|
},
|
|
}
|
|
st.readKeyChanges = []string{record.Id}
|
|
st.accountStates[mapKeyFromPubKey(record.Identity)] = accountState
|
|
st.lastRecordId = record.Id
|
|
return
|
|
}
|
|
|
|
func (st *AclState) saveKeysFromRoot(id string, root *aclrecordproto.AclRoot) (err error) {
|
|
aclKeys := st.keys[id]
|
|
if root.EncryptedReadKey == nil {
|
|
readKey, err := st.deriveKey()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
aclKeys.ReadKey = readKey
|
|
} else {
|
|
readKey, err := st.unmarshallDecryptReadKey(root.EncryptedReadKey, st.key.Decrypt)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
metadataKey, err := st.unmarshallDecryptPrivKey(root.EncryptedMetadataPrivKey, readKey.Decrypt)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
aclKeys.ReadKey = readKey
|
|
aclKeys.MetadataPrivKey = metadataKey
|
|
}
|
|
st.keys[id] = aclKeys
|
|
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); err != nil {
|
|
log.Info("error while applying changes", zap.Error(err))
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (st *AclState) Copy() *AclState {
|
|
newSt := &AclState{
|
|
id: st.id,
|
|
key: st.key,
|
|
pubKey: st.key.GetPublic(),
|
|
keys: make(map[string]AclKeys),
|
|
accountStates: make(map[string]AccountState),
|
|
invites: make(map[string]Invite),
|
|
requestRecords: make(map[string]RequestRecord),
|
|
pendingRequests: make(map[string]string),
|
|
keyStore: st.keyStore,
|
|
}
|
|
for k, v := range st.keys {
|
|
newSt.keys[k] = v
|
|
}
|
|
for k, v := range st.accountStates {
|
|
var permChanges []PermissionChange
|
|
permChanges = append(permChanges, v.PermissionChanges...)
|
|
accState := v
|
|
accState.PermissionChanges = permChanges
|
|
newSt.accountStates[k] = accState
|
|
}
|
|
for k, v := range st.invites {
|
|
newSt.invites[k] = v
|
|
}
|
|
for k, v := range st.requestRecords {
|
|
newSt.requestRecords[k] = v
|
|
}
|
|
for k, v := range st.pendingRequests {
|
|
newSt.pendingRequests[k] = v
|
|
}
|
|
newSt.readKeyChanges = append(newSt.readKeyChanges, st.readKeyChanges...)
|
|
newSt.list = st.list
|
|
newSt.lastRecordId = st.lastRecordId
|
|
newSt.contentValidator = newContentValidator(newSt.keyStore, newSt, st.list.verifier)
|
|
return newSt
|
|
}
|
|
|
|
func (st *AclState) applyChangeContent(ch *aclrecordproto.AclContentValue, record *AclRecord) error {
|
|
switch {
|
|
case ch.GetInviteJoin() != nil:
|
|
return st.applyInviteJoin(ch.GetInviteJoin(), record)
|
|
case ch.GetPermissionChange() != nil:
|
|
return st.applyPermissionChange(ch.GetPermissionChange(), record)
|
|
case ch.GetInvite() != nil:
|
|
return st.applyInvite(ch.GetInvite(), record)
|
|
case ch.GetInviteRevoke() != nil:
|
|
return st.applyInviteRevoke(ch.GetInviteRevoke(), record)
|
|
case ch.GetRequestJoin() != nil:
|
|
return st.applyRequestJoin(ch.GetRequestJoin(), record)
|
|
case ch.GetRequestAccept() != nil:
|
|
return st.applyRequestAccept(ch.GetRequestAccept(), record)
|
|
case ch.GetRequestDecline() != nil:
|
|
return st.applyRequestDecline(ch.GetRequestDecline(), record)
|
|
case ch.GetRequestCancel() != nil:
|
|
return st.applyRequestCancel(ch.GetRequestCancel(), record)
|
|
case ch.GetAccountRemove() != nil:
|
|
return st.applyAccountRemove(ch.GetAccountRemove(), record)
|
|
case ch.GetReadKeyChange() != nil:
|
|
return st.applyReadKeyChange(ch.GetReadKeyChange(), record, true)
|
|
case ch.GetAccountRequestRemove() != nil:
|
|
return st.applyRequestRemove(ch.GetAccountRequestRemove(), record)
|
|
case ch.GetAccountsAdd() != nil:
|
|
return st.applyAccountsAdd(ch.GetAccountsAdd(), record)
|
|
case ch.GetPermissionChanges() != nil:
|
|
return st.applyPermissionChanges(ch.GetPermissionChanges(), record)
|
|
default:
|
|
log.Errorf("got unexpected content type: %s", record.Id)
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func (st *AclState) applyPermissionChanges(ch *aclrecordproto.AclAccountPermissionChanges, record *AclRecord) (err error) {
|
|
for _, ch := range ch.Changes {
|
|
err := st.applyPermissionChange(ch, record)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (st *AclState) applyPermissionChange(ch *aclrecordproto.AclAccountPermissionChange, record *AclRecord) error {
|
|
chIdentity, err := st.keyStore.PubKeyFromProto(ch.Identity)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = st.contentValidator.ValidatePermissionChange(ch, record.Identity)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
stringKey := mapKeyFromPubKey(chIdentity)
|
|
state, _ := st.accountStates[stringKey]
|
|
state.Permissions = AclPermissions(ch.Permissions)
|
|
state.PermissionChanges = append(state.PermissionChanges, PermissionChange{
|
|
RecordId: record.Id,
|
|
Permission: state.Permissions,
|
|
})
|
|
st.accountStates[stringKey] = state
|
|
return nil
|
|
}
|
|
|
|
func (st *AclState) applyInvite(ch *aclrecordproto.AclAccountInvite, record *AclRecord) error {
|
|
inviteKey, err := st.keyStore.PubKeyFromProto(ch.InviteKey)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = st.contentValidator.ValidateInvite(ch, record.Identity)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
st.invites[record.Id] = Invite{
|
|
Key: inviteKey,
|
|
Type: ch.InviteType,
|
|
Permissions: AclPermissions(ch.Permissions),
|
|
encryptedKey: ch.EncryptedReadKey,
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (st *AclState) applyInviteRevoke(ch *aclrecordproto.AclAccountInviteRevoke, record *AclRecord) error {
|
|
err := st.contentValidator.ValidateInviteRevoke(ch, record.Identity)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
delete(st.invites, ch.InviteRecordId)
|
|
return nil
|
|
}
|
|
|
|
func (st *AclState) applyRequestJoin(ch *aclrecordproto.AclAccountRequestJoin, record *AclRecord) error {
|
|
err := st.contentValidator.ValidateRequestJoin(ch, record.Identity)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
st.pendingRequests[mapKeyFromPubKey(record.Identity)] = record.Id
|
|
st.requestRecords[record.Id] = RequestRecord{
|
|
RequestIdentity: record.Identity,
|
|
RequestMetadata: ch.Metadata,
|
|
KeyRecordId: st.CurrentReadKeyId(),
|
|
RecordId: record.Id,
|
|
Type: RequestTypeJoin,
|
|
}
|
|
pKeyString := mapKeyFromPubKey(record.Identity)
|
|
state, exists := st.accountStates[pKeyString]
|
|
if !exists {
|
|
st.accountStates[mapKeyFromPubKey(record.Identity)] = AccountState{
|
|
PubKey: record.Identity,
|
|
Permissions: AclPermissionsNone,
|
|
Status: StatusJoining,
|
|
RequestMetadata: ch.Metadata,
|
|
KeyRecordId: st.CurrentReadKeyId(),
|
|
}
|
|
} else {
|
|
st.accountStates[mapKeyFromPubKey(record.Identity)] = AccountState{
|
|
PubKey: record.Identity,
|
|
Permissions: AclPermissionsNone,
|
|
Status: StatusJoining,
|
|
RequestMetadata: ch.Metadata,
|
|
KeyRecordId: st.CurrentReadKeyId(),
|
|
PermissionChanges: state.PermissionChanges,
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (st *AclState) applyAccountsAdd(ch *aclrecordproto.AclAccountsAdd, record *AclRecord) error {
|
|
err := st.contentValidator.ValidateAccountsAdd(ch, record.Identity)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for _, acc := range ch.Additions {
|
|
identity, err := st.keyStore.PubKeyFromProto(acc.Identity)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
st.accountStates[mapKeyFromPubKey(identity)] = AccountState{
|
|
PubKey: identity,
|
|
Permissions: AclPermissions(acc.Permissions),
|
|
Status: StatusActive,
|
|
RequestMetadata: acc.Metadata,
|
|
KeyRecordId: st.CurrentReadKeyId(),
|
|
PermissionChanges: []PermissionChange{
|
|
{
|
|
Permission: AclPermissions(acc.Permissions),
|
|
RecordId: record.Id,
|
|
},
|
|
},
|
|
}
|
|
if !st.pubKey.Equals(identity) {
|
|
continue
|
|
}
|
|
err = st.unpackAllKeys(acc.EncryptedReadKey)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (st *AclState) applyRequestAccept(ch *aclrecordproto.AclAccountRequestAccept, record *AclRecord) error {
|
|
err := st.contentValidator.ValidateRequestAccept(ch, record.Identity)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
acceptIdentity, err := st.keyStore.PubKeyFromProto(ch.Identity)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
requestRecord, _ := st.requestRecords[ch.RequestRecordId]
|
|
pKeyString := mapKeyFromPubKey(acceptIdentity)
|
|
state, exists := st.accountStates[pKeyString]
|
|
if !exists {
|
|
st.accountStates[pKeyString] = AccountState{
|
|
PubKey: acceptIdentity,
|
|
Permissions: AclPermissions(ch.Permissions),
|
|
RequestMetadata: requestRecord.RequestMetadata,
|
|
KeyRecordId: requestRecord.KeyRecordId,
|
|
Status: StatusActive,
|
|
PermissionChanges: []PermissionChange{
|
|
{
|
|
Permission: AclPermissions(ch.Permissions),
|
|
RecordId: record.Id,
|
|
},
|
|
},
|
|
}
|
|
} else {
|
|
st.accountStates[pKeyString] = AccountState{
|
|
PubKey: acceptIdentity,
|
|
Permissions: AclPermissions(ch.Permissions),
|
|
RequestMetadata: requestRecord.RequestMetadata,
|
|
KeyRecordId: requestRecord.KeyRecordId,
|
|
Status: StatusActive,
|
|
PermissionChanges: append(state.PermissionChanges, PermissionChange{
|
|
Permission: AclPermissions(ch.Permissions),
|
|
RecordId: record.Id,
|
|
}),
|
|
}
|
|
}
|
|
delete(st.pendingRequests, mapKeyFromPubKey(st.requestRecords[ch.RequestRecordId].RequestIdentity))
|
|
delete(st.requestRecords, ch.RequestRecordId)
|
|
if !st.pubKey.Equals(acceptIdentity) {
|
|
return nil
|
|
}
|
|
return st.unpackAllKeys(ch.EncryptedReadKey)
|
|
}
|
|
|
|
func (st *AclState) applyInviteJoin(ch *aclrecordproto.AclAccountInviteJoin, record *AclRecord) error {
|
|
err := st.contentValidator.ValidateInviteJoin(ch, record.Identity)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
identity, err := st.keyStore.PubKeyFromProto(ch.Identity)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
inviteRecord, _ := st.invites[ch.InviteRecordId]
|
|
pKeyString := mapKeyFromPubKey(identity)
|
|
state, exists := st.accountStates[pKeyString]
|
|
if !exists {
|
|
st.accountStates[pKeyString] = AccountState{
|
|
PubKey: identity,
|
|
Permissions: inviteRecord.Permissions,
|
|
RequestMetadata: ch.Metadata,
|
|
KeyRecordId: st.CurrentReadKeyId(),
|
|
Status: StatusActive,
|
|
PermissionChanges: []PermissionChange{
|
|
{
|
|
Permission: inviteRecord.Permissions,
|
|
RecordId: record.Id,
|
|
},
|
|
},
|
|
}
|
|
} else {
|
|
st.accountStates[pKeyString] = AccountState{
|
|
PubKey: identity,
|
|
Permissions: inviteRecord.Permissions,
|
|
RequestMetadata: ch.Metadata,
|
|
KeyRecordId: st.CurrentReadKeyId(),
|
|
Status: StatusActive,
|
|
PermissionChanges: append(state.PermissionChanges, PermissionChange{
|
|
Permission: inviteRecord.Permissions,
|
|
RecordId: record.Id,
|
|
}),
|
|
}
|
|
}
|
|
if st.pubKey.Equals(identity) {
|
|
return st.unpackAllKeys(ch.EncryptedReadKey)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (st *AclState) unpackAllKeys(rk []byte) error {
|
|
iterReadKey, err := st.unmarshallDecryptReadKey(rk, st.key.Decrypt)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for idx := len(st.readKeyChanges) - 1; idx >= 0; idx-- {
|
|
recId := st.readKeyChanges[idx]
|
|
keys := st.keys[recId]
|
|
metadataKey, err := st.unmarshallDecryptPrivKey(keys.encMetadatKey, iterReadKey.Decrypt)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
aclKeys := st.keys[recId]
|
|
aclKeys.ReadKey = iterReadKey
|
|
aclKeys.MetadataPrivKey = metadataKey
|
|
st.keys[recId] = aclKeys
|
|
if idx != 0 {
|
|
if keys.oldEncryptedReadKey == nil {
|
|
return ErrIncorrectReadKey
|
|
}
|
|
oldReadKey, err := st.unmarshallDecryptReadKey(keys.oldEncryptedReadKey, iterReadKey.Decrypt)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
iterReadKey = oldReadKey
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (st *AclState) applyRequestDecline(ch *aclrecordproto.AclAccountRequestDecline, record *AclRecord) error {
|
|
err := st.contentValidator.ValidateRequestDecline(ch, record.Identity)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
pk := mapKeyFromPubKey(st.requestRecords[ch.RequestRecordId].RequestIdentity)
|
|
accSt, exists := st.accountStates[pk]
|
|
if !exists {
|
|
return ErrNoSuchAccount
|
|
}
|
|
accSt.Status = StatusDeclined
|
|
st.accountStates[pk] = accSt
|
|
delete(st.pendingRequests, mapKeyFromPubKey(st.requestRecords[ch.RequestRecordId].RequestIdentity))
|
|
delete(st.requestRecords, ch.RequestRecordId)
|
|
return nil
|
|
}
|
|
|
|
func (st *AclState) applyRequestCancel(ch *aclrecordproto.AclAccountRequestCancel, record *AclRecord) error {
|
|
err := st.contentValidator.ValidateRequestCancel(ch, record.Identity)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
pk := mapKeyFromPubKey(st.requestRecords[ch.RecordId].RequestIdentity)
|
|
accSt, exists := st.accountStates[pk]
|
|
if !exists {
|
|
return ErrNoSuchAccount
|
|
}
|
|
rec := st.requestRecords[ch.RecordId]
|
|
if rec.Type == RequestTypeJoin {
|
|
accSt.Status = StatusCanceled
|
|
} else {
|
|
accSt.Status = StatusActive
|
|
}
|
|
st.accountStates[pk] = accSt
|
|
delete(st.pendingRequests, mapKeyFromPubKey(st.requestRecords[ch.RecordId].RequestIdentity))
|
|
delete(st.requestRecords, ch.RecordId)
|
|
return nil
|
|
}
|
|
|
|
func (st *AclState) applyRequestRemove(ch *aclrecordproto.AclAccountRequestRemove, record *AclRecord) error {
|
|
err := st.contentValidator.ValidateRequestRemove(ch, record.Identity)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
st.requestRecords[record.Id] = RequestRecord{
|
|
RequestIdentity: record.Identity,
|
|
Type: RequestTypeRemove,
|
|
RecordId: record.Id,
|
|
}
|
|
st.pendingRequests[mapKeyFromPubKey(record.Identity)] = record.Id
|
|
pk := mapKeyFromPubKey(record.Identity)
|
|
accSt, exists := st.accountStates[pk]
|
|
if !accSt.Permissions.CanRequestRemove() {
|
|
return ErrInsufficientPermissions
|
|
}
|
|
if !exists {
|
|
return ErrNoSuchAccount
|
|
}
|
|
accSt.Status = StatusRemoving
|
|
st.accountStates[pk] = accSt
|
|
return nil
|
|
}
|
|
|
|
func (st *AclState) applyAccountRemove(ch *aclrecordproto.AclAccountRemove, record *AclRecord) error {
|
|
err := st.contentValidator.ValidateAccountRemove(ch, record.Identity)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for _, rawIdentity := range ch.Identities {
|
|
identity, err := st.keyStore.PubKeyFromProto(rawIdentity)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
idKey := mapKeyFromPubKey(identity)
|
|
accSt, exists := st.accountStates[idKey]
|
|
if !exists {
|
|
return ErrNoSuchAccount
|
|
}
|
|
accSt.Status = StatusRemoved
|
|
accSt.Permissions = AclPermissionsNone
|
|
accSt.PermissionChanges = append(accSt.PermissionChanges, PermissionChange{
|
|
RecordId: record.Id,
|
|
Permission: AclPermissionsNone,
|
|
})
|
|
st.accountStates[idKey] = accSt
|
|
recId, exists := st.pendingRequests[idKey]
|
|
if exists {
|
|
delete(st.pendingRequests, idKey)
|
|
delete(st.requestRecords, recId)
|
|
}
|
|
}
|
|
return st.applyReadKeyChange(ch.ReadKeyChange, record, false)
|
|
}
|
|
|
|
func (st *AclState) applyReadKeyChange(ch *aclrecordproto.AclReadKeyChange, record *AclRecord, validate bool) error {
|
|
if validate {
|
|
err := st.contentValidator.ValidateReadKeyChange(ch, record.Identity)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
st.readKeyChanges = append(st.readKeyChanges, record.Id)
|
|
mkPubKey, err := st.keyStore.PubKeyFromProto(ch.MetadataPubKey)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
aclKeys := AclKeys{
|
|
MetadataPubKey: mkPubKey,
|
|
oldEncryptedReadKey: ch.EncryptedOldReadKey,
|
|
encMetadatKey: ch.EncryptedMetadataPrivKey,
|
|
}
|
|
for _, accKey := range ch.AccountKeys {
|
|
identity, _ := st.keyStore.PubKeyFromProto(accKey.Identity)
|
|
if st.pubKey.Equals(identity) {
|
|
res, err := st.unmarshallDecryptReadKey(accKey.EncryptedReadKey, st.key.Decrypt)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
aclKeys.ReadKey = res
|
|
}
|
|
}
|
|
for _, encKey := range ch.InviteKeys {
|
|
invKey, err := st.keyStore.PubKeyFromProto(encKey.Identity)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for key, invite := range st.invites {
|
|
if invite.Key.Equals(invKey) {
|
|
invite.encryptedKey = encKey.EncryptedReadKey
|
|
st.invites[key] = invite
|
|
break
|
|
}
|
|
}
|
|
}
|
|
if aclKeys.ReadKey != nil {
|
|
metadataKey, err := st.unmarshallDecryptPrivKey(ch.EncryptedMetadataPrivKey, aclKeys.ReadKey.Decrypt)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
aclKeys.MetadataPrivKey = metadataKey
|
|
}
|
|
st.keys[record.Id] = aclKeys
|
|
return nil
|
|
}
|
|
|
|
func (st *AclState) unmarshallDecryptReadKey(msg []byte, decryptor func(msg []byte) ([]byte, error)) (crypto.SymKey, error) {
|
|
decrypted, err := decryptor(msg)
|
|
if err != nil {
|
|
return nil, ErrFailedToDecrypt
|
|
}
|
|
key, err := crypto.UnmarshallAESKeyProto(decrypted)
|
|
if err != nil {
|
|
return nil, ErrFailedToDecrypt
|
|
}
|
|
return key, nil
|
|
}
|
|
|
|
func (st *AclState) unmarshallDecryptPrivKey(msg []byte, decryptor func(msg []byte) ([]byte, error)) (crypto.PrivKey, error) {
|
|
decrypted, err := decryptor(msg)
|
|
if err != nil {
|
|
return nil, ErrFailedToDecrypt
|
|
}
|
|
key, err := crypto.UnmarshalEd25519PrivateKeyProto(decrypted)
|
|
if err != nil {
|
|
return nil, ErrFailedToDecrypt
|
|
}
|
|
return key, nil
|
|
}
|
|
|
|
func (st *AclState) GetInviteIdByPrivKey(inviteKey crypto.PrivKey) (recId string, err error) {
|
|
for id, inv := range st.invites {
|
|
if inv.Key.Equals(inviteKey.GetPublic()) {
|
|
return id, nil
|
|
}
|
|
}
|
|
return "", ErrNoSuchRecord
|
|
}
|
|
|
|
func (st *AclState) GetMetadata(identity crypto.PubKey, decrypt bool) (res []byte, err error) {
|
|
state, exists := st.accountStates[mapKeyFromPubKey(identity)]
|
|
if !exists {
|
|
return nil, ErrNoSuchAccount
|
|
}
|
|
if !decrypt {
|
|
return state.RequestMetadata, nil
|
|
}
|
|
aclKeys := st.keys[state.KeyRecordId]
|
|
if aclKeys.MetadataPrivKey == nil {
|
|
return nil, ErrFailedToDecrypt
|
|
}
|
|
return aclKeys.MetadataPrivKey.Decrypt(state.RequestMetadata)
|
|
}
|
|
|
|
func (st *AclState) Permissions(identity crypto.PubKey) AclPermissions {
|
|
state, exists := st.accountStates[mapKeyFromPubKey(identity)]
|
|
if !exists {
|
|
return AclPermissions(aclrecordproto.AclUserPermissions_None)
|
|
}
|
|
return state.Permissions
|
|
}
|
|
|
|
func (st *AclState) JoinRecords(decrypt bool) (records []RequestRecord, err error) {
|
|
for _, recId := range st.pendingRequests {
|
|
rec := st.requestRecords[recId]
|
|
if rec.Type == RequestTypeJoin {
|
|
if decrypt {
|
|
aclKeys := st.keys[rec.KeyRecordId]
|
|
if aclKeys.MetadataPrivKey == nil {
|
|
return nil, ErrFailedToDecrypt
|
|
}
|
|
res, err := aclKeys.MetadataPrivKey.Decrypt(rec.RequestMetadata)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
rec.RequestMetadata = res
|
|
}
|
|
records = append(records, rec)
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
func (st *AclState) Record(identity crypto.PubKey) (RequestRecord, error) {
|
|
recId, exists := st.pendingRequests[mapKeyFromPubKey(identity)]
|
|
if !exists {
|
|
return RequestRecord{}, ErrNoSuchRecord
|
|
}
|
|
return st.requestRecords[recId], nil
|
|
}
|
|
|
|
func (st *AclState) JoinRecord(identity crypto.PubKey, decrypt bool) (RequestRecord, error) {
|
|
recId, exists := st.pendingRequests[mapKeyFromPubKey(identity)]
|
|
if !exists {
|
|
return RequestRecord{}, ErrNoSuchRecord
|
|
}
|
|
rec := st.requestRecords[recId]
|
|
if decrypt {
|
|
aclKeys := st.keys[rec.KeyRecordId]
|
|
if aclKeys.MetadataPrivKey == nil {
|
|
return RequestRecord{}, ErrFailedToDecrypt
|
|
}
|
|
res, err := aclKeys.MetadataPrivKey.Decrypt(rec.RequestMetadata)
|
|
if err != nil {
|
|
return RequestRecord{}, err
|
|
}
|
|
rec.RequestMetadata = res
|
|
}
|
|
return rec, nil
|
|
}
|
|
|
|
func (st *AclState) RemoveRecords() (records []RequestRecord) {
|
|
for _, recId := range st.pendingRequests {
|
|
rec := st.requestRecords[recId]
|
|
if rec.Type == RequestTypeRemove {
|
|
records = append(records, rec)
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
func (st *AclState) LastRecordId() string {
|
|
return st.lastRecordId
|
|
}
|
|
|
|
func (st *AclState) OwnerPubKey() (ownerIdentity crypto.PubKey, err error) {
|
|
for _, aState := range st.accountStates {
|
|
if aState.Permissions.IsOwner() {
|
|
return aState.PubKey, nil
|
|
}
|
|
}
|
|
return nil, ErrOwnerNotFound
|
|
}
|
|
|
|
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())
|
|
}
|
|
|
|
func closestPermissions(accountState AccountState, recordId string, isAfter func(first, second string) bool) AclPermissions {
|
|
if len(accountState.PermissionChanges) == 0 {
|
|
return AclPermissionsNone
|
|
}
|
|
permChanges := accountState.PermissionChanges
|
|
for i := len(permChanges) - 1; i >= 0; i-- {
|
|
if isAfter(recordId, permChanges[i].RecordId) {
|
|
return permChanges[i].Permission
|
|
}
|
|
}
|
|
return AclPermissionsNone
|
|
}
|