From e1238463fa761085fd5926d9d1455a19a14bd1f4 Mon Sep 17 00:00:00 2001 From: Gabriel Handford Date: Mon, 24 Aug 2020 11:38:35 -0700 Subject: [PATCH] User: Hide echo from search (#131) --- user/echo_test.go | 13 +++++----- user/github_test.go | 11 +++++--- user/options.go | 37 +++++++++++++++++++++++++++ user/reddit_test.go | 24 +++++++++++++++++- user/result.go | 45 +++++++++++++++++++++++++++++++++ user/search.go | 3 +-- user/search_test.go | 18 ++++++------- user/twitter_test.go | 26 +++++++++++++++++-- user/users.go | 60 +++++++++++--------------------------------- user/users_test.go | 8 +++--- 10 files changed, 172 insertions(+), 73 deletions(-) create mode 100644 user/options.go create mode 100644 user/result.go diff --git a/user/echo_test.go b/user/echo_test.go index 2ef1532..6954f0d 100644 --- a/user/echo_test.go +++ b/user/echo_test.go @@ -22,7 +22,7 @@ func TestResultEcho(t *testing.T) { req := request.NewMockRequestor() ds := docs.NewMem() scs := keys.NewSigchains(ds) - users := user.NewUsers(ds, scs, req, clock) + users := user.NewUsers(ds, scs, user.Requestor(req), user.Clock(clock)) usr, err := user.NewForSigning(sk.ID(), "echo", "alice") require.NoError(t, err) @@ -72,10 +72,11 @@ func TestResultEcho(t *testing.T) { require.Equal(t, 1, len(kids)) require.Equal(t, keys.ID("kex132yw8ht5p8cetl2jmvknewjawt9xwzdlrk2pyxlnwjyqrdq0dawqqph077"), kids[0]) - // TODO: When we flip indexUser to indexSearch, uncomment this... - // res, err := users.Search(context.TODO(), &user.SearchRequest{Query: "alice@echo"}) - // require.NoError(t, err) - // require.Equal(t, 0, len(res)) + // Echo is hidden from search + res, err := users.Search(context.TODO(), &user.SearchRequest{Query: "alice@echo"}) + require.NoError(t, err) + require.Equal(t, 0, len(res)) + // require.Equal(t, keys.ID("kex132yw8ht5p8cetl2jmvknewjawt9xwzdlrk2pyxlnwjyqrdq0dawqqph077"), res[0].KID) } func TestRequestVerifyEcho(t *testing.T) { @@ -85,7 +86,7 @@ func TestRequestVerifyEcho(t *testing.T) { req := request.NewMockRequestor() ds := docs.NewMem() scs := keys.NewSigchains(ds) - users := user.NewUsers(ds, scs, req, clock) + users := user.NewUsers(ds, scs, user.Requestor(req), user.Clock(clock)) usrSign, err := user.NewForSigning(sk.ID(), "echo", "alice") require.NoError(t, err) diff --git a/user/github_test.go b/user/github_test.go index f51b402..9afa824 100644 --- a/user/github_test.go +++ b/user/github_test.go @@ -20,7 +20,7 @@ func TestResultGithub(t *testing.T) { req := request.NewMockRequestor() ds := docs.NewMem() scs := keys.NewSigchains(ds) - users := user.NewUsers(ds, scs, req, clock) + users := user.NewUsers(ds, scs, user.Requestor(req), user.Clock(clock)) req.SetResponse("https://gist.github.com/alice/70281cc427850c272a8574af4d8564d9", testdata(t, "testdata/github/70281cc427850c272a8574af4d8564d9")) @@ -68,6 +68,11 @@ func TestResultGithub(t *testing.T) { require.NoError(t, err) require.Equal(t, 1, len(kids)) require.Equal(t, keys.ID("kex132yw8ht5p8cetl2jmvknewjawt9xwzdlrk2pyxlnwjyqrdq0dawqqph077"), kids[0]) + + res, err := users.Search(context.TODO(), &user.SearchRequest{Query: "alice"}) + require.NoError(t, err) + require.Equal(t, 1, len(res)) + require.Equal(t, keys.ID("kex132yw8ht5p8cetl2jmvknewjawt9xwzdlrk2pyxlnwjyqrdq0dawqqph077"), res[0].KID) } func TestResultGithubWrongName(t *testing.T) { @@ -77,7 +82,7 @@ func TestResultGithubWrongName(t *testing.T) { req := request.NewMockRequestor() ds := docs.NewMem() scs := keys.NewSigchains(ds) - users := user.NewUsers(ds, scs, req, clock) + users := user.NewUsers(ds, scs, user.Requestor(req), user.Clock(clock)) usr, err := user.NewForSigning(sk.ID(), "github", "alice2") require.NoError(t, err) @@ -111,7 +116,7 @@ func TestResultGithubWrongService(t *testing.T) { req := request.NewMockRequestor() ds := docs.NewMem() scs := keys.NewSigchains(ds) - users := user.NewUsers(ds, scs, req, clock) + users := user.NewUsers(ds, scs, user.Requestor(req), user.Clock(clock)) sc := keys.NewSigchain(sk.ID()) muser := &user.User{KID: sk.ID(), Service: "github2", Name: "gabriel"} diff --git a/user/options.go b/user/options.go new file mode 100644 index 0000000..d18b0d9 --- /dev/null +++ b/user/options.go @@ -0,0 +1,37 @@ +package user + +import ( + "github.com/keys-pub/keys/request" + "github.com/keys-pub/keys/tsutil" +) + +// UsersOptions are options for Users. +type UsersOptions struct { + Req request.Requestor + Clock tsutil.Clock +} + +// UsersOption ... +type UsersOption func(*UsersOptions) + +func newUserOptions(opts ...UsersOption) UsersOptions { + var options UsersOptions + for _, o := range opts { + o(&options) + } + return options +} + +// Requestor to use. +func Requestor(req request.Requestor) UsersOption { + return func(o *UsersOptions) { + o.Req = req + } +} + +// Clock to use. +func Clock(clock tsutil.Clock) UsersOption { + return func(o *UsersOptions) { + o.Clock = clock + } +} diff --git a/user/reddit_test.go b/user/reddit_test.go index 4dc1e93..3cc841f 100644 --- a/user/reddit_test.go +++ b/user/reddit_test.go @@ -19,7 +19,7 @@ func TestResultReddit(t *testing.T) { req := request.NewMockRequestor() ds := docs.NewMem() scs := keys.NewSigchains(ds) - users := user.NewUsers(ds, scs, req, clock) + users := user.NewUsers(ds, scs, user.Requestor(req), user.Clock(clock)) usr, err := user.NewForSigning(sk.ID(), "reddit", "charlie") require.NoError(t, err) @@ -51,4 +51,26 @@ func TestResultReddit(t *testing.T) { require.Equal(t, "charlie", result.User.Name) require.Equal(t, int64(1234567890003), result.VerifiedAt) require.Equal(t, int64(1234567890003), result.Timestamp) + + result, err = users.Get(context.TODO(), sk.ID()) + require.NoError(t, err) + require.NotNil(t, result) + require.Equal(t, "reddit", result.User.Service) + require.Equal(t, "charlie", result.User.Name) + + result, err = users.User(context.TODO(), "charlie@reddit") + require.NoError(t, err) + require.NotNil(t, result) + require.Equal(t, "reddit", result.User.Service) + require.Equal(t, "charlie", result.User.Name) + + kids, err := users.KIDs(context.TODO()) + require.NoError(t, err) + require.Equal(t, 1, len(kids)) + require.Equal(t, keys.ID("kex132yw8ht5p8cetl2jmvknewjawt9xwzdlrk2pyxlnwjyqrdq0dawqqph077"), kids[0]) + + res, err := users.Search(context.TODO(), &user.SearchRequest{Query: "charlie"}) + require.NoError(t, err) + require.Equal(t, 1, len(res)) + require.Equal(t, keys.ID("kex132yw8ht5p8cetl2jmvknewjawt9xwzdlrk2pyxlnwjyqrdq0dawqqph077"), res[0].KID) } diff --git a/user/result.go b/user/result.go new file mode 100644 index 0000000..1b118e3 --- /dev/null +++ b/user/result.go @@ -0,0 +1,45 @@ +package user + +import ( + "fmt" + "time" + + "github.com/keys-pub/keys" + "github.com/keys-pub/keys/tsutil" +) + +// Result describes the status of a User. +// TODO: Make Err/Status more explicit, it can be confusing. +type Result struct { + Err string `json:"err,omitempty"` + Status Status `json:"status"` + // Timestamp is the when the status was last updated. + Timestamp int64 `json:"ts"` + User *User `json:"user"` + // VerifiedAt is when the status was last OK. + VerifiedAt int64 `json:"vts"` +} + +type keyDocument struct { + KID keys.ID `json:"kid"` + Result *Result `json:"result,omitempty"` +} + +func (r Result) String() string { + if r.Status == StatusOK { + return fmt.Sprintf("%s:%s(%d)", r.Status, r.User, r.VerifiedAt) + } + return fmt.Sprintf("%s:%s;err=%s", r.Status, r.User, r.Err) +} + +// IsTimestampExpired returns true if result Timestamp is older than dt. +func (r Result) IsTimestampExpired(now time.Time, dt time.Duration) bool { + ts := tsutil.ConvertMillis(r.Timestamp) + return (ts.IsZero() || now.Sub(ts) > dt) +} + +// IsVerifyExpired returns true if result VerifiedAt is older than dt. +func (r Result) IsVerifyExpired(now time.Time, dt time.Duration) bool { + ts := tsutil.ConvertMillis(r.VerifiedAt) + return (ts.IsZero() || now.Sub(ts) > dt) +} diff --git a/user/search.go b/user/search.go index 6201084..f1116ea 100644 --- a/user/search.go +++ b/user/search.go @@ -26,8 +26,7 @@ type SearchResult struct { func (u *Users) searchUsers(ctx context.Context, query string, limit int) ([]*SearchResult, error) { logger.Infof("Searching users %q", query) - // TODO: Change to indexSearch when that index is available - iter, err := u.ds.DocumentIterator(ctx, indexUser, docs.Prefix(query)) + iter, err := u.ds.DocumentIterator(ctx, indexSearch, docs.Prefix(query)) if err != nil { return nil, err } diff --git a/user/search_test.go b/user/search_test.go index b83ba5a..b188de5 100644 --- a/user/search_test.go +++ b/user/search_test.go @@ -27,7 +27,7 @@ func TestSearchUsers(t *testing.T) { scs.SetClock(clock) req := request.NewMockRequestor() - users := user.NewUsers(ds, scs, req, clock) + users := user.NewUsers(ds, scs, user.Requestor(req), user.Clock(clock)) ctx := context.TODO() results, err := users.Search(ctx, &user.SearchRequest{}) @@ -180,7 +180,7 @@ func TestFind(t *testing.T) { scs.SetClock(clock) req := request.NewMockRequestor() - users := user.NewUsers(ds, scs, req, clock) + users := user.NewUsers(ds, scs, user.Requestor(req), user.Clock(clock)) ctx := context.TODO() alice := keys.NewEdX25519KeyFromSeed(testSeed(0x01)) @@ -219,7 +219,7 @@ func TestUsersEmpty(t *testing.T) { scs.SetClock(clock) req := request.NewMockRequestor() - users := user.NewUsers(ds, scs, req, clock) + users := user.NewUsers(ds, scs, user.Requestor(req), user.Clock(clock)) key := keys.NewEdX25519KeyFromSeed(testSeed(0x01)) @@ -242,7 +242,7 @@ func TestUserValidateName(t *testing.T) { scs.SetClock(clock) req := request.NewMockRequestor() - users := user.NewUsers(ds, scs, req, clock) + users := user.NewUsers(ds, scs, user.Requestor(req), user.Clock(clock)) key := keys.NewEdX25519KeyFromSeed(keys.Bytes32(bytes.Repeat([]byte{0x20}, 32))) @@ -271,7 +271,7 @@ func TestUserValidateUpdateInvalid(t *testing.T) { scs.SetClock(clock) req := request.NewMockRequestor() - users := user.NewUsers(ds, scs, req, clock) + users := user.NewUsers(ds, scs, user.Requestor(req), user.Clock(clock)) // Unvalidated user to sigchain key := keys.NewEdX25519KeyFromSeed(keys.Bytes32(bytes.Repeat([]byte{0x021}, 32))) @@ -321,7 +321,7 @@ func TestReddit(t *testing.T) { scs.SetClock(clock) req := request.NewMockRequestor() - users := user.NewUsers(ds, scs, req, clock) + users := user.NewUsers(ds, scs, user.Requestor(req), user.Clock(clock)) key := keys.NewEdX25519KeyFromSeed(testSeed(0x01)) redditURL := "https://reddit.com/r/keyspubmsgs/comments/123/alice" @@ -376,7 +376,7 @@ func TestSearchUsersRequestErrors(t *testing.T) { scs.SetClock(clock) req := request.NewMockRequestor() - users := user.NewUsers(ds, scs, req, clock) + users := user.NewUsers(ds, scs, user.Requestor(req), user.Clock(clock)) ctx := context.TODO() results, err := users.Search(ctx, &user.SearchRequest{}) @@ -460,7 +460,7 @@ func TestExpired(t *testing.T) { clock := tsutil.NewTestClock() req := request.NewMockRequestor() - users := user.NewUsers(ds, scs, req, clock) + users := user.NewUsers(ds, scs, user.Requestor(req), user.Clock(clock)) ctx := context.TODO() ids, err := users.Expired(ctx, time.Hour, time.Hour*24*60) @@ -583,7 +583,7 @@ func TestSearch(t *testing.T) { ds := docs.NewMem() scs := keys.NewSigchains(ds) req := request.NewMockRequestor() - users := user.NewUsers(ds, scs, req, clock) + users := user.NewUsers(ds, scs, user.Requestor(req), user.Clock(clock)) ctx := context.TODO() for i := 0; i < 10; i++ { diff --git a/user/twitter_test.go b/user/twitter_test.go index 5d8e33b..16d6e8a 100644 --- a/user/twitter_test.go +++ b/user/twitter_test.go @@ -41,7 +41,7 @@ func TestResultTwitter(t *testing.T) { req := request.NewMockRequestor() ds := docs.NewMem() scs := keys.NewSigchains(ds) - users := user.NewUsers(ds, scs, req, clock) + users := user.NewUsers(ds, scs, user.Requestor(req), user.Clock(clock)) // usr, err := user.NewForSigning(sk.ID(), "twitter", "bob") // require.NoError(t, err) @@ -104,6 +104,28 @@ func TestResultTwitter(t *testing.T) { require.Equal(t, "bob", result.User.Name) require.Equal(t, int64(1234567890004), result.VerifiedAt) require.Equal(t, int64(1234567890005), result.Timestamp) + + result, err = users.Get(context.TODO(), sk.ID()) + require.NoError(t, err) + require.NotNil(t, result) + require.Equal(t, "twitter", result.User.Service) + require.Equal(t, "bob", result.User.Name) + + result, err = users.User(context.TODO(), "bob@twitter") + require.NoError(t, err) + require.NotNil(t, result) + require.Equal(t, "twitter", result.User.Service) + require.Equal(t, "bob", result.User.Name) + + kids, err := users.KIDs(context.TODO()) + require.NoError(t, err) + require.Equal(t, 1, len(kids)) + require.Equal(t, keys.ID("kex132yw8ht5p8cetl2jmvknewjawt9xwzdlrk2pyxlnwjyqrdq0dawqqph077"), kids[0]) + + res, err := users.Search(context.TODO(), &user.SearchRequest{Query: "bob"}) + require.NoError(t, err) + require.Equal(t, 1, len(res)) + require.Equal(t, keys.ID("kex132yw8ht5p8cetl2jmvknewjawt9xwzdlrk2pyxlnwjyqrdq0dawqqph077"), res[0].KID) } func TestResultTwitterInvalidStatement(t *testing.T) { @@ -114,7 +136,7 @@ func TestResultTwitterInvalidStatement(t *testing.T) { req := request.NewMockRequestor() ds := docs.NewMem() scs := keys.NewSigchains(ds) - users := user.NewUsers(ds, scs, req, clock) + users := user.NewUsers(ds, scs, user.Requestor(req), user.Clock(clock)) sc := keys.NewSigchain(sk.ID()) stu, err := user.New(sk.ID(), "twitter", "bob", "https://twitter.com/bob/status/1205589994380783616", sc.LastSeq()+1) diff --git a/user/users.go b/user/users.go index 115ce46..33cc49a 100644 --- a/user/users.go +++ b/user/users.go @@ -13,42 +13,6 @@ import ( "github.com/pkg/errors" ) -// Result describes the status of a User. -// TODO: Make Err/Status more explicit, it can be confusing. -type Result struct { - Err string `json:"err,omitempty"` - Status Status `json:"status"` - // Timestamp is the when the status was last updated. - Timestamp int64 `json:"ts"` - User *User `json:"user"` - // VerifiedAt is when the status was last OK. - VerifiedAt int64 `json:"vts"` -} - -func (r Result) String() string { - if r.Status == StatusOK { - return fmt.Sprintf("%s:%s(%d)", r.Status, r.User, r.VerifiedAt) - } - return fmt.Sprintf("%s:%s;err=%s", r.Status, r.User, r.Err) -} - -// IsTimestampExpired returns true if result Timestamp is older than dt. -func (r Result) IsTimestampExpired(now time.Time, dt time.Duration) bool { - ts := tsutil.ConvertMillis(r.Timestamp) - return (ts.IsZero() || now.Sub(ts) > dt) -} - -// IsVerifyExpired returns true if result VerifiedAt is older than dt. -func (r Result) IsVerifyExpired(now time.Time, dt time.Duration) bool { - ts := tsutil.ConvertMillis(r.VerifiedAt) - return (ts.IsZero() || now.Sub(ts) > dt) -} - -type keyDocument struct { - KID keys.ID `json:"kid"` - Result *Result `json:"result,omitempty"` -} - // Users keeps track of sigchain user links. type Users struct { ds docs.Documents @@ -58,7 +22,16 @@ type Users struct { } // NewUsers creates Users. -func NewUsers(ds docs.Documents, scs *keys.Sigchains, req request.Requestor, clock tsutil.Clock) *Users { +func NewUsers(ds docs.Documents, scs *keys.Sigchains, opt ...UsersOption) *Users { + opts := newUserOptions(opt...) + req := opts.Req + if req == nil { + req = request.NewHTTPRequestor() + } + clock := opts.Clock + if clock == nil { + clock = tsutil.NewClock() + } return &Users{ ds: ds, scs: scs, @@ -72,11 +45,6 @@ func (u *Users) Requestor() request.Requestor { return u.req } -// Documents ... -func (u *Users) Documents() docs.Documents { - return u.ds -} - // Update index for sigchain KID. func (u *Users) Update(ctx context.Context, kid keys.ID) (*Result, error) { logger.Infof("Updating user index for %s", kid) @@ -239,14 +207,14 @@ func (u *Users) unindexUser(ctx context.Context, user *User) error { if _, err := u.ds.Delete(ctx, userPath); err != nil { return err } - searchPath := docs.Path(indexSearch, indexUserKey(user.Service, user.Name)) - if _, err := u.ds.Delete(ctx, searchPath); err != nil { - return err - } servicePath := docs.Path(indexService, indexServiceKey(user.Service, user.Name)) if _, err := u.ds.Delete(ctx, servicePath); err != nil { return err } + searchPath := docs.Path(indexSearch, indexUserKey(user.Service, user.Name)) + if _, err := u.ds.Delete(ctx, searchPath); err != nil { + return err + } return nil } diff --git a/user/users_test.go b/user/users_test.go index 6895a7c..ad985e0 100644 --- a/user/users_test.go +++ b/user/users_test.go @@ -21,7 +21,7 @@ func TestCheckNoUsers(t *testing.T) { clock := tsutil.NewTestClock() ds := docs.NewMem() scs := keys.NewSigchains(ds) - users := user.NewUsers(ds, scs, req, clock) + users := user.NewUsers(ds, scs, user.Requestor(req), user.Clock(clock)) result, err := users.CheckSigchain(context.TODO(), sc) require.NoError(t, err) @@ -38,7 +38,7 @@ func TestCheckFailure(t *testing.T) { clock := tsutil.NewTestClock() ds := docs.NewMem() scs := keys.NewSigchains(ds) - users := user.NewUsers(ds, scs, req, clock) + users := user.NewUsers(ds, scs, user.Requestor(req), user.Clock(clock)) msg := "BEGIN MESSAGE.HWNhu0mATP1TJvQ 2MsM6UREvrdpmJL mlr4taMzxi0olt7 nV35Vkco9gjJ3wyZ0z9hiq2OxrlFUT QVAdNgSZPX3TCKq 6Xr2MZHgg6PbuKB KKAcQRbMCMprx0eQ9AAmF37oSytfuD ekFhesy6sjWc4kJ XA4C6PAxTFwtO14 CEXTYQyBxGH2CYAsm4w2O9xq9TNTZw lo0e7ydqx99UXE8 Qivwr0VNs5.END MESSAGE." req.SetResponse("https://mobile.twitter.com/boboloblaw/status/1259188857846632448", []byte(msg)) @@ -76,7 +76,7 @@ func TestSigchainUsersUpdate(t *testing.T) { ds := docs.NewMem() scs := keys.NewSigchains(ds) req := request.NewMockRequestor() - users := user.NewUsers(ds, scs, req, clock) + users := user.NewUsers(ds, scs, user.Requestor(req), user.Clock(clock)) msg := "BEGIN MESSAGE.HWNhu0mATP1TJvQ 2MsM6UREvrdpmJL mlr4taMzxi0olt7 nV35Vkco9gjJ3wyZ0z9hiq2OxrlFUT QVAdNgSZPX3TCKq 6Xr2MZHgg6PbuKB KKAcQRbMCMprx0eQ9AAmF37oSytfuD ekFhesy6sjWc4kJ XA4C6PAxTFwtO14 CEXTYQyBxGH2CYAsm4w2O9xq9TNTZw lo0e7ydqx99UXE8 Qivwr0VNs5.END MESSAGE." req.SetResponse("https://mobile.twitter.com/gabriel/status/1259188857846632448", []byte(msg)) @@ -95,7 +95,7 @@ func TestSigchainRevokeUpdate(t *testing.T) { ds := docs.NewMem() scs := keys.NewSigchains(ds) req := request.NewMockRequestor() - users := user.NewUsers(ds, scs, req, clock) + users := user.NewUsers(ds, scs, user.Requestor(req), user.Clock(clock)) sk := keys.GenerateEdX25519Key() kid := sk.ID()