Merge branch 'delete' of dan/cwtch into master
the build was successful Details

This commit is contained in:
Sarah Jamie Lewis 2019-12-13 14:50:18 -08:00 committed by Gogs
commit 45946dd451
12 changed files with 356 additions and 46 deletions

View File

@ -18,11 +18,14 @@ import (
"sync" "sync"
) )
// AttributeTag is a const name for a peer attribute that can be set at creation time, for example for versioning info
const AttributeTag = "tag"
type applicationCore struct { type applicationCore struct {
eventBuses map[string]event.Manager eventBuses map[string]event.Manager
directory string directory string
mutex sync.Mutex coremutex sync.Mutex
} }
type application struct { type application struct {
@ -30,16 +33,20 @@ type application struct {
appletPeers appletPeers
appletACN appletACN
appletPlugins appletPlugins
storage map[string]storage.ProfileStore storage map[string]storage.ProfileStore
engines map[string]connections.Engine engines map[string]connections.Engine
appBus event.Manager appBus event.Manager
appmutex sync.Mutex
} }
// Application is a full cwtch peer application. It allows management, usage and storage of multiple peers // Application is a full cwtch peer application. It allows management, usage and storage of multiple peers
type Application interface { type Application interface {
LoadProfiles(password string) LoadProfiles(password string)
CreatePeer(name string, password string) CreatePeer(name string, password string)
CreateTaggedPeer(name string, password string, tag string)
DeletePeer(onion string)
AddPeerPlugin(onion string, pluginID plugins.PluginID) AddPeerPlugin(onion string, pluginID plugins.PluginID)
ChangePeerPassword(onion, oldpass, newpass string)
LaunchPeers() LaunchPeers()
GetPrimaryBus() event.Manager GetPrimaryBus() event.Manager
@ -78,8 +85,8 @@ func (ac *applicationCore) CreatePeer(name string) (*model.Profile, error) {
profile := storage.NewProfile(name) profile := storage.NewProfile(name)
ac.mutex.Lock() ac.coremutex.Lock()
defer ac.mutex.Unlock() defer ac.coremutex.Unlock()
_, exists := ac.eventBuses[profile.Onion] _, exists := ac.eventBuses[profile.Onion]
if exists { if exists {
@ -92,8 +99,15 @@ func (ac *applicationCore) CreatePeer(name string) (*model.Profile, error) {
return profile, nil return profile, nil
} }
// CreatePeer creates a new Peer with the given name and required accessories (eventbus, storage, protocol engine) func (ac *applicationCore) DeletePeer(onion string) {
func (app *application) CreatePeer(name string, password string) { ac.coremutex.Lock()
defer ac.coremutex.Unlock()
ac.eventBuses[onion].Shutdown()
delete(ac.eventBuses, onion)
}
func (app *application) CreateTaggedPeer(name string, password string, tag string) {
profile, err := app.applicationCore.CreatePeer(name) profile, err := app.applicationCore.CreatePeer(name)
if err != nil { if err != nil {
app.appBus.Publish(event.NewEventList(event.PeerError, event.Error, err.Error())) app.appBus.Publish(event.NewEventList(event.PeerError, event.Error, err.Error()))
@ -115,9 +129,46 @@ func (app *application) CreatePeer(name string, password string) {
app.peers[profile.Onion] = peer app.peers[profile.Onion] = peer
app.engines[profile.Onion] = engine app.engines[profile.Onion] = engine
if tag != "" {
peer.SetAttribute(AttributeTag, tag)
}
app.appBus.Publish(event.NewEvent(event.NewPeer, map[event.Field]string{event.Identity: profile.Onion})) app.appBus.Publish(event.NewEvent(event.NewPeer, map[event.Field]string{event.Identity: profile.Onion}))
} }
// CreatePeer creates a new Peer with the given name and required accessories (eventbus, storage, protocol engine)
func (app *application) CreatePeer(name string, password string) {
app.CreateTaggedPeer(name, password, "")
}
func (app *application) DeletePeer(onion string) {
log.Infof("DeletePeer called on %v\n", onion)
app.appmutex.Lock()
defer app.appmutex.Unlock()
app.appletPlugins.ShutdownPeer(onion)
app.plugins.Delete(onion)
app.peers[onion].Shutdown()
delete(app.peers, onion)
app.engines[onion].Shutdown()
delete(app.engines, onion)
app.storage[onion].Shutdown()
app.storage[onion].Delete()
delete(app.storage, onion)
app.eventBuses[onion].Publish(event.NewEventList(event.ShutdownPeer, event.Identity, onion))
app.applicationCore.DeletePeer(onion)
log.Debugf("Delete peer for %v Done\n", onion)
}
func (app *application) ChangePeerPassword(onion, oldpass, newpass string) {
app.eventBuses[onion].Publish(event.NewEventList(event.ChangePassword, event.Password, oldpass, event.NewPassword, newpass))
}
func (app *application) AddPeerPlugin(onion string, pluginID plugins.PluginID) { func (app *application) AddPeerPlugin(onion string, pluginID plugins.PluginID) {
app.AddPlugin(onion, pluginID, app.eventBuses[onion], app.acn) app.AddPlugin(onion, pluginID, app.eventBuses[onion], app.acn)
} }
@ -147,9 +198,9 @@ func (ac *applicationCore) LoadProfiles(password string, timeline bool, loadProf
continue continue
} }
ac.mutex.Lock() ac.coremutex.Lock()
ac.eventBuses[profile.Onion] = eventBus ac.eventBuses[profile.Onion] = eventBus
ac.mutex.Unlock() ac.coremutex.Unlock()
loadProfileFn(profile, profileStore) loadProfileFn(profile, profileStore)
} }
@ -166,11 +217,11 @@ func (app *application) LoadProfiles(password string) {
blockedPeers := profile.BlockedPeers() blockedPeers := profile.BlockedPeers()
identity := primitives.InitializeIdentity(profile.Name, &profile.Ed25519PrivateKey, &profile.Ed25519PublicKey) identity := primitives.InitializeIdentity(profile.Name, &profile.Ed25519PrivateKey, &profile.Ed25519PublicKey)
engine := connections.NewProtocolEngine(identity, profile.Ed25519PrivateKey, app.acn, app.eventBuses[profile.Onion], profile.GetContacts(), blockedPeers) engine := connections.NewProtocolEngine(identity, profile.Ed25519PrivateKey, app.acn, app.eventBuses[profile.Onion], profile.GetContacts(), blockedPeers)
app.mutex.Lock() app.appmutex.Lock()
app.peers[profile.Onion] = peer app.peers[profile.Onion] = peer
app.storage[profile.Onion] = profileStore app.storage[profile.Onion] = profileStore
app.engines[profile.Onion] = engine app.engines[profile.Onion] = engine
app.mutex.Unlock() app.appmutex.Unlock()
app.appBus.Publish(event.NewEvent(event.NewPeer, map[event.Field]string{event.Identity: profile.Onion})) app.appBus.Publish(event.NewEvent(event.NewPeer, map[event.Field]string{event.Identity: profile.Onion}))
count++ count++
}) })
@ -210,8 +261,8 @@ func (app *application) QueryACNStatus() {
// ShutdownPeer shuts down a peer and removes it from the app's management // ShutdownPeer shuts down a peer and removes it from the app's management
func (app *application) ShutdownPeer(onion string) { func (app *application) ShutdownPeer(onion string) {
app.mutex.Lock() app.appmutex.Lock()
defer app.mutex.Unlock() defer app.appmutex.Unlock()
app.eventBuses[onion].Shutdown() app.eventBuses[onion].Shutdown()
delete(app.eventBuses, onion) delete(app.eventBuses, onion)
app.peers[onion].Shutdown() app.peers[onion].Shutdown()

View File

@ -9,6 +9,7 @@ import (
"git.openprivacy.ca/openprivacy/libricochet-go/log" "git.openprivacy.ca/openprivacy/libricochet-go/log"
"path" "path"
"strconv" "strconv"
"sync"
) )
type applicationClient struct { type applicationClient struct {
@ -16,6 +17,7 @@ type applicationClient struct {
appletPeers appletPeers
appBus event.Manager appBus event.Manager
acmutex sync.Mutex
} }
// NewAppClient returns an Application that acts as a client to a AppService, connected by the IPCBridge supplied // NewAppClient returns an Application that acts as a client to a AppService, connected by the IPCBridge supplied
@ -43,6 +45,9 @@ func (ac *applicationClient) handleEvent(ev *event.Event) {
password := ev.Data[event.Password] password := ev.Data[event.Password]
reload := ev.Data[event.Status] == "running" reload := ev.Data[event.Status] == "running"
ac.newPeer(localID, password, reload) ac.newPeer(localID, password, reload)
case event.DeletePeer:
onion := ev.Data[event.Identity]
ac.handleDeletedPeer(onion)
case event.PeerError: case event.PeerError:
ac.appBus.Publish(*ev) ac.appBus.Publish(*ev)
case event.AppError: case event.AppError:
@ -73,8 +78,8 @@ func (ac *applicationClient) newPeer(localID, password string, reload bool) {
peer := peer.FromProfile(profile) peer := peer.FromProfile(profile)
peer.Init(eventBus) peer.Init(eventBus)
ac.mutex.Lock() ac.acmutex.Lock()
defer ac.mutex.Unlock() defer ac.acmutex.Unlock()
ac.peers[profile.Onion] = peer ac.peers[profile.Onion] = peer
ac.eventBuses[profile.Onion] = eventBus ac.eventBuses[profile.Onion] = eventBus
npEvent := event.NewEvent(event.NewPeer, map[event.Field]string{event.Identity: profile.Onion}) npEvent := event.NewEvent(event.NewPeer, map[event.Field]string{event.Identity: profile.Onion})
@ -90,11 +95,36 @@ func (ac *applicationClient) newPeer(localID, password string, reload bool) {
// CreatePeer messages the service to create a new Peer with the given name // CreatePeer messages the service to create a new Peer with the given name
func (ac *applicationClient) CreatePeer(name string, password string) { func (ac *applicationClient) CreatePeer(name string, password string) {
ac.CreateTaggedPeer(name, password, "")
}
func (ac *applicationClient) CreateTaggedPeer(name, password, tag string) {
log.Infof("appClient CreatePeer %v\n", name) log.Infof("appClient CreatePeer %v\n", name)
message := event.IPCMessage{Dest: DestApp, Message: event.NewEvent(event.CreatePeer, map[event.Field]string{event.ProfileName: name, event.Password: password})} message := event.IPCMessage{Dest: DestApp, Message: event.NewEvent(event.CreatePeer, map[event.Field]string{event.ProfileName: name, event.Password: password, event.Data: tag})}
ac.bridge.Write(&message) ac.bridge.Write(&message)
} }
// DeletePeer messages tehe service to delete a peer
func (ac *applicationClient) DeletePeer(onion string) {
message := event.IPCMessage{Dest: DestApp, Message: event.NewEvent(event.DeletePeer, map[event.Field]string{event.Identity: onion})}
ac.bridge.Write(&message)
}
func (ac *applicationClient) ChangePeerPassword(onion, oldpass, newpass string) {
message := event.IPCMessage{Dest: onion, Message: event.NewEventList(event.ChangePassword, event.Password, oldpass, event.NewPassword, newpass)}
ac.bridge.Write(&message)
}
func (ac *applicationClient) handleDeletedPeer(onion string) {
ac.acmutex.Lock()
defer ac.acmutex.Unlock()
ac.peers[onion].Shutdown()
delete(ac.peers, onion)
ac.eventBuses[onion].Publish(event.NewEventList(event.ShutdownPeer, event.Identity, onion))
ac.applicationCore.DeletePeer(onion)
}
func (ac *applicationClient) AddPeerPlugin(onion string, pluginID plugins.PluginID) { func (ac *applicationClient) AddPeerPlugin(onion string, pluginID plugins.PluginID) {
message := event.IPCMessage{Dest: DestApp, Message: event.NewEvent(event.AddPeerPlugin, map[event.Field]string{event.Identity: onion, event.Data: strconv.Itoa(int(pluginID))})} message := event.IPCMessage{Dest: DestApp, Message: event.NewEvent(event.AddPeerPlugin, map[event.Field]string{event.Identity: onion, event.Data: strconv.Itoa(int(pluginID))})}
ac.bridge.Write(&message) ac.bridge.Write(&message)
@ -113,8 +143,8 @@ func (ac *applicationClient) QueryACNStatus() {
// ShutdownPeer shuts down a peer and removes it from the app's management // ShutdownPeer shuts down a peer and removes it from the app's management
func (ac *applicationClient) ShutdownPeer(onion string) { func (ac *applicationClient) ShutdownPeer(onion string) {
ac.mutex.Lock() ac.acmutex.Lock()
defer ac.mutex.Unlock() defer ac.acmutex.Unlock()
ac.eventBuses[onion].Shutdown() ac.eventBuses[onion].Shutdown()
delete(ac.eventBuses, onion) delete(ac.eventBuses, onion)
ac.peers[onion].Shutdown() ac.peers[onion].Shutdown()

View File

@ -11,6 +11,7 @@ import (
"git.openprivacy.ca/openprivacy/libricochet-go/log" "git.openprivacy.ca/openprivacy/libricochet-go/log"
"path" "path"
"strconv" "strconv"
"sync"
) )
type applicationService struct { type applicationService struct {
@ -20,6 +21,7 @@ type applicationService struct {
storage map[string]storage.ProfileStore storage map[string]storage.ProfileStore
engines map[string]connections.Engine engines map[string]connections.Engine
asmutex sync.Mutex
} }
// ApplicationService is the back end of an application that manages engines and writing storage and communicates to an ApplicationClient by an IPCBridge // ApplicationService is the back end of an application that manages engines and writing storage and communicates to an ApplicationClient by an IPCBridge
@ -46,7 +48,14 @@ func (as *applicationService) handleEvent(ev *event.Event) {
case event.CreatePeer: case event.CreatePeer:
profileName := ev.Data[event.ProfileName] profileName := ev.Data[event.ProfileName]
password := ev.Data[event.Password] password := ev.Data[event.Password]
as.createPeer(profileName, password) tag := ev.Data[event.Data]
as.createPeer(profileName, password, tag)
case event.DeletePeer:
onion := ev.Data[event.Identity]
as.deletePeer(onion)
message := event.IPCMessage{Dest: DestApp, Message: *ev}
as.bridge.Write(&message)
case event.AddPeerPlugin: case event.AddPeerPlugin:
onion := ev.Data[event.Identity] onion := ev.Data[event.Identity]
pluginID, _ := strconv.Atoi(ev.Data[event.Data]) pluginID, _ := strconv.Atoi(ev.Data[event.Data])
@ -79,7 +88,7 @@ func (as *applicationService) handleEvent(ev *event.Event) {
} }
} }
func (as *applicationService) createPeer(name, password string) { func (as *applicationService) createPeer(name, password, tag string) {
log.Infof("app Service create peer %v %v\n", name, password) log.Infof("app Service create peer %v %v\n", name, password)
profile, err := as.applicationCore.CreatePeer(name) profile, err := as.applicationCore.CreatePeer(name)
as.eventBuses[profile.Onion] = event.IPCEventManagerFrom(as.bridge, profile.Onion, as.eventBuses[profile.Onion]) as.eventBuses[profile.Onion] = event.IPCEventManagerFrom(as.bridge, profile.Onion, as.eventBuses[profile.Onion])
@ -90,6 +99,10 @@ func (as *applicationService) createPeer(name, password string) {
return return
} }
if tag != "" {
profile.SetAttribute(AttributeTag, tag)
}
profileStore := storage.NewProfileWriterStore(as.eventBuses[profile.Onion], path.Join(as.directory, "profiles", profile.LocalID), password, profile) profileStore := storage.NewProfileWriterStore(as.eventBuses[profile.Onion], path.Join(as.directory, "profiles", profile.LocalID), password, profile)
blockedPeers := profile.BlockedPeers() blockedPeers := profile.BlockedPeers()
@ -112,10 +125,10 @@ func (as *applicationService) loadProfiles(password string) {
blockedPeers := profile.BlockedPeers() blockedPeers := profile.BlockedPeers()
identity := primitives.InitializeIdentity(profile.Name, &profile.Ed25519PrivateKey, &profile.Ed25519PublicKey) identity := primitives.InitializeIdentity(profile.Name, &profile.Ed25519PrivateKey, &profile.Ed25519PublicKey)
engine := connections.NewProtocolEngine(identity, profile.Ed25519PrivateKey, as.acn, as.eventBuses[profile.Onion], profile.GetContacts(), blockedPeers) engine := connections.NewProtocolEngine(identity, profile.Ed25519PrivateKey, as.acn, as.eventBuses[profile.Onion], profile.GetContacts(), blockedPeers)
as.mutex.Lock() as.asmutex.Lock()
as.storage[profile.Onion] = profileStore as.storage[profile.Onion] = profileStore
as.engines[profile.Onion] = engine as.engines[profile.Onion] = engine
as.mutex.Unlock() as.asmutex.Unlock()
message := event.IPCMessage{Dest: DestApp, Message: event.NewEvent(event.NewPeer, map[event.Field]string{event.Identity: profile.LocalID, event.Password: password})} message := event.IPCMessage{Dest: DestApp, Message: event.NewEvent(event.NewPeer, map[event.Field]string{event.Identity: profile.LocalID, event.Password: password})}
as.bridge.Write(&message) as.bridge.Write(&message)
count++ count++
@ -136,6 +149,23 @@ func (as *applicationService) getACNStatusHandler() func(int, string) {
} }
} }
func (as *applicationService) deletePeer(onion string) {
as.asmutex.Lock()
defer as.asmutex.Unlock()
as.appletPlugins.ShutdownPeer(onion)
as.plugins.Delete(onion)
as.engines[onion].Shutdown()
delete(as.engines, onion)
as.storage[onion].Shutdown()
as.storage[onion].Delete()
delete(as.storage, onion)
as.applicationCore.DeletePeer(onion)
}
func (as *applicationService) ShutdownPeer(onion string) { func (as *applicationService) ShutdownPeer(onion string) {
as.engines[onion].Shutdown() as.engines[onion].Shutdown()
delete(as.engines, onion) delete(as.engines, onion)

View File

@ -131,7 +131,7 @@ func main() {
profileStore, _ := storage.NewProfileStore(nil, os.Args[2], pw) profileStore, _ := storage.NewProfileStore(nil, os.Args[2], pw)
err = profileStore.Load() err = profileStore.Read()
if err != nil { if err != nil {
log.Errorln(err) log.Errorln(err)
os.Exit(1) os.Exit(1)

View File

@ -82,6 +82,15 @@ const (
// RemotePeer [eg ""] // RemotePeer [eg ""]
PeerCreated = Type("PeerCreated") PeerCreated = Type("PeerCreated")
// Password, NewPassword
ChangePassword = Type("ChangePassword")
// Error(err), EventID
ChangePasswordError = Type("ChangePasswordError")
// EventID
ChangePasswordSuccess = Type("ChangePasswordSuccess")
// a group has been successfully added or newly created // a group has been successfully added or newly created
// attributes: // attributes:
// Data [serialized *model.Group] // Data [serialized *model.Group]
@ -129,13 +138,16 @@ const (
/***** Application client / service messages *****/ /***** Application client / service messages *****/
// ProfileName, Password // ProfileName, Password, Data(tag)
CreatePeer = Type("CreatePeer") CreatePeer = Type("CreatePeer")
// service -> client: Identity(localId), Password, [Status(new/default=blank || from reload='running')] // service -> client: Identity(localId), Password, [Status(new/default=blank || from reload='running')]
// app -> Identity(onion) // app -> Identity(onion)
NewPeer = Type("NewPeer") NewPeer = Type("NewPeer")
// Identity(onion)
DeletePeer = Type("DeletePeer")
// Identity(onion), Data(pluginID) // Identity(onion), Data(pluginID)
AddPeerPlugin = Type("AddPeerPlugin") AddPeerPlugin = Type("AddPeerPlugin")
@ -156,6 +168,7 @@ const (
Shutdown = Type("Shutdown") Shutdown = Type("Shutdown")
// Error(err) // Error(err)
// Error creating peer
PeerError = Type("PeerError") PeerError = Type("PeerError")
// Error(err) // Error(err)
@ -197,6 +210,7 @@ const (
ProfileName = Field("ProfileName") ProfileName = Field("ProfileName")
Password = Field("Password") Password = Field("Password")
NewPassword = Field("NewPassword")
ConnectionState = Field("ConnectionState") ConnectionState = Field("ConnectionState")

View File

@ -36,7 +36,7 @@ type Group struct {
// NewGroup initializes a new group associated with a given CwtchServer // NewGroup initializes a new group associated with a given CwtchServer
func NewGroup(server string) (*Group, error) { func NewGroup(server string) (*Group, error) {
group := new(Group) group := new(Group)
group.LocalID = generateRandomID() group.LocalID = GenerateRandomID()
if utils.IsValidHostname(server) == false { if utils.IsValidHostname(server) == false {
return nil, errors.New("Server is not a valid v3 onion") return nil, errors.New("Server is not a valid v3 onion")

View File

@ -44,7 +44,8 @@ type Profile struct {
// TODO: Should this be per server? // TODO: Should this be per server?
const MaxGroupMessageLength = 1800 const MaxGroupMessageLength = 1800
func generateRandomID() string { // GenerateRandomID generates a random 16 byte hex id code
func GenerateRandomID() string {
randBytes := make([]byte, 16) randBytes := make([]byte, 16)
rand.Read(randBytes) rand.Read(randBytes)
return filepath.Join(hex.EncodeToString(randBytes)) return filepath.Join(hex.EncodeToString(randBytes))
@ -55,7 +56,7 @@ func (p *PublicProfile) init() {
p.Attributes = make(map[string]string) p.Attributes = make(map[string]string)
} }
p.unacknowledgedMessages = make(map[string]Message) p.unacknowledgedMessages = make(map[string]Message)
p.LocalID = generateRandomID() p.LocalID = GenerateRandomID()
} }
// SetAttribute allows applications to store arbitrary configuration info at the profile level. // SetAttribute allows applications to store arbitrary configuration info at the profile level.
@ -371,7 +372,7 @@ func (p *Profile) ProcessInvite(invite string, peerHostname string) (string, err
if err == nil { if err == nil {
group := new(Group) group := new(Group)
group.GroupID = gci.GetGroupName() group.GroupID = gci.GetGroupName()
group.LocalID = generateRandomID() group.LocalID = GenerateRandomID()
group.SignedGroupID = gci.GetSignedGroupId() group.SignedGroupID = gci.GetSignedGroupId()
copy(group.GroupKey[:], gci.GetGroupSharedKey()[:]) copy(group.GroupKey[:], gci.GetGroupSharedKey()[:])
group.GroupServer = gci.GetServerHost() group.GroupServer = gci.GetServerHost()

View File

@ -1,7 +1,9 @@
package storage package storage
import ( import (
"git.openprivacy.ca/openprivacy/libricochet-go/log"
"io/ioutil" "io/ioutil"
"os"
"path" "path"
) )
@ -14,8 +16,10 @@ type fileStore struct {
// FileStore is a primitive around storing encrypted files // FileStore is a primitive around storing encrypted files
type FileStore interface { type FileStore interface {
Save([]byte) error Write([]byte) error
Load() ([]byte, error) Read() ([]byte, error)
Delete()
ChangePassword(newpass string)
} }
// NewFileStore instantiates a fileStore given a filename and a password // NewFileStore instantiates a fileStore given a filename and a password
@ -27,8 +31,8 @@ func NewFileStore(directory string, filename string, password string) FileStore
return filestore return filestore
} }
// save serializes a cwtchPeer to a file // write serializes a cwtchPeer to a file
func (fps *fileStore) Save(data []byte) error { func (fps *fileStore) Write(data []byte) error {
key, salt, _ := createKey(fps.password) key, salt, _ := createKey(fps.password)
encryptedbytes, err := encryptFileData(data, key) encryptedbytes, err := encryptFileData(data, key)
if err != nil { if err != nil {
@ -39,9 +43,19 @@ func (fps *fileStore) Save(data []byte) error {
encryptedbytes = append(salt[:], encryptedbytes...) encryptedbytes = append(salt[:], encryptedbytes...)
err = ioutil.WriteFile(path.Join(fps.directory, fps.filename), encryptedbytes, 0600) err = ioutil.WriteFile(path.Join(fps.directory, fps.filename), encryptedbytes, 0600)
return err return err
} }
func (fps *fileStore) Load() ([]byte, error) { func (fps *fileStore) Read() ([]byte, error) {
return readEncryptedFile(fps.directory, fps.filename, fps.password) return readEncryptedFile(fps.directory, fps.filename, fps.password)
} }
func (fps *fileStore) Delete() {
err := os.Remove(path.Join(fps.directory, fps.filename))
if err != nil {
log.Errorf("Deleting file %v\n", err)
}
}
func (fps *fileStore) ChangePassword(newpass string) {
fps.password = newpass
}

View File

@ -9,11 +9,13 @@ import (
"time" "time"
) )
const groupIDLen = 32
const peerIDLen = 56
const profileFilename = "profile" const profileFilename = "profile"
type profileStore struct { type profileStore struct {
fs FileStore fs FileStore
streamStores map[string]StreamStore streamStores map[string]StreamStore // map [groupId|onion] StreamStore
directory string directory string
password string password string
profile *model.Profile profile *model.Profile
@ -26,6 +28,7 @@ type profileStore struct {
type ProfileStore interface { type ProfileStore interface {
Load() error Load() error
Shutdown() Shutdown()
Delete()
GetProfileCopy(timeline bool) *model.Profile GetProfileCopy(timeline bool) *model.Profile
GetNewPeerMessage() *event.Event GetNewPeerMessage() *event.Event
GetStatusMessages() []*event.Event GetStatusMessages() []*event.Event
@ -58,6 +61,7 @@ func NewProfileWriterStore(eventManager event.Manager, directory, password strin
ps.eventManager.Subscribe(event.ServerStateChange, ps.queue) ps.eventManager.Subscribe(event.ServerStateChange, ps.queue)
ps.eventManager.Subscribe(event.DeleteContact, ps.queue) ps.eventManager.Subscribe(event.DeleteContact, ps.queue)
ps.eventManager.Subscribe(event.DeleteGroup, ps.queue) ps.eventManager.Subscribe(event.DeleteGroup, ps.queue)
ps.eventManager.Subscribe(event.ChangePassword, ps.queue)
return ps return ps
} }
@ -115,17 +119,64 @@ func (ps *profileStore) GetStatusMessages() []*event.Event {
return messages return messages
} }
func (ps *profileStore) ChangePassword(oldpass, newpass, eventID string) {
if oldpass != ps.password {
ps.eventManager.Publish(event.NewEventList(event.ChangePasswordError, event.Error, "Supplied current password does not match", event.EventID, eventID))
return
}
newStreamStores := map[string]StreamStore{}
idToNewLocalID := map[string]string{}
// Generate all new StreamStores with the new password and write all the old StreamStore data into these ones
for ssid, ss := range ps.streamStores {
// New ss with new pass and new localID
newlocalID := model.GenerateRandomID()
idToNewLocalID[ssid] = newlocalID
newSS := NewStreamStore(ps.directory, newlocalID, newpass)
newStreamStores[ssid] = newSS
// write whole store
messages := ss.Read()
newSS.WriteN(messages)
}
// Switch over
oldStreamStores := ps.streamStores
ps.streamStores = newStreamStores
for ssid, newLocalID := range idToNewLocalID {
if len(ssid) == groupIDLen {
ps.profile.Groups[ssid].LocalID = newLocalID
} else {
ps.profile.Contacts[ssid].LocalID = newLocalID
}
}
ps.password = newpass
ps.fs.ChangePassword(newpass)
ps.save()
// Clean up
for _, oldss := range oldStreamStores {
oldss.Delete()
}
ps.eventManager.Publish(event.NewEventList(event.ChangePasswordSuccess, event.EventID, eventID))
return
}
func (ps *profileStore) save() error { func (ps *profileStore) save() error {
if ps.writer { if ps.writer {
bytes, _ := json.Marshal(ps.profile) bytes, _ := json.Marshal(ps.profile)
return ps.fs.Save(bytes) return ps.fs.Write(bytes)
} }
return nil return nil
} }
// Load instantiates a cwtchPeer from the file store // Read instantiates a cwtchPeer from the file store
func (ps *profileStore) Load() error { func (ps *profileStore) Load() error {
decrypted, err := ps.fs.Load() decrypted, err := ps.fs.Read()
if err != nil { if err != nil {
return err return err
} }
@ -136,6 +187,7 @@ func (ps *profileStore) Load() error {
for gid, group := range cp.Groups { for gid, group := range cp.Groups {
ss := NewStreamStore(ps.directory, group.LocalID, ps.password) ss := NewStreamStore(ps.directory, group.LocalID, ps.password)
cp.Groups[gid].Timeline.SetMessages(ss.Read()) cp.Groups[gid].Timeline.SetMessages(ss.Read())
ps.streamStores[group.GroupID] = ss ps.streamStores[group.GroupID] = ss
} }
@ -149,8 +201,11 @@ func (ps *profileStore) GetProfileCopy(timeline bool) *model.Profile {
} }
func (ps *profileStore) eventHandler() { func (ps *profileStore) eventHandler() {
log.Infoln("eventHandler()!")
for { for {
ev := ps.queue.Next() ev := ps.queue.Next()
log.Infof("eventHandler event %v\n", ev)
switch ev.EventType { switch ev.EventType {
case event.BlockPeer: case event.BlockPeer:
contact, exists := ps.profile.GetContact(ev.Data[event.RemotePeer]) contact, exists := ps.profile.GetContact(ev.Data[event.RemotePeer])
@ -253,6 +308,10 @@ func (ps *profileStore) eventHandler() {
ss.Delete() ss.Delete()
delete(ps.streamStores, groupID) delete(ps.streamStores, groupID)
} }
case event.ChangePassword:
oldpass := ev.Data[event.Password]
newpass := ev.Data[event.NewPassword]
ps.ChangePassword(oldpass, newpass, ev.EventID)
default: default:
return return
} }
@ -265,3 +324,18 @@ func (ps *profileStore) Shutdown() {
ps.queue.Shutdown() ps.queue.Shutdown()
} }
} }
func (ps *profileStore) Delete() {
log.Debugf("Delete ProfileStore for %v\n", ps.profile.Onion)
for _, ss := range ps.streamStores {
ss.Delete()
}
ps.fs.Delete()
err := os.RemoveAll(ps.directory)
if err != nil {
log.Errorf("ProfileStore Delete error on RemoveAll on %v was %v\n", ps.directory, err)
}
}

View File

@ -4,6 +4,8 @@ package storage
import ( import (
"cwtch.im/cwtch/event" "cwtch.im/cwtch/event"
"fmt"
"log"
"os" "os"
"testing" "testing"
"time" "time"
@ -16,6 +18,7 @@ const testInitialMessage = "howdy"
const testMessage = "Hello from storage" const testMessage = "Hello from storage"
func TestProfileStoreWriteRead(t *testing.T) { func TestProfileStoreWriteRead(t *testing.T) {
log.Println("profile store test!")
os.RemoveAll(testingDir) os.RemoveAll(testingDir)
eventBus := event.NewEventManager() eventBus := event.NewEventManager()
profile := NewProfile(testProfileName) profile := NewProfile(testProfileName)
@ -68,3 +71,82 @@ func TestProfileStoreWriteRead(t *testing.T) {
} }
} }
func TestProfileStoreChangePassword(t *testing.T) {
os.RemoveAll(testingDir)
eventBus := event.NewEventManager()
queue := event.NewQueue()
eventBus.Subscribe(event.ChangePasswordSuccess, queue)
profile := NewProfile(testProfileName)
ps1 := NewProfileWriterStore(eventBus, testingDir, password, profile)
groupid, invite, err := profile.StartGroup("2c3kmoobnyghj2zw6pwv7d57yzld753auo3ugauezzpvfak3ahc4bdyd")
if err != nil {
t.Errorf("Creating group: %v\n", err)
}
if err != nil {
t.Errorf("Creating group invite: %v\n", err)
}
eventBus.Publish(event.NewEvent(event.NewGroupInvite, map[event.Field]string{event.TimestampReceived: time.Now().Format(time.RFC3339Nano), event.RemotePeer: ps1.GetProfileCopy(true).Onion, event.GroupInvite: string(invite)}))
time.Sleep(1 * time.Second)
fmt.Println("Sending 200 messages...")
for i := 0; i < 200; i++ {
eventBus.Publish(event.NewEvent(event.NewMessageFromGroup, map[event.Field]string{
event.GroupID: groupid,
event.TimestampSent: time.Now().Format(time.RFC3339Nano),
event.TimestampReceived: time.Now().Format(time.RFC3339Nano),
event.RemotePeer: ps1.GetProfileCopy(true).Onion,
event.Data: testMessage,
}))
}
newPass := "qwerty123"
fmt.Println("Sending Change Passwords event...")
eventBus.Publish(event.NewEventList(event.ChangePassword, event.Password, password, event.NewPassword, newPass))
ev := queue.Next()
if ev.EventType != event.ChangePasswordSuccess {
t.Errorf("Unexpected event response detected %v\n", ev.EventType)
return
}
fmt.Println("Sending 10 more messages...")
for i := 0; i < 10; i++ {
eventBus.Publish(event.NewEvent(event.NewMessageFromGroup, map[event.Field]string{
event.GroupID: groupid,
event.TimestampSent: time.Now().Format(time.RFC3339Nano),
event.TimestampReceived: time.Now().Format(time.RFC3339Nano),
event.RemotePeer: ps1.GetProfileCopy(true).Onion,
event.Data: testMessage,
}))
}
time.Sleep(3 * time.Second)
fmt.Println("Shutdown profile store...")
ps1.Shutdown()
fmt.Println("New Profile store...")
ps2 := NewProfileWriterStore(eventBus, testingDir, newPass, nil)
err = ps2.Load()
if err != nil {
t.Errorf("Error createing new profileStore with new password: %v\n", err)
return
}
profile2 := ps2.GetProfileCopy(true)
if profile2.Groups[groupid] == nil {
t.Errorf("Failed to load group %v\n", groupid)
return
}
if len(profile2.Groups[groupid].Timeline.Messages) != 210 {
t.Errorf("Failed to load group's 210 messages, instead got %v\n", len(profile2.Groups[groupid].Timeline.Messages))
}
}

View File

@ -33,8 +33,10 @@ type streamStore struct {
// StreamStore provides a stream like interface to encrypted storage // StreamStore provides a stream like interface to encrypted storage
type StreamStore interface { type StreamStore interface {
Write(message model.Message) Write(message model.Message)
WriteN(messages []model.Message)
Read() []model.Message Read() []model.Message
Delete() Delete()
ChangePassword(newpass string)
} }
// NewStreamStore returns an initialized StreamStore ready for reading and writing // NewStreamStore returns an initialized StreamStore ready for reading and writing
@ -138,7 +140,7 @@ func (ss *streamStore) Read() (messages []model.Message) {
return resp return resp
} }
// AddMessage adds a GroupMessage to the store // Write adds a GroupMessage to the store
func (ss *streamStore) Write(m model.Message) { func (ss *streamStore) Write(m model.Message) {
ss.lock.Lock() ss.lock.Lock()
defer ss.lock.Unlock() defer ss.lock.Unlock()
@ -151,3 +153,21 @@ func (ss *streamStore) Write(m model.Message) {
ss.initBuffer() ss.initBuffer()
} }
} }
func (ss *streamStore) WriteN(messages []model.Message) {
ss.lock.Lock()
defer ss.lock.Unlock()
for _, m := range messages {
ss.updateBuffer(m)
if ss.bufferByteCount > bytesPerFile {
ss.updateFile()
log.Debugf("rotating log file")
ss.rotateFileStore()
ss.initBuffer()
}
}
}
func (ss *streamStore) ChangePassword(newpass string) {}

View File

@ -2,7 +2,6 @@ package storage
import ( import (
"cwtch.im/cwtch/model" "cwtch.im/cwtch/model"
"git.openprivacy.ca/openprivacy/libricochet-go/log"
"os" "os"
"testing" "testing"
) )
@ -13,9 +12,6 @@ const password = "asdfqwer"
const line1 = "Hello from storage!" const line1 = "Hello from storage!"
func TestStreamStoreWriteRead(t *testing.T) { func TestStreamStoreWriteRead(t *testing.T) {
log.SetLevel(log.LevelDebug)
os.Remove(".test.json") os.Remove(".test.json")
os.RemoveAll(testingDir) os.RemoveAll(testingDir)
os.Mkdir(testingDir, 0777) os.Mkdir(testingDir, 0777)
@ -34,8 +30,6 @@ func TestStreamStoreWriteRead(t *testing.T) {
} }
func TestStreamStoreWriteReadRotate(t *testing.T) { func TestStreamStoreWriteReadRotate(t *testing.T) {
log.SetLevel(log.LevelDebug)
os.Remove(".test.json") os.Remove(".test.json")
os.RemoveAll(testingDir) os.RemoveAll(testingDir)
os.Mkdir(testingDir, 0777) os.Mkdir(testingDir, 0777)