cwtch/storage/profile_store.go

342 lines
10 KiB
Go
Raw Normal View History

2018-10-05 03:18:34 +00:00
package storage
2018-10-06 03:50:55 +00:00
import (
2019-01-21 20:11:40 +00:00
"cwtch.im/cwtch/event"
"cwtch.im/cwtch/model"
"encoding/json"
2019-02-03 01:18:33 +00:00
"git.openprivacy.ca/openprivacy/libricochet-go/log"
"os"
"time"
2018-10-06 03:50:55 +00:00
)
const groupIDLen = 32
const peerIDLen = 56
const profileFilename = "profile"
type profileStore struct {
2019-01-21 20:11:40 +00:00
fs FileStore
streamStores map[string]StreamStore // map [groupId|onion] StreamStore
directory string
password string
2019-01-21 20:11:40 +00:00
profile *model.Profile
eventManager event.Manager
queue event.Queue
writer bool
}
2018-10-06 03:50:55 +00:00
// ProfileStore is an interface to managing the storage of Cwtch Profiles
type ProfileStore interface {
2019-01-21 20:11:40 +00:00
Load() error
Shutdown()
2019-12-10 23:45:43 +00:00
Delete()
GetProfileCopy(timeline bool) *model.Profile
GetNewPeerMessage() *event.Event
GetStatusMessages() []*event.Event
}
// NewProfileWriterStore returns a profile store backed by a filestore listening for events and saving them
// directory should be $appDir/profiles/$rand
func NewProfileWriterStore(eventManager event.Manager, directory, password string, profile *model.Profile) ProfileStore {
os.Mkdir(directory, 0700)
ps := &profileStore{fs: NewFileStore(directory, profileFilename, password), password: password, directory: directory, profile: profile, eventManager: eventManager, streamStores: map[string]StreamStore{}, writer: true}
//ps.queue = event.NewQueue(100)
ps.queue = event.NewQueue()
2019-02-04 19:17:33 +00:00
if profile != nil {
ps.save()
}
go ps.eventHandler()
2019-01-21 20:11:40 +00:00
ps.eventManager.Subscribe(event.BlockPeer, ps.queue)
ps.eventManager.Subscribe(event.UnblockPeer, ps.queue)
ps.eventManager.Subscribe(event.PeerCreated, ps.queue)
ps.eventManager.Subscribe(event.GroupCreated, ps.queue)
ps.eventManager.Subscribe(event.SetProfileName, ps.queue)
ps.eventManager.Subscribe(event.SetAttribute, ps.queue)
ps.eventManager.Subscribe(event.SetPeerAttribute, ps.queue)
ps.eventManager.Subscribe(event.SetGroupAttribute, ps.queue)
ps.eventManager.Subscribe(event.AcceptGroupInvite, ps.queue)
ps.eventManager.Subscribe(event.NewGroupInvite, ps.queue)
ps.eventManager.Subscribe(event.NewMessageFromGroup, ps.queue)
ps.eventManager.Subscribe(event.PeerStateChange, ps.queue)
ps.eventManager.Subscribe(event.ServerStateChange, ps.queue)
ps.eventManager.Subscribe(event.DeleteContact, ps.queue)
ps.eventManager.Subscribe(event.DeleteGroup, ps.queue)
ps.eventManager.Subscribe(event.ChangePassword, ps.queue)
return ps
}
// ReadProfile reads a profile from storqage and returns the profile
// directory should be $appDir/profiles/$rand
func ReadProfile(directory, password string) (*model.Profile, error) {
os.Mkdir(directory, 0700)
ps := &profileStore{fs: NewFileStore(directory, profileFilename, password), password: password, directory: directory, profile: nil, eventManager: nil, streamStores: map[string]StreamStore{}, writer: true}
err := ps.Load()
if err != nil {
return nil, err
}
profile := ps.GetProfileCopy(true)
return profile, nil
}
2019-02-04 18:25:58 +00:00
// NewProfile creates a new profile for use in the profile store.
func NewProfile(name string) *model.Profile {
profile := model.GenerateNewProfile(name)
return profile
2019-01-21 20:11:40 +00:00
}
// GetNewPeerMessage is for AppService to call on Reload events, to reseed the AppClient with the loaded peers
func (ps *profileStore) GetNewPeerMessage() *event.Event {
message := event.NewEventList(event.NewPeer, event.Identity, ps.profile.LocalID, event.Password, ps.password, event.Status, "running")
return &message
}
func (ps *profileStore) GetStatusMessages() []*event.Event {
messages := []*event.Event{}
for _, contact := range ps.profile.Contacts {
message := event.NewEvent(event.PeerStateChange, map[event.Field]string{
event.RemotePeer: string(contact.Onion),
event.ConnectionState: contact.State,
})
messages = append(messages, &message)
}
doneServers := make(map[string]bool)
for _, group := range ps.profile.Groups {
if _, exists := doneServers[group.GroupServer]; !exists {
message := event.NewEvent(event.ServerStateChange, map[event.Field]string{
event.GroupServer: string(group.GroupServer),
event.ConnectionState: group.State,
})
messages = append(messages, &message)
doneServers[group.GroupServer] = true
}
}
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 {
if ps.writer {
bytes, _ := json.Marshal(ps.profile)
return ps.fs.Write(bytes)
}
return nil
}
// Read instantiates a cwtchPeer from the file store
2019-01-21 20:11:40 +00:00
func (ps *profileStore) Load() error {
decrypted, err := ps.fs.Read()
if err != nil {
2019-01-21 20:11:40 +00:00
return err
}
cp := new(model.Profile)
err = json.Unmarshal(decrypted, &cp)
if err == nil {
2019-01-21 20:11:40 +00:00
ps.profile = cp
for gid, group := range cp.Groups {
2019-02-03 01:18:33 +00:00
ss := NewStreamStore(ps.directory, group.LocalID, ps.password)
cp.Groups[gid].Timeline.SetMessages(ss.Read())
2019-02-03 01:18:33 +00:00
ps.streamStores[group.GroupID] = ss
}
}
2019-01-21 20:11:40 +00:00
return err
}
func (ps *profileStore) GetProfileCopy(timeline bool) *model.Profile {
return ps.profile.GetCopy(timeline)
2019-01-21 20:11:40 +00:00
}
func (ps *profileStore) eventHandler() {
log.Infoln("eventHandler()!")
2019-01-21 20:11:40 +00:00
for {
ev := ps.queue.Next()
log.Infof("eventHandler event %v\n", ev)
2019-01-21 20:11:40 +00:00
switch ev.EventType {
case event.BlockPeer:
2019-08-07 01:25:38 +00:00
contact, exists := ps.profile.GetContact(ev.Data[event.RemotePeer])
2019-01-21 20:11:40 +00:00
if exists {
contact.Blocked = true
ps.save()
}
2019-08-07 18:49:44 +00:00
case event.UnblockPeer:
contact, exists := ps.profile.GetContact(ev.Data[event.RemotePeer])
if exists {
contact.Blocked = false
ps.save()
}
2019-02-03 01:18:33 +00:00
case event.PeerCreated:
var pp *model.PublicProfile
json.Unmarshal([]byte(ev.Data[event.Data]), &pp)
ps.profile.AddContact(ev.Data[event.RemotePeer], pp)
// TODO: configure - allow peers to be configured to turn on limited storage
/*ss := NewStreamStore(ps.directory, pp.LocalID, ps.password)
2019-02-03 01:18:33 +00:00
pp.Timeline.SetMessages(ss.Read())
ps.streamStores[pp.Onion] = ss
ps.save()*/
2019-02-03 01:18:33 +00:00
case event.GroupCreated:
var group *model.Group
json.Unmarshal([]byte(ev.Data[event.Data]), &group)
ps.profile.AddGroup(group)
ps.streamStores[group.GroupID] = NewStreamStore(ps.directory, group.LocalID, ps.password)
ps.save()
case event.SetProfileName:
ps.profile.Name = ev.Data[event.ProfileName]
ps.profile.SetAttribute("name", ev.Data[event.ProfileName])
ps.save()
case event.SetAttribute:
ps.profile.SetAttribute(ev.Data[event.Key], ev.Data[event.Data])
ps.save()
case event.SetPeerAttribute:
contact, exists := ps.profile.GetContact(ev.Data[event.RemotePeer])
if exists {
contact.SetAttribute(ev.Data[event.Key], ev.Data[event.Data])
ps.save()
2019-02-13 03:41:09 +00:00
} else {
log.Errorf("error setting attribute on peer %v peer does not exist", ev)
2019-01-21 20:11:40 +00:00
}
case event.SetGroupAttribute:
group := ps.profile.GetGroup(ev.Data[event.GroupID])
2019-02-14 01:57:42 +00:00
if group != nil {
group.SetAttribute(ev.Data[event.Key], ev.Data[event.Data])
ps.save()
2019-02-13 03:41:09 +00:00
} else {
log.Errorf("error setting attribute on group %v group does not exist", ev)
}
2019-02-14 01:57:42 +00:00
case event.AcceptGroupInvite:
err := ps.profile.AcceptInvite(ev.Data[event.GroupID])
if err == nil {
ps.save()
} else {
log.Errorf("error accepting group invite")
}
case event.NewGroupInvite:
gid, err := ps.profile.ProcessInvite(ev.Data[event.GroupInvite], ev.Data[event.RemotePeer])
log.Errorf("gid: %v err:%v\n", gid, err)
2019-02-13 03:41:09 +00:00
if err == nil {
ps.save()
group := ps.profile.Groups[gid]
2019-02-13 03:41:09 +00:00
ps.streamStores[group.GroupID] = NewStreamStore(ps.directory, group.LocalID, ps.password)
} else {
log.Errorf("error storing new group invite: %v (%v)", err, ev)
2019-02-13 03:41:09 +00:00
}
case event.NewMessageFromGroup:
groupid := ev.Data[event.GroupID]
received, _ := time.Parse(time.RFC3339Nano, ev.Data[event.TimestampReceived])
sent, _ := time.Parse(time.RFC3339Nano, ev.Data[event.TimestampSent])
2019-02-04 04:32:22 +00:00
message := model.Message{Received: received, Timestamp: sent, Message: ev.Data[event.Data], PeerID: ev.Data[event.RemotePeer], Signature: []byte(ev.Data[event.Signature]), PreviousMessageSig: []byte(ev.Data[event.PreviousSignature])}
2019-02-13 03:41:09 +00:00
ss, exists := ps.streamStores[groupid]
if exists {
ss.Write(message)
} else {
log.Errorf("error storing new group message: %v stream store does not exist", ev)
}
case event.PeerStateChange:
if _, exists := ps.profile.Contacts[ev.Data[event.RemotePeer]]; exists {
ps.profile.Contacts[ev.Data[event.RemotePeer]].State = ev.Data[event.ConnectionState]
}
case event.ServerStateChange:
for _, group := range ps.profile.Groups {
if group.GroupServer == ev.Data[event.GroupServer] {
group.State = ev.Data[event.ConnectionState]
}
}
case event.DeleteContact:
onion := ev.Data[event.RemotePeer]
ps.profile.DeleteContact(onion)
ps.save()
case event.DeleteGroup:
groupID := ev.Data[event.GroupID]
ps.profile.DeleteGroup(groupID)
ps.save()
ss, exists := ps.streamStores[groupID]
if exists {
ss.Delete()
delete(ps.streamStores, groupID)
}
case event.ChangePassword:
oldpass := ev.Data[event.Password]
newpass := ev.Data[event.NewPassword]
ps.ChangePassword(oldpass, newpass, ev.EventID)
2019-01-21 20:11:40 +00:00
default:
return
}
}
}
func (ps *profileStore) Shutdown() {
if ps.queue != nil {
ps.queue.Shutdown()
}
2018-10-06 03:50:55 +00:00
}
2019-12-10 23:45:43 +00:00
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)
}
}