diff --git a/data/acltreebuilder.go b/data/acltreebuilder.go index 99289d41..1308ec43 100644 --- a/data/acltreebuilder.go +++ b/data/acltreebuilder.go @@ -36,7 +36,7 @@ func (tb *ACLTreeBuilder) Init() { } func (tb *ACLTreeBuilder) Build() (*Tree, error) { - heads := tb.thread.Heads() + heads := tb.thread.MaybeHeads() aclHeads, err := tb.getACLHeads(heads) if err != nil { return nil, err diff --git a/data/document.go b/data/document.go index 45eaa6a4..4fbabcac 100644 --- a/data/document.go +++ b/data/document.go @@ -64,6 +64,8 @@ func (d *Document) Update(changes ...*threadmodels.RawChange) (DocumentState, Up treeChange := d.treeBuilder.changeCreator(ch.Id, aclChange) treeChanges = append(treeChanges, treeChange) + // this already sets MaybeHeads to include new changes + // TODO: change this behaviour as non-obvious, because it is not evident from the interface err = d.thread.AddChange(ch) if err != nil { return nil, UpdateResultNoAction, fmt.Errorf("change with id %s cannot be added: %w", ch.Id, err) @@ -99,6 +101,11 @@ func (d *Document) Update(changes ...*threadmodels.RawChange) (DocumentState, Up return res, UpdateResultRebuild, fmt.Errorf("could not add changes to state, rebuilded") } + // setting all heads + d.thread.SetHeads(d.docContext.fullTree.Heads()) + // this should be the entrypoint when we build the document + d.thread.SetMaybeHeads(d.docContext.fullTree.Heads()) + return newState, UpdateResultAppend, nil } @@ -151,6 +158,11 @@ func (d *Document) build(fromStart bool) (DocumentState, error) { return nil, err } + // setting all heads + d.thread.SetHeads(d.docContext.fullTree.Heads()) + // this should be the entrypoint when we build the document + d.thread.SetMaybeHeads(d.docContext.fullTree.Heads()) + return d.docContext.docState, nil } diff --git a/data/threadbuilder/invalidsnapshotexample.yml b/data/threadbuilder/invalidsnapshotexample.yml index eb49fa95..fff71365 100644 --- a/data/threadbuilder/invalidsnapshotexample.yml +++ b/data/threadbuilder/invalidsnapshotexample.yml @@ -117,6 +117,6 @@ graph: baseSnapshot: A.1.2 aclHeads: [A.1.2] treeHeads: [A.1.2] -heads: +maybeHeads: - A.1.3 - B.1.2 diff --git a/data/threadbuilder/keychain.go b/data/threadbuilder/keychain.go index 69051c79..be3119ab 100644 --- a/data/threadbuilder/keychain.go +++ b/data/threadbuilder/keychain.go @@ -14,11 +14,13 @@ type SymKey struct { } type Keychain struct { - SigningKeys map[string]threadmodels.SigningPrivKey - EncryptionKeys map[string]threadmodels.EncryptionPrivKey - ReadKeys map[string]*SymKey - GeneratedIdentities map[string]string - coder *threadmodels.Ed25519SigningPubKeyDecoder + SigningKeys map[string]threadmodels.SigningPrivKey + SigningKeysByIdentity map[string]threadmodels.SigningPrivKey + EncryptionKeys map[string]threadmodels.EncryptionPrivKey + ReadKeys map[string]*SymKey + ReadKeysByHash map[uint64]*SymKey + GeneratedIdentities map[string]string + coder *threadmodels.Ed25519SigningPubKeyDecoder } func NewKeychain() *Keychain { @@ -71,6 +73,7 @@ func (k *Keychain) AddSigningKey(name string) { if err != nil { panic(err) } + k.SigningKeysByIdentity[res] = newPrivKey k.GeneratedIdentities[name] = res } @@ -87,6 +90,10 @@ func (k *Keychain) AddReadKey(name string) { Hash: hasher.Sum64(), Key: key, } + k.ReadKeysByHash[hasher.Sum64()] = &SymKey{ + Hash: hasher.Sum64(), + Key: key, + } } func (k *Keychain) AddKey(key string) { diff --git a/data/threadbuilder/threadbuilder.go b/data/threadbuilder/threadbuilder.go index 92424ab1..c1de52b5 100644 --- a/data/threadbuilder/threadbuilder.go +++ b/data/threadbuilder/threadbuilder.go @@ -20,36 +20,17 @@ type threadChange struct { readKey *SymKey signKey threadmodels.SigningPrivKey - changesData *pb.PlainTextChangeData + changesDataDecrypted []byte } type ThreadBuilder struct { threadId string allChanges map[string]*threadChange heads []string + maybeHeads []string keychain *Keychain } -func (t *ThreadBuilder) AddChange(change *threadmodels.RawChange) error { - //TODO implement me - panic("implement me") - return nil -} - -func (t *ThreadBuilder) MaybeHeads() []string { - //TODO implement me - panic("implement me") -} - -func (t *ThreadBuilder) SetMaybeHeads(heads []string) { - //TODO implement me - panic("implement me") -} - -func (t *ThreadBuilder) SetHeads(heads []string) { - //TODO implement me -} - func NewThreadBuilder(keychain *Keychain) *ThreadBuilder { return &ThreadBuilder{ allChanges: make(map[string]*threadChange), @@ -92,17 +73,58 @@ func (t *ThreadBuilder) Heads() []string { return t.heads } +func (t *ThreadBuilder) AddChange(change *threadmodels.RawChange) error { + aclChange := new(pb.ACLChange) + var err error + + // TODO: think what should we do with such cases, because this can be used by attacker to break our tree + if err = proto.Unmarshal(change.Payload, aclChange); err != nil { + return fmt.Errorf("could not unmarshall changes") + } + var changesData []byte + + // get correct readkey + readKey := t.keychain.ReadKeysByHash[aclChange.CurrentReadKeyHash] + if aclChange.ChangesData != nil { + changesData, err = readKey.Key.Decrypt(aclChange.ChangesData) + if err != nil { + return fmt.Errorf("failed to decrypt changes data: %w", err) + } + } + + // get correct signing key + signKey := t.keychain.SigningKeysByIdentity[aclChange.Identity] + t.maybeHeads = append(t.maybeHeads, change.Id) + + t.allChanges[change.Id] = &threadChange{ + ACLChange: aclChange, + id: change.Id, + readKey: readKey, + signKey: signKey, + changesDataDecrypted: changesData, + } + return nil +} + +func (t *ThreadBuilder) MaybeHeads() []string { + return t.maybeHeads +} + +func (t *ThreadBuilder) SetMaybeHeads(heads []string) { + // we should copy here instead of just setting the value + t.maybeHeads = heads +} + +func (t *ThreadBuilder) SetHeads(heads []string) { + // we should copy here instead of just setting the value + t.heads = heads +} + func (t *ThreadBuilder) GetChange(ctx context.Context, recordID string) (*threadmodels.RawChange, error) { rec := t.allChanges[recordID] - var encrypted []byte - if rec.changesData != nil { - m, err := proto.Marshal(rec.changesData) - if err != nil { - panic("should be able to marshal data!") - } - - encrypted, err = rec.readKey.Key.Encrypt(m) + if rec.changesDataDecrypted != nil { + encrypted, err := rec.readKey.Key.Encrypt(rec.changesDataDecrypted) if err != nil { panic("should be able to encrypt data with read key!") } @@ -162,9 +184,9 @@ func (t *ThreadBuilder) Parse(thread *YMLThread) { } } if len(ch.Changes) > 0 || ch.Snapshot != nil { - newChange.changesData = &pb.PlainTextChangeData{} + changesData := &pb.PlainTextChangeData{} if ch.Snapshot != nil { - newChange.changesData.Snapshot = t.parseChangeSnapshot(ch.Snapshot) + changesData.Snapshot = t.parseChangeSnapshot(ch.Snapshot) } if len(ch.Changes) > 0 { var changeContents []*pb.PlainTextChangeContent @@ -172,8 +194,13 @@ func (t *ThreadBuilder) Parse(thread *YMLThread) { aclChangeContent := t.parseDocumentChange(ch) changeContents = append(changeContents, aclChangeContent) } - newChange.changesData.Content = changeContents + changesData.Content = changeContents } + m, err := proto.Marshal(changesData) + if err != nil { + return + } + newChange.changesDataDecrypted = m } aclChange.CurrentReadKeyHash = k.Hash newChange.ACLChange = aclChange @@ -421,4 +448,5 @@ func (t *ThreadBuilder) parseGraph(thread *YMLThread) { func (t *ThreadBuilder) parseHeads(thread *YMLThread) { t.heads = thread.Heads + t.maybeHeads = thread.MaybeHeads } diff --git a/data/threadbuilder/threadbuildergraph_nix.go b/data/threadbuilder/threadbuildergraph_nix.go index 1ea60a5a..d3d188b7 100644 --- a/data/threadbuilder/threadbuildergraph_nix.go +++ b/data/threadbuilder/threadbuildergraph_nix.go @@ -36,13 +36,13 @@ func (t *ThreadBuilder) Graph() (string, error) { style := "solid" if r.GetAclData() != nil { style = "filled" - } else if r.changesData != nil { + } else if r.changesDataDecrypted != nil { style = "dashed" } var chSymbs []string - if r.changesData != nil { - for _, chc := range r.changesData.Content { + if r.changesDataDecrypted != nil { + for _, chc := range r.changesDataDecrypted.Content { tp := fmt.Sprintf("%T", chc.Value) tp = strings.Replace(tp, "ChangeContentValueOf", "", 1) res := "" diff --git a/data/threadbuilder/userjoinexample.yml b/data/threadbuilder/userjoinexample.yml index d41665ca..883ba5e1 100644 --- a/data/threadbuilder/userjoinexample.yml +++ b/data/threadbuilder/userjoinexample.yml @@ -99,5 +99,5 @@ graph: baseSnapshot: A.1.1 aclHeads: [B.1.1] treeHeads: [B.1.1] -heads: +maybeHeads: - "A.1.3" diff --git a/data/threadbuilder/userremovebeforeexample.yml b/data/threadbuilder/userremovebeforeexample.yml index fb214099..fe4d2b6e 100644 --- a/data/threadbuilder/userremovebeforeexample.yml +++ b/data/threadbuilder/userremovebeforeexample.yml @@ -101,6 +101,6 @@ graph: baseSnapshot: A.1.1 aclHeads: [A.1.2] treeHeads: [A.1.2] -heads: +maybeHeads: - "A.1.3" - "B.1.2" diff --git a/data/threadbuilder/userremoveexample.yml b/data/threadbuilder/userremoveexample.yml index 51fa2d76..ab131fb5 100644 --- a/data/threadbuilder/userremoveexample.yml +++ b/data/threadbuilder/userremoveexample.yml @@ -101,6 +101,6 @@ graph: aclSnapshot: A.1.1 aclHeads: [A.1.3] treeHeads: [A.1.3] -heads: +maybeHeads: - "A.1.4" - "B.1.2" diff --git a/data/threadbuilder/validsnapshotexample.yml b/data/threadbuilder/validsnapshotexample.yml index 2e3c60c6..2c3e49d0 100644 --- a/data/threadbuilder/validsnapshotexample.yml +++ b/data/threadbuilder/validsnapshotexample.yml @@ -124,7 +124,7 @@ graph: baseSnapshot: A.1.2 aclHeads: [A.1.2] treeHeads: [A.1.2] -heads: +maybeHeads: - "A.1.3" - "B.1.2" diff --git a/data/threadbuilder/ymlentities.go b/data/threadbuilder/ymlentities.go index 8052deb4..78775758 100644 --- a/data/threadbuilder/ymlentities.go +++ b/data/threadbuilder/ymlentities.go @@ -94,5 +94,6 @@ type YMLThread struct { TreeHeads []string `yaml:"treeHeads"` } `yaml:"graph"` - Heads []string `yaml:"heads"` + Heads []string `yaml:"heads"` + MaybeHeads []string `yaml:"maybeHeads"` }