diff --git a/bindings/ud.go b/bindings/ud.go index ac89b1e08b6b557bbc5004b4a535bed72a4d3457..df4d3c5bf2fa1ed27bad1ee66b7c9146862c4da9 100644 --- a/bindings/ud.go +++ b/bindings/ud.go @@ -9,6 +9,7 @@ package bindings import ( "fmt" + jww "github.com/spf13/jwalterweatherman" "time" "github.com/pkg/errors" @@ -46,6 +47,63 @@ func NewUserDiscovery(client *Client) (*UserDiscovery, error) { } } +// NewUserDiscoveryFromBackup returns a new user discovery object. It +// wil set up the manager with the backup data. Pass into it the backed up +// facts, one email and phone number each. This will add the registered facts +// to the backed Store. Any one of these fields may be empty, +// however both fields being empty will cause an error. Any other fact that is not +// an email or phone number will return an error. You may only add a fact for the +// accepted types once each. If you attempt to back up a fact type that has already +// been backed up, an error will be returned. Anytime an error is returned, it means +// the backup was not successful. +// NOTE: Do not use this as a direct store operation. This feature is intended to add facts +// to a backend store that have ALREADY BEEN REGISTERED on the account. +// THIS IS NOT FOR ADDING NEWLY REGISTERED FACTS. That is handled on the backend. +// Only call this once. It must be called after StartNetworkFollower +// is called and will fail if the network has never been contacted. +// This function technically has a memory leak because it causes both sides of +// the bindings to think the other is in charge of the client object. +// In general this is not an issue because the client object should exist +// for the life of the program. +// This must be called while start network follower is running. +func NewUserDiscoveryFromBackup(client *Client, + email, phone string) (*UserDiscovery, error) { + single, err := client.getSingle() + if err != nil { + return nil, errors.WithMessage(err, "Failed to create User Discovery Manager") + } + + var emailFact, phoneFact fact.Fact + // Parse email as a fact, if it exists + if len(email) > 2 { + emailFact, err = fact.UnstringifyFact(email) + if err != nil { + return nil, errors.WithMessagef(err, + "Failed to parse malformed email fact: %s", email) + } + } else { + jww.WARN.Printf("Loading manager without a registered email") + } + + // Parse phone number as a fact, if it exists + if len(phone) > 2 { + phoneFact, err = fact.UnstringifyFact(phone) + if err != nil { + return nil, errors.WithMessagef(err, "Failed to parse "+ + "stringified phone fact %q", phone) + } + } else { + jww.WARN.Printf("Loading manager without a registered phone number") + } + + m, err := ud.NewManagerFromBackup(&client.api, single, emailFact, phoneFact) + if err != nil { + return nil, errors.WithMessage(err, "Failed to create User Discovery Manager") + } else { + return &UserDiscovery{ud: m}, nil + } +} + // Register registers a user with user discovery. Will return an error if the // network signatures are malformed or if the username is taken. Usernames // cannot be changed after registration at this time. Will fail if the user is @@ -103,37 +161,6 @@ func (ud *UserDiscovery) RemoveUser(fStr string) error { return ud.ud.RemoveUser(f) } -//BackUpMissingFacts adds a registered fact to the Store object and saves -// it to storage. It can take in both an email or a phone number, passed into -// the function in that order. Any one of these fields may be empty, -// however both fields being empty will cause an error. Any other fact that is not -// an email or phone number will return an error. You may only add a fact for the -// accepted types once each. If you attempt to back up a fact type that has already -// been backed up, an error will be returned. Anytime an error is returned, it means -// the backup was not successful. -// NOTE: Do not use this as a direct store operation. This feature is intended to add facts -// to a backend store that have ALREADY BEEN REGISTERED on the account. -// THIS IS NOT FOR ADDING NEWLY REGISTERED FACTS. That is handled on the backend. -func (ud *UserDiscovery) BackUpMissingFacts(email, phone string) error { - var emailFact, phoneFact fact.Fact - var err error - if len(email) > 2 { - emailFact, err = fact.UnstringifyFact(email) - if err != nil { - return errors.WithMessagef(err, "Failed to parse malformed email fact: %s", email) - } - } - - if len(phone) > 2 { - phoneFact, err = fact.UnstringifyFact(phone) - if err != nil { - return errors.WithMessagef(err, "Failed to parse malformed phone fact: %s", phone) - } - } - - return ud.ud.BackUpMissingFacts(emailFact, phoneFact) -} - // SearchCallback returns the result of a search type SearchCallback interface { Callback(contacts *ContactList, error string) diff --git a/ud/manager.go b/ud/manager.go index 9aa0027d2d25dde78ef62d625b2c13910b762810..821718a58b2fdd29e3427e7c655f88e8288cf364 100644 --- a/ud/manager.go +++ b/ud/manager.go @@ -22,6 +22,7 @@ import ( "gitlab.com/xx_network/crypto/csprng" "gitlab.com/xx_network/crypto/signature/rsa" "gitlab.com/xx_network/primitives/id" + "math" "time" ) @@ -158,6 +159,71 @@ func NewManager(services cmix.Client, e2e e2e.Handler, events event.Manager, return m, nil } +// NewManagerFromBackup builds a new user discover manager from a backup. +// It will construct a manager that is already registered and restore +// already registered facts into store. +func NewManagerFromBackup(client *api.Client, single *single.Manager, + email, phone fact.Fact) (*Manager, error) { + jww.INFO.Println("ud.NewManagerFromBackup()") + if client.NetworkFollowerStatus() != api.Running { + return nil, errors.New( + "cannot start UD Manager when network follower is not running.") + } + + registered := uint32(0) + + m := &Manager{ + client: client, + comms: client.GetComms(), + rng: client.GetRng(), + sw: client.GetSwitchboard(), + storage: client.GetStorage(), + net: client.GetNetworkInterface(), + single: single, + registered: ®istered, + } + + err := m.client.GetStorage().GetUd(). + BackUpMissingFacts(email, phone) + if err != nil { + return nil, errors.WithMessage(err, "Failed to restore UD store "+ + "from backup") + } + + // check that user discovery is available in the NDF + def := m.net.GetInstance().GetPartialNdf().Get() + + if def.UDB.Cert == "" { + return nil, errors.New("NDF does not have User Discovery information, " + + "is there network access?: Cert not present.") + } + + // Create the user discovery host object + hp := connect.GetDefaultHostParams() + // Client will not send KeepAlive packets + hp.KaClientOpts.Time = time.Duration(math.MaxInt64) + hp.MaxRetries = 3 + hp.SendTimeout = 3 * time.Second + hp.AuthEnabled = false + + m.myID = m.storage.User().GetCryptographicIdentity().GetReceptionID() + + // Get the commonly used data from storage + m.privKey = m.storage.GetUser().ReceptionRSA + + // Set as registered. Since it's from a backup, + // the client is already registered + if err = m.setRegistered(); err != nil { + return nil, errors.WithMessage(err, "failed to set client as "+ + "registered with user discovery.") + } + + // Store the pointer to the group locally for easy access + m.grp = m.storage.E2e().GetGroup() + + return m, nil +} + func LoadManager(services cmix.Client, e2e e2e.Handler, events event.Manager, comms Comms, userStore Userinfo, rng *fastRNG.StreamGenerator, privKey *rsa.PrivateKey, kv *versioned.KV) (*Manager, error) { @@ -234,18 +300,6 @@ func (m *Manager) UnsetAlternativeUserDiscovery() error { return nil } -// BackUpMissingFacts adds a registered fact to the Store object. -// It can take in both an email and a phone number. One or the other may be nil, -// however both is considered an error. It checks for the proper fact type for -// the associated fact. Any other fact.FactType is not accepted and returns an -// error and nothing is backed up. If you attempt to back up a fact type that h -// as already been backed up, an error will be returned and nothing will be -// backed up. Otherwise, it adds the fact and returns whether the Store saved -// successfully. -func (m *Manager) BackUpMissingFacts(email, phone fact.Fact) error { - return m.store.BackUpMissingFacts(email, phone) -} - // GetFacts returns a list of fact.Fact objects that exist within the // Store's registeredFacts map. func (m *Manager) GetFacts() []fact.Fact { diff --git a/ud/store/facts.go b/ud/store/facts.go index 9762bc068b915c4d5138226bf827157911508332..127e09d2bcf6287800f53e8c6496ff7e59114f0a 100644 --- a/ud/store/facts.go +++ b/ud/store/facts.go @@ -21,6 +21,7 @@ const ( "%s (%s) is non-empty but not an email. Cancelling backup operation" backupMissingAllZeroesFactErr = "Cannot backup missing facts: Both email and phone facts are empty!" factNotInStoreErr = "Fact %v does not exist in store" + statefulStoreErr = "cannot overwrite ud store with existing data" ) // Store is the storage object for the higher level ud.Manager object. @@ -48,6 +49,26 @@ func NewStore(kv *versioned.KV) (*Store, error) { return s, s.save() } +// RestoreFromBackUp initializes the confirmedFacts map +// with the backed up fact data. This will error if +// the store is already stateful. +func (s *Store) RestoreFromBackUp(backupData fact.FactList) error { + s.mux.Lock() + defer s.mux.Unlock() + + if len(s.confirmedFacts) != 0 || len(s.unconfirmedFacts) != 0 { + return errors.New(statefulStoreErr) + } + + for _, f := range backupData { + if !isFactZero(f) { + s.confirmedFacts[f] = struct{}{} + } + } + + return s.save() +} + // StoreUnconfirmedFact stores a fact that has been added to UD but has not been // confirmed by the user. It is keyed on the confirmation ID given by UD. func (s *Store) StoreUnconfirmedFact(confirmationId string, f fact.Fact) error { @@ -94,10 +115,6 @@ func (s *Store) BackUpMissingFacts(email, phone fact.Fact) error { s.mux.Lock() defer s.mux.Unlock() - if isFactZero(email) && isFactZero(phone) { - return errors.New(backupMissingAllZeroesFactErr) - } - modifiedEmail, modifiedPhone := false, false // Handle email if it is not zero (empty string) diff --git a/ud/store/facts_test.go b/ud/store/facts_test.go index c1f5dfc5c965d8b1bece3ab7fa3b903dc21e2b33..c4bb5ea779b2c9cf65e2b63bb520cc1a901afa57 100644 --- a/ud/store/facts_test.go +++ b/ud/store/facts_test.go @@ -27,6 +27,66 @@ func TestNewStore(t *testing.T) { } +// Unit test +func TestStore_RestoreFromBackUp(t *testing.T) { + + kv := versioned.NewKV(make(ekv.Memstore)) + + s, err := NewStore(kv) + if err != nil { + t.Errorf("NewStore() produced an error: %v", err) + } + + expected := fact.Fact{ + Fact: "josh", + T: fact.Username, + } + + fl := fact.FactList{expected} + + err = s.RestoreFromBackUp(fl) + if err != nil { + t.Fatalf("RestoreFromBackup err: %v", err) + } + + _, exists := s.confirmedFacts[expected] + if !exists { + t.Fatalf("Fact %s does not exist in map", expected) + } + +} + +// Error case. +func TestStore_RestoreFromBackUp_StatefulStore(t *testing.T) { + + kv := versioned.NewKV(make(ekv.Memstore)) + + s, err := NewStore(kv) + if err != nil { + t.Errorf("NewStore() produced an error: %v", err) + } + + confirmId := "confirm" + expected := fact.Fact{ + Fact: "josh", + T: fact.Username, + } + + err = s.StoreUnconfirmedFact(confirmId, expected) + if err != nil { + t.Fatalf("StoreUnconfirmedFact error: %v", err) + } + + // Expected error: should error when restoring on + // a stateful store. + fl := fact.FactList{expected} + err = s.RestoreFromBackUp(fl) + if err == nil { + t.Fatalf("RestoreFromBackup err: %v", err) + } + +} + func TestStore_ConfirmFact(t *testing.T) { kv := versioned.NewKV(make(ekv.Memstore))