diff --git a/bindings/backup.go b/bindings/backup.go new file mode 100644 index 0000000000000000000000000000000000000000..05da31668f2155cdb610a074c9d4ba1eea7d991c --- /dev/null +++ b/bindings/backup.go @@ -0,0 +1,113 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright © 2020 xx network SEZC // +// // +// Use of this source code is governed by a license that can be found in the // +// LICENSE file // +/////////////////////////////////////////////////////////////////////////////// + +package bindings + +import ( + "gitlab.com/elixxir/client/backup" +) + +// Backup is a bindings-level struct encapsulating the backup.Backup +// client object. +type Backup struct { + b *backup.Backup +} + +// UpdateBackupFunc contains a function callback that returns new backups. +type UpdateBackupFunc interface { + UpdateBackup(encryptedBackup []byte) +} + +// InitializeBackup creates a bindings-layer Backup object. +// +// Params +// - e2eID - ID of the E2e object in the e2e tracker. +// - udID - ID of the UserDiscovery object in the ud tracker. +// - password - password used in LoadCmix. +// - cb - the callback to be called when a backup is triggered. +func InitializeBackup(e2eID, udID int, password string, + cb UpdateBackupFunc) (*Backup, error) { + // Retrieve the user from the tracker + user, err := e2eTrackerSingleton.get(e2eID) + if err != nil { + return nil, err + } + + // Retrieve the UD manager + ud, err := udTrackerSingleton.get(udID) + if err != nil { + return nil, err + } + + // Initialize backup + b, err := backup.InitializeBackup(password, cb.UpdateBackup, + user.api.GetBackupContainer(), user.api.GetE2E(), + user.api.GetStorage(), ud.api, + user.api.GetStorage().GetKV(), user.api.GetRng()) + if err != nil { + return nil, err + } + + return &Backup{b: b}, nil +} + +// ResumeBackup resumes the backup processes with a new callback. +// Call this function only when resuming a backup that has already been +// initialized or to replace the callback. +// To start the backup for the first time or to use a new password, use +// InitializeBackup. +// +// Params +// - e2eID - ID of the E2e object in the e2e tracker. +// - udID - ID of the UserDiscovery object in the ud tracker. +// - cb - the callback to be called when a backup is triggered. +// This will replace any callback that has been passed into InitializeBackup. +func ResumeBackup(e2eID, udID int, cb UpdateBackupFunc) ( + *Backup, error) { + + // Retrieve the user from the tracker + user, err := e2eTrackerSingleton.get(e2eID) + if err != nil { + return nil, err + } + + // Retrieve the UD manager + ud, err := udTrackerSingleton.get(udID) + if err != nil { + return nil, err + } + + // Resume backup + b, err := backup.ResumeBackup(cb.UpdateBackup, user.api.GetBackupContainer(), + user.api.GetE2E(), user.api.GetStorage(), ud.api, + user.api.GetStorage().GetKV(), user.api.GetRng()) + if err != nil { + return nil, err + } + + return &Backup{b}, nil +} + +// StopBackup stops the backup processes and deletes the user's password from +// storage. To enable backups again, call InitializeBackup. +func (b *Backup) StopBackup() error { + return b.b.StopBackup() +} + +// IsBackupRunning returns true if the backup has been initialized and is +// running. Returns false if it has been stopped. +func (b *Backup) IsBackupRunning() bool { + return b.b.IsBackupRunning() +} + +// AddJson stores the argument within the Backup structure. +// +// Params +// - json - JSON string +func (b *Backup) AddJson(json string) { + b.b.AddJson(json) +} diff --git a/bindings/ud.go b/bindings/ud.go new file mode 100644 index 0000000000000000000000000000000000000000..8c857233ceca9b660e24b43e93c52cbc1ff5bd83 --- /dev/null +++ b/bindings/ud.go @@ -0,0 +1,269 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright © 2020 xx network SEZC // +// // +// Use of this source code is governed by a license that can be found in the // +// LICENSE file // +//////////////////////////////////////////////////////////////////////////////// + +package bindings + +import ( + "encoding/json" + "github.com/pkg/errors" + jww "github.com/spf13/jwalterweatherman" + "gitlab.com/elixxir/client/ud" + "gitlab.com/elixxir/client/xxdk" + "gitlab.com/elixxir/primitives/fact" + "sync" +) + +//////////////////////////////////////////////////////////////////////////////// +// Singleton Tracker // +//////////////////////////////////////////////////////////////////////////////// + +// udTrackerSingleton is used to track UserDiscovery objects so that they can be +// referenced by ID back over the bindings. +var udTrackerSingleton = &udTracker{ + tracked: make(map[int]*UserDiscovery), + count: 0, +} + +// udTracker is a singleton used to keep track of extant UserDiscovery objects, +// preventing race conditions created by passing it over the bindings. +type udTracker struct { + tracked map[int]*UserDiscovery + count int + mux sync.RWMutex +} + +// make create a UserDiscovery from an ud.Manager, assigns it a unique ID, and +// adds it to the udTracker. +func (ut *udTracker) make(u *ud.Manager) *UserDiscovery { + ut.mux.Lock() + defer ut.mux.Unlock() + + id := ut.count + ut.count++ + + ut.tracked[id] = &UserDiscovery{ + api: u, + id: id, + } + + return ut.tracked[id] +} + +// get an UserDiscovery from the udTracker given its ID. +func (ut *udTracker) get(id int) (*UserDiscovery, error) { + ut.mux.RLock() + defer ut.mux.RUnlock() + + c, exist := ut.tracked[id] + if !exist { + return nil, errors.Errorf( + "Cannot get UserDiscovery for ID %d, does not exist", id) + } + + return c, nil +} + +// delete removes a UserDiscovery from the udTracker. +func (ut *udTracker) delete(id int) { + ut.mux.Lock() + defer ut.mux.Unlock() + + delete(ut.tracked, id) +} + +//////////////////////////////////////////////////////////////////////////////// +// Structs and Interfaces // +//////////////////////////////////////////////////////////////////////////////// + +// UserDiscovery is a bindings-layer struct that wraps an ud.Manager interface. +type UserDiscovery struct { + api *ud.Manager + id int +} + +// GetID returns the udTracker ID for the UserDiscovery object. +func (ud *UserDiscovery) GetID() int { + return ud.id +} + +// UdNetworkStatus contains the UdNetworkStatus, which is a bindings-level +// interface for ud.udNetworkStatus. +type UdNetworkStatus interface { + // UdNetworkStatus returns: + // - int - a xxdk.Status int + UdNetworkStatus() int +} + +//////////////////////////////////////////////////////////////////////////////// +// Main functions // +//////////////////////////////////////////////////////////////////////////////// + +// LoadOrNewUserDiscovery creates a bindings-level user discovery manager. +// +// Parameters: +// - e2eID - e2e object ID in the tracker +// - follower - network follower func wrapped in UdNetworkStatus +func LoadOrNewUserDiscovery(e2eID int, follower UdNetworkStatus, + username string, registrationValidationSignature []byte) ( + *UserDiscovery, error) { + + // Get user from singleton + user, err := e2eTrackerSingleton.get(e2eID) + if err != nil { + return nil, err + } + + UdNetworkStatusFn := func() xxdk.Status { + return xxdk.Status(follower.UdNetworkStatus()) + } + + u, err := ud.LoadOrNewManager(user.api, user.api.GetComms(), + UdNetworkStatusFn, username, registrationValidationSignature) + if err != nil { + return nil, err + } + + return udTrackerSingleton.make(u), nil +} + +// NewUdManagerFromBackup 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. +// +// Parameters: +// - e2eID - e2e object ID in the tracker +// - follower - network follower func wrapped in UdNetworkStatus +// - emailFactJson - a JSON marshalled email fact.Fact +// - phoneFactJson - a JSON marshalled phone fact.Fact +func NewUdManagerFromBackup(e2eID int, follower UdNetworkStatus, emailFactJson, + phoneFactJson []byte) (*UserDiscovery, error) { + + // Get user from singleton + user, err := e2eTrackerSingleton.get(e2eID) + if err != nil { + return nil, err + } + + var email, phone fact.Fact + err = json.Unmarshal(emailFactJson, &email) + if err != nil { + return nil, err + } + + err = json.Unmarshal(phoneFactJson, &phone) + if err != nil { + return nil, err + } + + UdNetworkStatusFn := func() xxdk.Status { + return xxdk.Status(follower.UdNetworkStatus()) + } + + u, err := ud.NewManagerFromBackup( + user.api, user.api.GetComms(), UdNetworkStatusFn, email, phone) + if err != nil { + return nil, err + } + + return udTrackerSingleton.make(u), nil +} + +// GetFacts returns a JSON marshalled list of fact.Fact objects that exist +// within the Store's registeredFacts map. +func (ud *UserDiscovery) GetFacts() []byte { + jsonData, err := json.Marshal(ud.api.GetFacts()) + if err != nil { + jww.FATAL.Panicf("Failed to JSON marshal fact list: %+v", err) + } + return jsonData +} + +// GetContact returns the marshalled bytes of the contact.Contact for UD as +// retrieved from the NDF. +func (ud *UserDiscovery) GetContact() ([]byte, error) { + c, err := ud.api.GetContact() + if err != nil { + return nil, err + } + + return json.Marshal(c) +} + +// ConfirmFact confirms a fact first registered via AddFact. The confirmation ID +// comes from AddFact while the code will come over the associated +// communications system. +func (ud *UserDiscovery) ConfirmFact(confirmationID, code string) error { + return ud.api.ConfirmFact(confirmationID, code) +} + +// SendRegisterFact adds a fact for the user to user discovery. Will only +// succeed if the user is already registered and the system does not have the +// fact currently registered for any user. +// +// This does not complete the fact registration process, it returns a +// confirmation ID instead. Over the communications system the fact is +// associated with, a code will be sent. This confirmation ID needs to be called +// along with the code to finalize the fact. +// +// Parameters: +// - factJson - a JSON marshalled fact.Fact +func (ud *UserDiscovery) SendRegisterFact(factJson []byte) (string, error) { + var f fact.Fact + err := json.Unmarshal(factJson, &f) + if err != nil { + return "", err + } + + return ud.api.SendRegisterFact(f) +} + +// PermanentDeleteAccount removes the username associated with this user from +// the UD service. This will only take a username type fact, and the fact must +// be associated with this user. +// +// Parameters: +// - factJson - a JSON marshalled fact.Fact +func (ud *UserDiscovery) PermanentDeleteAccount(factJson []byte) error { + var f fact.Fact + err := json.Unmarshal(factJson, &f) + if err != nil { + return err + } + + return ud.api.PermanentDeleteAccount(f) +} + +// RemoveFact removes a previously confirmed fact. This will fail if the fact +// passed in is not UD service does not associate this fact with this user. +// +// Parameters: +// - factJson - a JSON marshalled fact.Fact +func (ud *UserDiscovery) RemoveFact(factJson []byte) error { + var f fact.Fact + err := json.Unmarshal(factJson, &f) + if err != nil { + return err + } + + return ud.api.RemoveFact(f) +} + +// SetAlternativeUserDiscovery sets the alternativeUd object within manager. +// Once set, any user discovery operation will go through the alternative +// user discovery service. +// +// To undo this operation, use UnsetAlternativeUserDiscovery. +func (ud *UserDiscovery) SetAlternativeUserDiscovery( + altCert, altAddress, contactFile []byte) error { + return ud.api.SetAlternativeUserDiscovery(altCert, altAddress, contactFile) +} + +// UnsetAlternativeUserDiscovery clears out the information from the Manager +// object. +func (ud *UserDiscovery) UnsetAlternativeUserDiscovery() error { + return ud.api.UnsetAlternativeUserDiscovery() +} diff --git a/cmd/ud.go b/cmd/ud.go index 22c4c918727be7491dc2d2d9ab43aeffb62281a8..10d423889ff8c1dcc2a363ad821af820db76beaa 100644 --- a/cmd/ud.go +++ b/cmd/ud.go @@ -10,7 +10,6 @@ package cmd import ( "fmt" - "strings" "time" "gitlab.com/elixxir/client/single" @@ -62,21 +61,12 @@ var udCmd = &cobra.Command{ jww.TRACE.Printf("[UD] Connected!") // Make user discovery manager - rng := user.GetRng() userToRegister := viper.GetString(udRegisterFlag) jww.TRACE.Printf("[UD] Registering identity %v...", userToRegister) - userDiscoveryMgr, err := ud.NewManager(user, user.GetComms(), + userDiscoveryMgr, err := ud.LoadOrNewManager(user, user.GetComms(), user.NetworkFollowerStatus, userToRegister, nil) if err != nil { - if strings.Contains(err.Error(), ud.IsRegisteredErr) { - userDiscoveryMgr, err = ud.LoadManager(user, user.GetComms()) - if err != nil { - jww.FATAL.Panicf("Failed to load UD manager: %+v", err) - } - } else { - jww.FATAL.Panicf("Failed to create new UD manager: %+v", err) - - } + jww.FATAL.Panicf("Failed to load or create new UD manager: %+v", err) } jww.INFO.Printf("[UD] Registered user %v", userToRegister) @@ -145,13 +135,11 @@ var udCmd = &cobra.Command{ printContact(newContact) } - stream := rng.GetStream() _, _, err = ud.Lookup(user, udContact, cb, lookupID, single.GetDefaultRequestParams()) if err != nil { jww.WARN.Printf("Failed UD lookup: %+v", err) } - stream.Close() time.Sleep(31 * time.Second) } diff --git a/ud/addFact.go b/ud/addFact.go index 20d33bc5fdc588f32f4a73bb3e786aecaabfab99..c107636c365bfeedd449e67c9ebab426365551fd 100644 --- a/ud/addFact.go +++ b/ud/addFact.go @@ -14,10 +14,11 @@ import ( // SendRegisterFact adds a fact for the user to user discovery. Will only // succeed if the user is already registered and the system does not have the // fact currently registered for any user. +// // This does not complete the fact registration process, it returns a -// confirmation id instead. Over the communications system the fact is -// associated with, a code will be sent. This confirmation ID needs to be -// called along with the code to finalize the fact. +// confirmation ID instead. Over the communications system the fact is +// associated with, a code will be sent. This confirmation ID needs to be called +// along with the code to finalize the fact. func (m *Manager) SendRegisterFact(f fact.Fact) (string, error) { jww.INFO.Printf("ud.SendRegisterFact(%s)", f.Stringify()) m.factMux.Lock() diff --git a/ud/alternate.go b/ud/alternate.go index 33720102924ce123b7cb423093e994f25a9ec99f..f53281c43470d44ed24b40f65a02c143c8d785e3 100644 --- a/ud/alternate.go +++ b/ud/alternate.go @@ -19,6 +19,7 @@ type alternateUd struct { // SetAlternativeUserDiscovery sets the alternativeUd object within manager. // Once set, any user discovery operation will go through the alternative // user discovery service. +// // To undo this operation, use UnsetAlternativeUserDiscovery. func (m *Manager) SetAlternativeUserDiscovery(altCert, altAddress, contactFile []byte) error { diff --git a/ud/manager.go b/ud/manager.go index 6c5817abcd05d9c27f935a1847539bc067339b34..845d2b800d86170b19a1954a1530bff639617d52 100644 --- a/ud/manager.go +++ b/ud/manager.go @@ -18,12 +18,6 @@ import ( "gitlab.com/xx_network/primitives/id" ) -const ( - IsRegisteredErr = "NewManager is already registered. " + - "NewManager is meant for the first instantiation. Use LoadManager " + - "for all other calls" -) - // Manager is the control structure for the contacting the user discovery service. type Manager struct { @@ -48,19 +42,22 @@ type Manager struct { // alternativeUd is an alternate User discovery service to circumvent // production. This is for testing with a separately deployed UD service. alternativeUd *alternateUd - - // registrationValidationSignature for the ReceptionID - // Optional, depending on UD configuration - registrationValidationSignature []byte } -// NewManager builds a new user discovery manager. -// It requires that an updated -// NDF is available and will error if one is not. -// registrationValidationSignature may be set to nil -func NewManager(user udE2e, comms Comms, follower udNetworkStatus, - username string, registrationValidationSignature []byte) (*Manager, error) { - jww.INFO.Println("ud.NewManager()") +// LoadOrNewManager loads an existing Manager from storage or creates a +// new one if there is no extant storage information. +// +// Params +// - user is an interface that adheres to the xxdk.E2e object. +// - comms is an interface that adheres to client.Comms object. +// - follower is a method off of xxdk.Cmix which returns the network follower's status. +// - username is the name of the user as it is registered with UD. This will be what the end user +// provides if through the bindings. +// - networkValidationSig is a signature provided by the network (i.e. the client registrar). This may +// be nil, however UD may return an error in some cases (e.g. in a production level environment). +func LoadOrNewManager(user udE2e, comms Comms, follower udNetworkStatus, + username string, networkValidationSig []byte) (*Manager, error) { + jww.INFO.Println("ud.LoadOrNewManager()") if follower() != xxdk.Running { return nil, errors.New( @@ -69,13 +66,18 @@ func NewManager(user udE2e, comms Comms, follower udNetworkStatus, // Initialize manager m := &Manager{ - user: user, - comms: comms, - registrationValidationSignature: registrationValidationSignature, + user: user, + comms: comms, } if m.isRegistered() { - return nil, errors.Errorf(IsRegisteredErr) + // Load manager if already registered + var err error + m.store, err = store.NewOrLoadStore(m.getKv()) + if err != nil { + return nil, errors.Errorf("Failed to initialize store: %v", err) + } + return m, nil } // Initialize store @@ -95,7 +97,7 @@ func NewManager(user udE2e, comms Comms, follower udNetworkStatus, // Register with user discovery stream := m.getRng().GetStream() defer stream.Close() - err = m.register(username, stream, m.comms, udHost) + err = m.register(username, networkValidationSig, stream, m.comms, udHost) if err != nil { return nil, errors.Errorf("Failed to register: %v", err) } @@ -159,7 +161,7 @@ func NewManagerFromBackup(user udE2e, comms Comms, follower udNetworkStatus, return m, nil } -// InitStoreFromBackup initializes the UD storage from the backup subsystem +// InitStoreFromBackup initializes the UD storage from the backup subsystem. func InitStoreFromBackup(kv *versioned.KV, username, email, phone fact.Fact) error { // Initialize our store @@ -185,32 +187,9 @@ func InitStoreFromBackup(kv *versioned.KV, return nil } -// LoadManager loads the state of the Manager -// from disk. This is meant to be called after any the first -// instantiation of the manager by NewUserDiscovery. -func LoadManager(user udE2e, comms Comms) (*Manager, error) { - m := &Manager{ - user: user, - comms: comms, - } - - if !m.isRegistered() { - return nil, errors.Errorf("LoadManager could not detect that " + - "the user has been registered. Has a manager been initiated before?") - } - - var err error - m.store, err = store.NewOrLoadStore(m.getKv()) - if err != nil { - return nil, errors.Errorf("Failed to initialize store: %v", err) - } - - return m, err -} - // GetFacts returns a list of fact.Fact objects that exist within the // Store's registeredFacts map. -func (m *Manager) GetFacts() []fact.Fact { +func (m *Manager) GetFacts() fact.FactList { return m.store.GetFacts() } @@ -312,9 +291,9 @@ func (m *Manager) getOrAddUdHost() (*connect.Host, error) { return host, nil } -///////////////////////////////////////////////////////////////////////////////////////// -// Internal getters ///////////////////////////////////////////////////////////////////// -///////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// Internal Getters // +//////////////////////////////////////////////////////////////////////////////// // getCmix retrieve a sub-interface of cmix.Client. // It allows the Manager to retrieve network state. diff --git a/ud/register.go b/ud/register.go index 66aa812b13f5f057113c5aa619c207e8a3556b89..f41532bd5605ae7d45ddf90baea152293c489d3e 100644 --- a/ud/register.go +++ b/ud/register.go @@ -14,8 +14,8 @@ import ( // register initiates registration with user discovery given a specified // username. Provided a comms sub-interface to facilitate testing. -func (m *Manager) register(username string, rng csprng.Source, - comm registerUserComms, udHost *connect.Host) error { +func (m *Manager) register(username string, networkSignature []byte, + rng csprng.Source, comm registerUserComms, udHost *connect.Host) error { var err error identity := m.user.GetReceptionIdentity() @@ -35,7 +35,7 @@ func (m *Manager) register(username string, rng csprng.Source, // Construct the user registration message msg := &pb.UDBUserRegistration{ - PermissioningSignature: m.registrationValidationSignature, + PermissioningSignature: networkSignature, RSAPublicPem: string(rsa.CreatePublicKeyPem(privKey.GetPublic())), IdentityRegistration: &pb.Identity{ Username: username, diff --git a/ud/register_test.go b/ud/register_test.go index a5bd09bcb5f5bca1386547c1cb8959edc7a3a2d1..3a521042c31d8703e752efd439a1456ce8b789af 100644 --- a/ud/register_test.go +++ b/ud/register_test.go @@ -34,13 +34,15 @@ func TestManager_register(t *testing.T) { c := &testRegisterComm{} prng := NewPrng(42) - err = m.register("testUser", prng, c, udHost) + mockSig := []byte("mock") + + err = m.register("testUser", mockSig, prng, c, udHost) if err != nil { t.Errorf("register() returned an error: %+v", err) } // Check if the UDBUserRegistration contents are correct - isCorrect("testUser", c.msg, m, t) + isCorrect("testUser", mockSig, c.msg, m, t) // Verify the signed identity data pubKeyPem := m.user.GetReceptionIdentity().RSAPrivatePem @@ -66,10 +68,10 @@ func TestManager_register(t *testing.T) { // isCorrect checks if the UDBUserRegistration has all the expected fields minus // any signatures. -func isCorrect(username string, msg *pb.UDBUserRegistration, m *Manager, t *testing.T) { - if !bytes.Equal(m.registrationValidationSignature, msg.PermissioningSignature) { +func isCorrect(username string, mockSig []byte, msg *pb.UDBUserRegistration, m *Manager, t *testing.T) { + if !bytes.Equal(mockSig, msg.PermissioningSignature) { t.Errorf("PermissioningSignature incorrect.\n\texpected: %v\n\treceived: %v", - m.registrationValidationSignature, msg.PermissioningSignature) + mockSig, msg.PermissioningSignature) } identity := m.user.GetReceptionIdentity()