Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement trusted contacts #1406

Merged
merged 28 commits into from
Aug 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
51216c9
wip(daemon): getting account on each change.
juligasa Jul 26, 2023
245d664
wip(daemon): new trusted endpoint definitions
juligasa Jul 26, 2023
35453e6
wip(daemon): move code to new trustedentity fcn
juligasa Jul 26, 2023
7d79d99
fix(proto): following ubiquitous language rules
juligasa Jul 26, 2023
78b44f9
wip: include new trusted table
juligasa Jul 26, 2023
e4e87b1
fix: remove sql comments not used in migrations
juligasa Jul 26, 2023
40cd971
wip: generate proto files for the new API endpoint
juligasa Jul 26, 2023
eec4217
wip: implement trusted and untrusted sql functions
juligasa Jul 27, 2023
d138d04
fix: migrate properly
juligasa Jul 27, 2023
6d4fbed
wip: implement trusting functions
juligasa Jul 27, 2023
4bcc768
wip: adding trusted/untrusted peer tests
juligasa Jul 27, 2023
7005ed6
move own acc trust to register and safety rework
juligasa Jul 28, 2023
00c5762
fix: not failing to migrate trust on new databases
juligasa Jul 28, 2023
7cd2a36
wip: show only latest trusted change
juligasa Jul 28, 2023
28566ee
wip: adding e2e tests
juligasa Jul 28, 2023
8f704ff
Trusted+Global+AccountTrust UI
ericvicenti Jul 31, 2023
0473796
fix(daemon): change trusted migration date
juligasa Jul 31, 2023
3094ffc
feat(daemon): forbid untrust self
juligasa Jul 31, 2023
0c4d3f8
Contacts List Trusting
ericvicenti Jul 31, 2023
1713328
wip: including partial complex trusted test
juligasa Aug 2, 2023
e2cdbf3
wip: remove alice's trustness from carol
juligasa Aug 2, 2023
2ab9044
Trusted publication context
ericvicenti Aug 2, 2023
b5f0503
wip: cleaning lints
juligasa Aug 3, 2023
d1a4d0c
wip: complex trusted testcase (failing)
juligasa Aug 3, 2023
5c4492f
fix(daemon): trusted entities algorithm in sql
juligasa Aug 4, 2023
7e6604f
wip:lint
juligasa Aug 4, 2023
213cc16
wip: remove unused sleeps
juligasa Aug 7, 2023
45d4258
Simplify queries for loading trusted changes
burdiyan Aug 7, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 45 additions & 2 deletions backend/daemon/api/accounts/v1alpha/accounts.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
// Package accounts implements account functions.
package accounts

import (
"bytes"
"context"
"fmt"

"mintter/backend/core"
accounts "mintter/backend/genproto/accounts/v1alpha"
"mintter/backend/hyper"
Expand Down Expand Up @@ -81,7 +81,13 @@ func (srv *Server) GetAccount(ctx context.Context, in *accounts.GetAccountReques
DeviceId: pids,
}
}

istrusted, err := hypersql.IsTrustedAccount(conn, aid)
if err != nil {
return err
}
if istrusted.TrustedAccountsID != 0 {
acc.IsTrusted = true
}
return nil
}); err != nil {
return nil, err
Expand Down Expand Up @@ -172,6 +178,7 @@ func (srv *Server) UpdateProfile(ctx context.Context, in *accounts.Profile) (*ac
return srv.GetAccount(ctx, &accounts.GetAccountRequest{})
}

// UpdateProfile is public so it can be called from sites.
func UpdateProfile(ctx context.Context, me core.Identity, blobs *hyper.Storage, in *accounts.Profile) error {
eid := hyper.EntityID("hd://a/" + me.Account().Principal().String())

Expand Down Expand Up @@ -229,6 +236,42 @@ func UpdateProfile(ctx context.Context, me core.Identity, blobs *hyper.Storage,
return nil
}

// SetAccountTrust implements the corresponding gRPC method.
func (srv *Server) SetAccountTrust(ctx context.Context, in *accounts.SetAccountTrustRequest) (*accounts.Account, error) {
acc, err := core.DecodePrincipal(in.Id)
if err != nil {
return nil, err
}
if in.IsTrusted {
err = srv.blobs.SetAccountTrust(ctx, acc)
} else {
me, ok := srv.me.Get()
if !ok {
return nil, fmt.Errorf("account not initialized yet")
}
if acc.String() == me.Account().Principal().String() {
return nil, fmt.Errorf("cannot untrust self")
}
err = srv.blobs.UnsetAccountTrust(ctx, acc)
}

if err != nil {
return nil, err
}

updatedAcc, err := srv.GetAccount(ctx, &accounts.GetAccountRequest{
Id: acc.String(),
})
if err != nil {
return nil, err
}
if updatedAcc.IsTrusted != in.IsTrusted {
return nil, fmt.Errorf("Expected trusted %t but got %t", in.IsTrusted, updatedAcc.IsTrusted)
}

return updatedAcc, nil
}

// ListAccounts implements the corresponding gRPC method.
func (srv *Server) ListAccounts(ctx context.Context, in *accounts.ListAccountsRequest) (*accounts.ListAccountsResponse, error) {
me, err := srv.me.Await(ctx)
Expand Down
20 changes: 18 additions & 2 deletions backend/daemon/api/accounts/v1alpha/accounts_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ func TestGetAccount_Own(t *testing.T) {
DeviceId: "12D3KooWFMTJanyH3XttUC2AmS9fZnbeYsxbAjSEvyCeHVbHBX3C",
},
},
IsTrusted: true,
}

acc, err := alice.GetAccount(ctx, &accounts.GetAccountRequest{})
require.NoError(t, err)
testutil.ProtoEqual(t, want, acc, "accounts don't match")
Expand Down Expand Up @@ -67,12 +67,12 @@ func TestAPIUpdateProfile(t *testing.T) {
DeviceId: "12D3KooWFMTJanyH3XttUC2AmS9fZnbeYsxbAjSEvyCeHVbHBX3C",
},
},
IsTrusted: true,
}

updated, err := alice.UpdateProfile(ctx, want.Profile)
require.NoError(t, err)
testutil.ProtoEqual(t, want, updated, "account must be equal")

stored, err := alice.GetAccount(ctx, &accounts.GetAccountRequest{})
require.NoError(t, err)
testutil.ProtoEqual(t, want, stored, "get account must return updated account")
Expand All @@ -90,6 +90,7 @@ func TestAPIUpdateProfile(t *testing.T) {
DeviceId: "12D3KooWFMTJanyH3XttUC2AmS9fZnbeYsxbAjSEvyCeHVbHBX3C",
},
},
IsTrusted: true,
}

updated, err := alice.UpdateProfile(ctx, want.Profile)
Expand All @@ -102,6 +103,21 @@ func TestAPIUpdateProfile(t *testing.T) {
}
}

func TestTrustOwnAccount(t *testing.T) {
alice := newTestServer(t, "alice")
bob := coretest.NewTester("bob")
ctx := context.Background()
acc, err := alice.SetAccountTrust(ctx, &accounts.SetAccountTrustRequest{
Id: bob.Account.Principal().String(),
IsTrusted: true,
})
require.Error(t, err, "Alice must not have Bob's account")
require.Nil(t, acc)
acc, err = alice.GetAccount(ctx, &accounts.GetAccountRequest{}) //No id=own account
require.NoError(t, err)
require.True(t, acc.IsTrusted)
}

// TODO: update profile idempotent no change

func newTestServer(t *testing.T, name string) *Server {
Expand Down
5 changes: 5 additions & 0 deletions backend/daemon/api/daemon/v1alpha/daemon.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
// Package daemon assembles everything to boot the mintterd program. It's like main, but made a separate package
// to be importable and testable by other packages, because package main can't be imported.
package daemon

import (
Expand Down Expand Up @@ -119,6 +121,9 @@ func Register(ctx context.Context, bs *hyper.Storage, account core.KeyPair, devi
return cid.Undef, err
}

if err = bs.SetAccountTrust(ctx, account.Principal()); err != nil {
return blob.CID, fmt.Errorf("Could not set own account to trusted: " + err.Error())
}
return blob.CID, nil
}

Expand Down
17 changes: 11 additions & 6 deletions backend/daemon/api/documents/v1alpha/documents.go
Original file line number Diff line number Diff line change
Expand Up @@ -338,7 +338,7 @@ func (api *Server) GetPublication(ctx context.Context, in *documents.GetPublicat
eid := hyper.EntityID("hd://d/" + in.DocumentId)
version := hyper.Version(in.Version)

pub, err := api.loadPublication(ctx, eid, version)
pub, err := api.loadPublication(ctx, eid, version, in.TrustedOnly)
if err == nil {
return pub, nil
}
Expand All @@ -362,10 +362,10 @@ func (api *Server) GetPublication(ctx context.Context, in *documents.GetPublicat
return nil, status.Errorf(codes.NotFound, "failed to discover object %q at version %q", eid, version)
}

return api.loadPublication(ctx, eid, version)
return api.loadPublication(ctx, eid, version, in.TrustedOnly)
}

func (api *Server) loadPublication(ctx context.Context, docid hyper.EntityID, version hyper.Version) (docpb *documents.Publication, err error) {
func (api *Server) loadPublication(ctx context.Context, docid hyper.EntityID, version hyper.Version, trustedOnly bool) (docpb *documents.Publication, err error) {
var entity *hyper.Entity
if version != "" {
heads, err := hyper.Version(version).Parse()
Expand All @@ -378,7 +378,11 @@ func (api *Server) loadPublication(ctx context.Context, docid hyper.EntityID, ve
return nil, err
}
} else {
entity, err = api.blobs.LoadEntity(ctx, docid)
if trustedOnly {
entity, err = api.blobs.LoadTrustedEntity(ctx, docid)
} else {
entity, err = api.blobs.LoadEntity(ctx, docid)
}
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -443,8 +447,9 @@ func (api *Server) ListPublications(ctx context.Context, in *documents.ListPubli
for _, e := range entities {
docid := e.TrimPrefix("hd://d/")
pub, err := api.GetPublication(ctx, &documents.GetPublicationRequest{
DocumentId: docid,
LocalOnly: true,
DocumentId: docid,
LocalOnly: true,
TrustedOnly: in.TrustedOnly,
})
if err != nil {
continue
Expand Down
151 changes: 149 additions & 2 deletions backend/daemon/daemon_e2e_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -421,6 +421,84 @@ func TestSite(t *testing.T) {
require.Error(t, err)
}

func TestTrustedChanges(t *testing.T) {
burdiyan marked this conversation as resolved.
Show resolved Hide resolved
ctx, cancel := context.WithCancel(context.Background())

t.Cleanup(func() {
cancel()
})

trusted := makeTestApp(t, "alice", makeTestConfig(t), true)
untrusted := makeTestApp(t, "bob", makeTestConfig(t), true)

// Both peers connect connect to each other, to exchange documents. Does not mean they trust each other.
_, err := trusted.RPC.Networking.Connect(ctx, &networking.ConnectRequest{Addrs: getAddrs(t, untrusted)})
require.NoError(t, err)

// Create a document.
sharedDocument := publishDocument(t, ctx, trusted)

// Sync the document with the untusted peer so it can modify it.
_, err = untrusted.RPC.Daemon.ForceSync(ctx, &daemon.ForceSyncRequest{})
require.NoError(t, err)
var publicationList *documents.ListPublicationsResponse
require.Eventually(t, func() bool {
// List all documents which by default includes untrusted changes.
publicationList, err = untrusted.RPC.Documents.ListPublications(ctx, &documents.ListPublicationsRequest{})
require.NoError(t, err)
return len(publicationList.Publications) == 1
}, 1*time.Second, 100*time.Millisecond, "peer should have synced the document")

// Check that the received version is the one the initial author created.
require.Equal(t, sharedDocument.Version, publicationList.Publications[0].Version)
require.Equal(t, sharedDocument.Document.Author, publicationList.Publications[0].Document.Author)
require.Equal(t, sharedDocument.Document.Id, publicationList.Publications[0].Document.Id)

// Add an untrusted change
const anotherTitle = "New Document title leading to a new version"
newVersion := updateDocumenTitle(t, ctx, untrusted, sharedDocument.Document.Id, anotherTitle)
require.Equal(t, sharedDocument.Document.Id, newVersion.Document.Id)

// Send the document back to the creator which the new untrusted changes.
_, err = trusted.RPC.Daemon.ForceSync(ctx, &daemon.ForceSyncRequest{})
require.NoError(t, err)

require.Eventually(t, func() bool {
// List all documents which by default includes untrusted changes.
publicationList, err = trusted.RPC.Documents.ListPublications(ctx, &documents.ListPublicationsRequest{})
require.NoError(t, err)
return len(publicationList.Publications) == 1
}, 1*time.Second, 100*time.Millisecond, "peer should have synced the document")

// Check that the version is the latest one (untrusted).
require.Equal(t, newVersion.Version, publicationList.Publications[0].Version)
require.Equal(t, newVersion.Document.Author, publicationList.Publications[0].Document.Author)
require.Equal(t, newVersion.Document.Id, publicationList.Publications[0].Document.Id)

// Now ask for trusted changes only.
publicationList, err = trusted.RPC.Documents.ListPublications(ctx, &documents.ListPublicationsRequest{
TrustedOnly: true,
})
require.NoError(t, err)
require.Len(t, publicationList.Publications, 1, "same document, different version")

// Check that the version is the initial one, without any untrusted changes.
require.Equal(t, sharedDocument.Version, publicationList.Publications[0].Version)
require.Equal(t, sharedDocument.Document.Author, publicationList.Publications[0].Document.Author)
require.Equal(t, sharedDocument.Document.Id, publicationList.Publications[0].Document.Id)

// But the untrusted peer (last editor) should get the latest version since it was him
// the one who wrote the latest changes and he trusts himself by default.
publicationList, err = untrusted.RPC.Documents.ListPublications(ctx, &documents.ListPublicationsRequest{
TrustedOnly: true,
})
require.NoError(t, err)
require.Len(t, publicationList.Publications, 1, "same document, different version")
require.Equal(t, newVersion.Version, publicationList.Publications[0].Version)
require.Equal(t, newVersion.Document.Author, publicationList.Publications[0].Document.Author)
require.Equal(t, newVersion.Document.Id, publicationList.Publications[0].Document.Id)
}

func TestGateway(t *testing.T) {
t.Parallel()
ctx, cancel := context.WithCancel(context.Background())
Expand Down Expand Up @@ -665,6 +743,7 @@ func TestPeriodicSync(t *testing.T) {
bacc := must.Do2(b.RPC.Accounts.GetAccount(ctx, &accounts.GetAccountRequest{}))

require.Len(t, accs.Accounts, 1, msg)
bacc.IsTrusted = accs.Accounts[0].IsTrusted // just bc they synced they dont trust each other
testutil.ProtoEqual(t, bacc, accs.Accounts[0], "a must fetch b's account fully")
}

Expand All @@ -683,9 +762,9 @@ func TestMultiDevice(t *testing.T) {
Addrs: getAddrs(t, alice2),
})
require.NoError(t, err)

acc1 := must.Do2(alice1.RPC.Accounts.GetAccount(ctx, &accounts.GetAccountRequest{}))
acc2 := must.Do2(alice2.RPC.Accounts.GetAccount(ctx, &accounts.GetAccountRequest{}))

require.False(t, proto.Equal(acc1, acc2), "accounts must not match before syncing")

{
Expand All @@ -701,14 +780,82 @@ func TestMultiDevice(t *testing.T) {
require.Equal(t, int64(0), sr.NumSyncFailed)
require.Equal(t, []peer.ID{alice2.Storage.Device().PeerID(), alice1.Storage.Device().PeerID()}, sr.Peers)
}

acc1 = must.Do2(alice1.RPC.Accounts.GetAccount(ctx, &accounts.GetAccountRequest{}))
acc2 = must.Do2(alice2.RPC.Accounts.GetAccount(ctx, &accounts.GetAccountRequest{}))
testutil.ProtoEqual(t, acc1, acc2, "accounts must match after sync")

require.Len(t, acc2.Devices, 2, "must have two devices after syncing")
}

func TestTrustedPeers(t *testing.T) {
t.Parallel()

alice := makeTestApp(t, "alice", makeTestConfig(t), true)
bob := makeTestApp(t, "bob", makeTestConfig(t), true)
ctx := context.Background()

_, err := alice.RPC.Networking.Connect(ctx, &networking.ConnectRequest{
Addrs: getAddrs(t, bob),
})
require.NoError(t, err)

{
sr := must.Do2(alice.Syncing.MustGet().Sync(ctx))
require.Equal(t, int64(1), sr.NumSyncOK)
require.Equal(t, int64(0), sr.NumSyncFailed)
require.Equal(t, []peer.ID{alice.Storage.Device().PeerID(), bob.Storage.Device().PeerID()}, sr.Peers)
}

{
sr := must.Do2(bob.Syncing.MustGet().Sync(ctx))
require.Equal(t, int64(1), sr.NumSyncOK)
require.Equal(t, int64(0), sr.NumSyncFailed)
require.Equal(t, []peer.ID{bob.Storage.Device().PeerID(), alice.Storage.Device().PeerID()}, sr.Peers)
}

acc1 := must.Do2(alice.RPC.Accounts.GetAccount(ctx, &accounts.GetAccountRequest{Id: bob.Net.MustGet().ID().Account().Principal().String()}))
require.False(t, acc1.IsTrusted)
acc2 := must.Do2(bob.RPC.Accounts.GetAccount(ctx, &accounts.GetAccountRequest{Id: alice.Net.MustGet().ID().Account().Principal().String()}))
require.False(t, acc2.IsTrusted)

acc1, err = alice.RPC.Accounts.SetAccountTrust(ctx, &accounts.SetAccountTrustRequest{Id: bob.Net.MustGet().ID().Account().Principal().String(), IsTrusted: true})
require.NoError(t, err)
require.True(t, acc1.IsTrusted)

//Just because they sync the should not be trusted
{
sr := must.Do2(alice.Syncing.MustGet().Sync(ctx))
require.Equal(t, int64(1), sr.NumSyncOK)
require.Equal(t, int64(0), sr.NumSyncFailed)
require.Equal(t, []peer.ID{alice.Storage.Device().PeerID(), bob.Storage.Device().PeerID()}, sr.Peers)
}

{
sr := must.Do2(bob.Syncing.MustGet().Sync(ctx))
require.Equal(t, int64(1), sr.NumSyncOK)
require.Equal(t, int64(0), sr.NumSyncFailed)
require.Equal(t, []peer.ID{bob.Storage.Device().PeerID(), alice.Storage.Device().PeerID()}, sr.Peers)
}
time.Sleep(100 * time.Millisecond) // to give time to sync
acc1 = must.Do2(alice.RPC.Accounts.GetAccount(ctx, &accounts.GetAccountRequest{Id: bob.Net.MustGet().ID().Account().Principal().String()}))
require.True(t, acc1.IsTrusted)
acc2 = must.Do2(bob.RPC.Accounts.GetAccount(ctx, &accounts.GetAccountRequest{Id: alice.Net.MustGet().ID().Account().Principal().String()}))
require.False(t, acc2.IsTrusted)

acc1, err = alice.RPC.Accounts.SetAccountTrust(ctx, &accounts.SetAccountTrustRequest{Id: bob.Net.MustGet().ID().Account().Principal().String(), IsTrusted: false})
require.NoError(t, err)
require.False(t, acc1.IsTrusted)
acc2, err = bob.RPC.Accounts.SetAccountTrust(ctx, &accounts.SetAccountTrustRequest{Id: alice.Net.MustGet().ID().Account().Principal().String(), IsTrusted: true})
require.NoError(t, err)
require.True(t, acc2.IsTrusted)

//Double check
acc1 = must.Do2(alice.RPC.Accounts.GetAccount(ctx, &accounts.GetAccountRequest{Id: bob.Net.MustGet().ID().Account().Principal().String()}))
require.False(t, acc1.IsTrusted)
acc2 = must.Do2(bob.RPC.Accounts.GetAccount(ctx, &accounts.GetAccountRequest{Id: alice.Net.MustGet().ID().Account().Principal().String()}))
require.True(t, acc2.IsTrusted)
}

func TestNetworkingListPeers(t *testing.T) {
t.Parallel()

Expand Down
Loading
Loading