From 84e31f02fe7cd4110d9ec3573eee9dabe30fab37 Mon Sep 17 00:00:00 2001 From: Dan Ballard Date: Mon, 21 Jan 2019 12:11:40 -0800 Subject: [PATCH] new profile storeage on eventbus --- app/app.go | 37 ++++++++-------- app/cli/main.go | 9 ---- app/cwtchutil/main.go | 80 +++++++--------------------------- event/common.go | 2 + model/profile.go | 31 ++++++------- model/profile_test.go | 4 +- server/server.go | 2 +- server/server_instance.go | 2 +- server/server_instance_test.go | 2 +- storage/file_store.go | 1 + storage/file_store_test.go | 22 ---------- storage/profile_store.go | 69 ++++++++++++++++++++++++----- 12 files changed, 112 insertions(+), 149 deletions(-) delete mode 100644 storage/file_store_test.go diff --git a/app/app.go b/app/app.go index 7167051..8287167 100644 --- a/app/app.go +++ b/app/app.go @@ -29,7 +29,6 @@ type application struct { // Application is a full cwtch peer application. It allows management, usage and storage of multiple peers type Application interface { - SaveProfile(cwtchPeer peer.CwtchPeer) LoadProfiles(password string) error CreatePeer(name string, password string) (peer.CwtchPeer, error) @@ -59,21 +58,16 @@ func generateRandomFilename() string { return filepath.Join(hex.EncodeToString(randBytes)) } -func (app *application) SaveProfile(p peer.CwtchPeer) { - app.mutex.Lock() - defer app.mutex.Unlock() - app.peers[p.GetProfile().Onion] = p - app.storage[p.GetProfile().Onion].Save(p) -} - // NewProfile creates a new cwtchPeer with a given name. func (app *application) CreatePeer(name string, password string) (peer.CwtchPeer, error) { log.Debugf("CreatePeer(%v)\n", name) randomFileName := generateRandomFilename() - fileStore := storage.NewFileStore(path.Join(app.directory, "profiles", randomFileName), password) + // TODO: eventBus per profile + profileStore, err := storage.NewProfileStore(app.eventBus, path.Join(app.directory, "profiles", randomFileName), password) + profileStore.Init(name) p := peer.NewCwtchPeer(name) - err := fileStore.Save(p) + app.eventBus.Publish(event.NewEvent(event.SetProfileName, map[string]string{"Name": name})) if err != nil { return nil, err } @@ -85,7 +79,7 @@ func (app *application) CreatePeer(name string, password string) (peer.CwtchPeer } app.mutex.Lock() app.peers[p.GetProfile().Onion] = p - app.storage[p.GetProfile().Onion] = fileStore + app.storage[p.GetProfile().Onion] = profileStore app.mutex.Unlock() return p, nil @@ -99,27 +93,30 @@ func (app *application) LoadProfiles(password string) error { for _, file := range files { - fileStore := storage.NewFileStore(path.Join(app.directory, "profiles", file.Name()), password) + // TODO: Per profile eventBus + profileStore, err := storage.NewProfileStore(app.eventBus, path.Join(app.directory, "profiles", file.Name()), password) - p, err := fileStore.Load() + err = profileStore.Load() if err != nil { continue } - _, exists := app.peers[p.GetProfile().Onion] + profile := profileStore.GetProfileCopy() + _, exists := app.peers[profile.Onion] if exists { - p.Shutdown() - log.Errorf("profile for onion %v already exists", p.GetProfile().Onion) + profileStore.Shutdown() + log.Errorf("profile for onion %v already exists", profile.Onion) continue } - p.Init(app.acn, app.eventBus) + peer := peer.FromProfile(profile) + peer.Init(app.acn, app.eventBus) app.mutex.Lock() - app.peers[p.GetProfile().Onion] = p - app.storage[p.GetProfile().Onion] = fileStore + app.peers[profile.Onion] = peer + app.storage[profile.Onion] = profileStore if app.primaryonion == "" { - app.primaryonion = p.GetProfile().Onion + app.primaryonion = profile.Onion } app.mutex.Unlock() } diff --git a/app/cli/main.go b/app/cli/main.go index 14df948..29294c9 100644 --- a/app/cli/main.go +++ b/app/cli/main.go @@ -548,8 +548,6 @@ func main() { fmt.Printf("%v", commands) fmt.Printf("Error importing group, usage: %s\n", usages[commands[0]]) } - case "/save": - app.SaveProfile(peer) case "/help": for _, command := range suggestions { fmt.Printf("%-18s%-56s%s\n", command.Text, command.Description, usages[command.Text]) @@ -599,13 +597,6 @@ func main() { } } } - if peer != nil { - app.SaveProfile(peer) - } - } - - if peer != nil { - app.SaveProfile(peer) } app.Shutdown() diff --git a/app/cwtchutil/main.go b/app/cwtchutil/main.go index 3c895c2..f4aab6f 100644 --- a/app/cwtchutil/main.go +++ b/app/cwtchutil/main.go @@ -1,54 +1,14 @@ package main import ( - "bufio" - "crypto/rand" - libpeer "cwtch.im/cwtch/peer" - storage2 "cwtch.im/cwtch/storage" - "fmt" - "git.openprivacy.ca/openprivacy/libricochet-go/utils" - "errors" + "fmt" "git.openprivacy.ca/openprivacy/libricochet-go/log" - "golang.org/x/crypto/ed25519" - "io/ioutil" "os" - "strconv" - "strings" - "time" + //"bufio" + //"cwtch.im/cwtch/storage" ) -func convertCwtchFile(filename string, password string) error { - fileStore := storage2.NewFileStore(filename, password) - peer, err := fileStore.Load() - if err != nil { - return err - } - - b := []byte("== ed25519v1-secret: type0 ==") - b = append(b, peer.GetProfile().Ed25519PrivateKey...) - err = ioutil.WriteFile("hs_ed25519_secret_key", b, 0600) - if err != nil { - return err - } - - b = []byte("== ed25519v1-public: type0 ==") - b = append(b, peer.GetProfile().Ed25519PublicKey...) - err = ioutil.WriteFile("hs_ed25519_public_key", b, 0600) - if err != nil { - return err - } - - b = []byte(peer.GetProfile().Onion + ".onion\n") - err = ioutil.WriteFile("hostname", b, 0600) - if err != nil { - return err - } - - log.Infoln("success!") - return nil -} - func convertTorFile(filename string, password string) error { return errors.New("this code doesn't work and can never work :( it's a math thing") @@ -87,6 +47,7 @@ func convertTorFile(filename string, password string) error { return nil*/ } +/* func vanity() error { for { pk, sk, err := ed25519.GenerateKey(rand.Reader) @@ -100,16 +61,14 @@ func vanity() error { peer.GetProfile().Ed25519PrivateKey = sk peer.GetProfile().Ed25519PublicKey = pk peer.GetProfile().Onion = onion - fileStore := storage2.NewFileStore(os.Args[3], onion+".cwtch") - err := fileStore.Save(peer) - if err != nil { - return err - } + profileStore, _ := storage2.NewProfileStore(nil, os.Args[3], onion+".cwtch") + profileStore.Init("") + // need to signal new onion? impossible log.Infof("found %s.onion\n", onion) } } } -} +}*/ func printHelp() { log.Infoln("usage: cwtchutil {help, convert-cwtch-file, convert-tor-file, changepw, vanity}") @@ -127,15 +86,6 @@ func main() { printHelp() case "help": printHelp() - case "convert-cwtch-file": - if len(os.Args) != 4 { - fmt.Println("example: cwtchutil convert-cwtch-file ~/.cwtch/profiles/11ddd78a9918c064e742d5e36a8b8fd4 passw0rd") - os.Exit(1) - } - err := convertCwtchFile(os.Args[2], os.Args[3]) - if err != nil { - log.Errorln(err) - } case "convert-tor-file": if len(os.Args) != 4 { fmt.Println("example: cwtchutil convert-tor-file /var/lib/tor/hs1 passw0rd") @@ -145,7 +95,7 @@ func main() { if err != nil { log.Errorln(err) } - case "vanity": + /*case "vanity": if len(os.Args) < 5 { fmt.Println("example: cwtchutil vanity 4 passw0rd erinn openpriv") os.Exit(1) @@ -163,8 +113,8 @@ func main() { for { // run until ctrl+c time.Sleep(time.Hour * 24) - } - case "changepw": + }*/ + /*case "changepw": if len(os.Args) != 3 { fmt.Println("example: cwtch changepw ~/.cwtch/profiles/XXX") os.Exit(1) @@ -179,9 +129,9 @@ func main() { } pw = pw[:len(pw)-1] - fileStore := storage2.NewFileStore(os.Args[2], pw) + profileStore, _ := storage.NewProfileStore(nil, os.Args[2], pw) - peer, err := fileStore.Load() + err = profileStore.Load() if err != nil { log.Errorln(err) os.Exit(1) @@ -195,7 +145,8 @@ func main() { } newpw1 = newpw1[:len(newpw1)-1] // fuck go with this linebreak shit ^ea - fileStore2 := storage2.NewFileStore(os.Args[2], newpw1) + fileStore2, _ := storage.NewProfileStore(nil, os.Args[2], newpw1) + // No way to copy, populate this method err = fileStore2.Save(peer) if err != nil { log.Errorln(err) @@ -203,5 +154,6 @@ func main() { } log.Infoln("success!") + */ } } diff --git a/event/common.go b/event/common.go index 151c3b2..a787b5c 100644 --- a/event/common.go +++ b/event/common.go @@ -24,4 +24,6 @@ const ( SendMessageToPeer = Type("SendMessageToPeer") NewMessageFromPeer = Type("NewMessageFromPeer") + + SetProfileName = Type("SetProfileName") ) diff --git a/model/profile.go b/model/profile.go index ee67785..b4a5356 100644 --- a/model/profile.go +++ b/model/profile.go @@ -4,6 +4,7 @@ import ( "crypto/rand" "cwtch.im/cwtch/protocol" "encoding/base32" + "encoding/json" "errors" "git.openprivacy.ca/openprivacy/libricochet-go/utils" "github.com/golang/protobuf/proto" @@ -32,8 +33,6 @@ type Profile struct { Contacts map[string]*PublicProfile Ed25519PrivateKey ed25519.PrivateKey Groups map[string]*Group - Custom map[string]string - lock sync.Mutex } // MaxGroupMessageLength is the maximum length of a message posted to a server group. @@ -62,6 +61,7 @@ func (p *PublicProfile) GetAttribute(name string) (value string, exists bool) { // GenerateNewProfile creates a new profile, with new encryption and signing keys, and a profile name. func GenerateNewProfile(name string) *Profile { p := new(Profile) + p.init() p.Name = name pub, priv, _ := ed25519.GenerateKey(rand.Reader) p.Ed25519PublicKey = pub @@ -71,7 +71,6 @@ func GenerateNewProfile(name string) *Profile { p.Contacts = make(map[string]*PublicProfile) p.Contacts[p.Onion] = &p.PublicProfile p.Groups = make(map[string]*Group) - p.Custom = make(map[string]string) return p } @@ -149,21 +148,6 @@ func (p *Profile) GetGroups() []string { return keys } -// SetCustomAttribute allows applications to store arbitrary configuration info at the profile level. -func (p *Profile) SetCustomAttribute(name string, value string) { - p.lock.Lock() - defer p.lock.Unlock() - p.Custom[name] = value -} - -// GetCustomAttribute returns the value of a value set with SetCustomAttribute. If no such value has been set exists is set to false. -func (p *Profile) GetCustomAttribute(name string) (value string, exists bool) { - p.lock.Lock() - defer p.lock.Unlock() - value, exists = p.Custom[name] - return -} - // GetContacts returns an unordered list of contact onions associated with this profile. func (p *Profile) GetContacts() []string { p.lock.Lock() @@ -370,3 +354,14 @@ func (p *Profile) EncryptMessageToGroup(message string, groupID string) ([]byte, } return nil, nil, errors.New("group does not exist") } + +// GetCopy returns a full deep copy of the Profile struct and its members +func (p *Profile) GetCopy() *Profile { + p.lock.Lock() + defer p.lock.Unlock() + + newp := new(Profile) + bytes, _ := json.Marshal(p) + json.Unmarshal(bytes, &newp) + return newp +} diff --git a/model/profile_test.go b/model/profile_test.go index e67cdc3..0560ae4 100644 --- a/model/profile_test.go +++ b/model/profile_test.go @@ -50,8 +50,8 @@ func TestProfileIdentity(t *testing.T) { t.Errorf("alice should be only contact: %v", alice.GetContacts()) } - alice.SetCustomAttribute("test", "hello world") - value, _ := alice.GetCustomAttribute("test") + alice.SetAttribute("test", "hello world") + value, _ := alice.GetAttribute("test") if value != "hello world" { t.Errorf("value from custom attribute should have been 'hello world', instead was: %v", value) } diff --git a/server/server.go b/server/server.go index 72cebad..3f20792 100644 --- a/server/server.go +++ b/server/server.go @@ -5,7 +5,7 @@ import ( "cwtch.im/cwtch/server/listen" "cwtch.im/cwtch/server/metrics" "cwtch.im/cwtch/server/send" - "cwtch.im/cwtch/storage" + "cwtch.im/cwtch/server/storage" "git.openprivacy.ca/openprivacy/libricochet-go/application" "git.openprivacy.ca/openprivacy/libricochet-go/channels" "git.openprivacy.ca/openprivacy/libricochet-go/connectivity" diff --git a/server/server_instance.go b/server/server_instance.go index 851668c..2d587d5 100644 --- a/server/server_instance.go +++ b/server/server_instance.go @@ -3,7 +3,7 @@ package server import ( "cwtch.im/cwtch/protocol" "cwtch.im/cwtch/server/listen" - "cwtch.im/cwtch/storage" + "cwtch.im/cwtch/server/storage" "git.openprivacy.ca/openprivacy/libricochet-go/application" "git.openprivacy.ca/openprivacy/libricochet-go/channels" ) diff --git a/server/server_instance_test.go b/server/server_instance_test.go index 9be7fa4..4a45702 100644 --- a/server/server_instance_test.go +++ b/server/server_instance_test.go @@ -3,7 +3,7 @@ package server import ( "cwtch.im/cwtch/protocol" "cwtch.im/cwtch/server/metrics" - "cwtch.im/cwtch/storage" + "cwtch.im/cwtch/server/storage" "git.openprivacy.ca/openprivacy/libricochet-go/application" "os" "testing" diff --git a/storage/file_store.go b/storage/file_store.go index 22fac72..2060112 100644 --- a/storage/file_store.go +++ b/storage/file_store.go @@ -17,6 +17,7 @@ type fileStore struct { password string } +// FileStore is a primitive around storing encrypted files type FileStore interface { Save([]byte) error Load() ([]byte, error) diff --git a/storage/file_store_test.go b/storage/file_store_test.go deleted file mode 100644 index da2a955..0000000 --- a/storage/file_store_test.go +++ /dev/null @@ -1,22 +0,0 @@ -package storage - -import ( - "cwtch.im/cwtch/peer" - "testing" -) - -func TestFileProfileStore(t *testing.T) { - fileStore := NewFileStore(".test.json", "password") - alice := peer.NewCwtchPeer("alice") - fileStore.Save(alice) - - aliceLoaded, err := fileStore.Load() - - if err != nil { - t.Errorf("alice profile should have been loaded from store instead %v", err) - } - - if aliceLoaded.GetProfile().Name != "alice" { - t.Errorf("alice profile should have been loaded from store instead %v", aliceLoaded) - } -} diff --git a/storage/profile_store.go b/storage/profile_store.go index 18c8958..ffbb0c2 100644 --- a/storage/profile_store.go +++ b/storage/profile_store.go @@ -1,39 +1,86 @@ package storage import ( + "cwtch.im/cwtch/event" "cwtch.im/cwtch/model" "encoding/json" ) type profileStore struct { - fs FileStore + fs FileStore + profile *model.Profile + eventManager *event.Manager + queue *event.Queue } // ProfileStore is an interface to managing the storage of Cwtch Profiles type ProfileStore interface { - Save(profile *model.Profile) error - Load() (*model.Profile, error) + Save() error + Init(name string) + Load() error + Shutdown() + GetProfileCopy() *model.Profile } -func NewProfileStore(filename string, password string) ProfileStore { - return &profileStore{NewFileStore(filename, password)} +// NewProfileStore returns a profile store backed by a filestore listening for events and saving them +func NewProfileStore(eventManager *event.Manager, filename string, password string) (ProfileStore, error) { + ps := &profileStore{fs: NewFileStore(filename, password), profile: nil, eventManager: eventManager} + err := ps.Load() + if err == nil { + ps.queue = event.NewEventQueue(100) + go ps.eventHandler() + + ps.eventManager.Subscribe(event.BlockPeer, ps.queue.EventChannel) + } + return ps, err } -func (ps *profileStore) Save(profile *model.Profile) error { - bytes, _ := json.Marshal(profile) +func (ps *profileStore) Init(name string) { + ps.profile = model.GenerateNewProfile(name) + ps.Save() +} + +func (ps *profileStore) Save() error { + bytes, _ := json.Marshal(ps.profile) return ps.fs.Save(bytes) } // Load instantiates a cwtchPeer from the file store -func (ps *profileStore) Load() (*model.Profile, error) { +func (ps *profileStore) Load() error { decrypted, err := ps.fs.Load() if err != nil { - return nil, err + return err } cp := new(model.Profile) err = json.Unmarshal(decrypted, &cp) if err == nil { - return cp, nil + ps.profile = cp + return nil } - return nil, err + return err +} + +func (ps *profileStore) GetProfileCopy() *model.Profile { + return ps.profile.GetCopy() +} + +func (ps *profileStore) eventHandler() { + for { + ev := ps.queue.Next() + switch ev.EventType { + case event.BlockPeer: + contact, exists := ps.profile.GetContact(ev.Data["Onion"]) + if exists { + contact.Blocked = true + } + default: + return + } + + ps.Save() + } +} + +func (ps *profileStore) Shutdown() { + ps.queue.Shutdown() }