First cut of Importing Legacy Profiles
continuous-integration/drone/push Build is pending Details

This commit is contained in:
Sarah Jamie Lewis 2021-11-17 15:34:14 -08:00
parent e296c30818
commit cfff858fe1
4 changed files with 147 additions and 25 deletions

View File

@ -157,7 +157,6 @@ func (ac *applicationCore) LoadProfiles(password string, timeline bool, loadProf
}
for _, file := range files {
// Attempt to load an encrypted database
profileDirectory := path.Join(ac.directory, "profiles", file.Name())
profile, err := peer.FromEncryptedDatabase(profileDirectory, password)
@ -167,7 +166,7 @@ func (ac *applicationCore) LoadProfiles(password string, timeline bool, loadProf
loadProfileFn(profile)
}
// On failure
// On failure attempt to load a legacy profile
if err != nil {
eventBus := event.NewEventManager()
@ -176,19 +175,11 @@ func (ac *applicationCore) LoadProfiles(password string, timeline bool, loadProf
continue
}
profile := profileStore.GetProfileCopy(timeline)
_, exists := ac.eventBuses[profile.Onion]
if exists {
eventBus.Shutdown()
log.Errorf("profile for onion %v already exists", profile.Onion)
continue
}
ac.coremutex.Lock()
ac.eventBuses[profile.Onion] = eventBus
ac.coremutex.Unlock()
legacyProfile := profileStore.GetProfileCopy(timeline)
cps, err := peer.CreateEncryptedStore(profileDirectory, password)
profile := peer.ImportLegacyProfile(legacyProfile, cps)
loadProfileFn(profile)
}
}
return nil
@ -198,15 +189,21 @@ func (ac *applicationCore) LoadProfiles(password string, timeline bool, loadProf
func (app *application) LoadProfiles(password string) {
count := 0
app.applicationCore.LoadProfiles(password, true, func(profile peer.CwtchPeer) {
eventBus := event.NewEventManager()
app.eventBuses[profile.GetOnion()] = eventBus
profile.Init(app.eventBuses[profile.GetOnion()])
app.appmutex.Lock()
app.peers[profile.GetOnion()] = profile
app.engines[profile.GetOnion()], _ = profile.GenerateProtocolEngine(app.acn, app.eventBuses[profile.GetOnion()])
// Only attempt to finalize the profile if we don't have one loaded...
if app.peers[profile.GetOnion()] == nil {
eventBus := event.NewEventManager()
app.eventBuses[profile.GetOnion()] = eventBus
profile.Init(app.eventBuses[profile.GetOnion()])
app.peers[profile.GetOnion()] = profile
app.engines[profile.GetOnion()], _ = profile.GenerateProtocolEngine(app.acn, app.eventBuses[profile.GetOnion()])
app.appBus.Publish(event.NewEvent(event.NewPeer, map[event.Field]string{event.Identity: profile.GetOnion(), event.Created: event.False}))
count++
} else {
// Otherwise shutdown the connections
profile.Shutdown()
}
app.appmutex.Unlock()
app.appBus.Publish(event.NewEvent(event.NewPeer, map[event.Field]string{event.Identity: profile.GetOnion(), event.Created: event.False}))
count++
})
if count == 0 {
message := event.NewEventList(event.AppError, event.Error, event.AppErrLoaded0)

View File

@ -38,3 +38,11 @@ const AttrErr = "error"
// AttrSentTimestamp - conversation attribute for the time the message was (nominally) sent
const AttrSentTimestamp = "sent"
// Legacy MessageFlags
// AttrRejected - conversation attribute for storing rejected prompts (for invites)
const AttrRejected = "rejected-invite"
// AttrDownloaded - conversation attribute for storing downloaded prompts (for file downloads)
const AttrDownloaded = "file-downloaded"

View File

@ -233,6 +233,7 @@ func NewProfileWithEncryptedStorage(name string, cps *CwtchProfileStorage) Cwtch
cp := new(cwtchPeer)
cp.shutdown = false
cp.storage = cps
cp.queue = event.NewQueue()
cp.state = make(map[string]connections.ConnectionState)
pub, priv, _ := ed25519.GenerateKey(rand.Reader)
@ -250,22 +251,106 @@ func FromEncryptedStorage(cps *CwtchProfileStorage) CwtchPeer {
cp := new(cwtchPeer)
cp.shutdown = false
cp.storage = cps
cp.queue = event.NewQueue()
cp.state = make(map[string]connections.ConnectionState)
// At some point we may want to populate caches here, for now we will assume hitting the
// database directly is tolerable
return cp
}
// FromProfile generates a new peer from a profile.
// ImportLegacyProfile generates a new peer from a profile.
// Deprecated - Only to be used for importing new profiles
func FromProfile(profile *model.Profile, cps *CwtchProfileStorage) CwtchPeer {
func ImportLegacyProfile(profile *model.Profile, cps *CwtchProfileStorage) CwtchPeer {
cp := new(cwtchPeer)
cp.shutdown = false
cp.storage = cps
cp.queue = event.NewQueue()
// Store all the Necessary Base Attributes In The Database
cp.SetScopedZonedAttribute(attr.LocalScope, attr.ProfileZone, constants.Name, profile.Name)
cp.SetScopedZonedAttribute(attr.PublicScope, attr.ProfileZone, constants.Name, profile.Name)
cp.storage.StoreProfileKeyValue(TypeAttribute, attr.PublicScope.ConstructScopedZonedPath(attr.ProfileZone.ConstructZonedPath(constants.Onion)).ToString(), []byte(tor.GetTorV3Hostname(profile.Ed25519PublicKey)))
cp.storage.StoreProfileKeyValue(TypePrivateKey, "Ed25519PrivateKey", profile.Ed25519PrivateKey)
cp.storage.StoreProfileKeyValue(TypePublicKey, "Ed25519PublicKey", profile.Ed25519PublicKey)
for k, v := range profile.Attributes {
parts := strings.SplitN(k, ".", 2)
if len(parts) != 2 {
scope := attr.IntoScope(parts[0])
zone, path := attr.ParseZone(parts[1])
cp.SetScopedZonedAttribute(scope, zone, path, v)
} else {
log.Errorf("could not import legacy style attribute %v", k)
}
}
for _, contact := range profile.Contacts {
var conversationID int
var err error
if contact.Authorization == model.AuthApproved {
conversationID, err = cp.NewContactConversation(contact.Onion, model.DefaultP2PAccessControl(), true)
} else if contact.Authorization == model.AuthBlocked {
conversationID, err = cp.NewContactConversation(contact.Onion, model.AccessControl{Blocked: true, Read: false, Append: false}, true)
} else {
conversationID, err = cp.NewContactConversation(contact.Onion, model.DefaultP2PAccessControl(), false)
}
if err == nil {
for key, value := range contact.Attributes {
switch key {
case "name":
cp.SetConversationAttribute(conversationID, attr.LocalScope.ConstructScopedZonedPath(attr.ProfileZone.ConstructZonedPath(constants.Name)), value)
case "local.profile.name":
cp.SetConversationAttribute(conversationID, attr.LocalScope.ConstructScopedZonedPath(attr.ProfileZone.ConstructZonedPath(constants.Name)), value)
case event.SaveHistoryKey:
cp.SetConversationAttribute(conversationID, attr.LocalScope.ConstructScopedZonedPath(attr.ProfileZone.ConstructZonedPath(event.SaveHistoryKey)), value)
case string(model.BundleType):
cp.AddServer(value)
case string(model.KeyTypeTokenOnion):
//ignore
case string(model.KeyTypeServerOnion):
// ignore
case string(model.KeyTypePrivacyPass):
// ignore
default:
log.Errorf("could not import conversation attribute %v %v", key, value)
}
}
for _, message := range contact.Timeline.GetMessages() {
// By definition anything stored in legacy timelines in acknowledged
attr := model.Attributes{constants.AttrAck: event.True, constants.AttrSentTimestamp: message.Timestamp.Format(time.RFC3339Nano)}
if message.Flags&0x01 == 0x01 {
attr[constants.AttrRejected] = event.True
}
if message.Flags&0x02 == 0x02 {
attr[constants.AttrDownloaded] = event.True
}
cp.storage.InsertMessage(conversationID, 0, message.Message, attr, model.GenerateRandomID(), model.CalculateContentHash(message.PeerID, message.Message))
}
}
}
for _, group := range profile.Groups {
invite, err := group.Invite()
if err == nil {
// Automatically grab all the important fields...
// Including Name...
conversationID, err := cp.ImportGroup(invite)
if err == nil {
for _, message := range group.Timeline.GetMessages() {
// By definition anything stored in legacy timelines in acknowledged
attr := model.Attributes{constants.AttrAck: event.True, constants.AttrSentTimestamp: message.Timestamp.Format(time.RFC3339Nano)}
if message.Flags&0x01 == 0x01 {
attr[constants.AttrRejected] = event.True
}
if message.Flags&0x02 == 0x02 {
attr[constants.AttrDownloaded] = event.True
}
cp.storage.InsertMessage(conversationID, 0, message.Message, attr, base64.StdEncoding.EncodeToString(message.Signature), model.CalculateContentHash(message.PeerID, message.Message))
}
}
}
}
return cp
}
@ -331,7 +416,6 @@ func (cp *cwtchPeer) Init(eventBus event.Manager) {
// InitForEvents
// Status: Ready for 1.5
func (cp *cwtchPeer) InitForEvents(eventBus event.Manager, toBeHandled []event.Type) {
cp.queue = event.NewQueue()
go cp.eventHandler()
cp.eventBus = eventBus

View File

@ -113,6 +113,39 @@ func CreateEncryptedStorePeer(profileDirectory string, name string, password str
return NewProfileWithEncryptedStorage(name, cps), nil
}
// CreateEncryptedStore creates a encrypted datastore
func CreateEncryptedStore(profileDirectory string, password string) (*CwtchProfileStorage, error) {
log.Debugf("Initializing Encrypted Storage Directory")
_, _, err := initV2Directory(profileDirectory, password)
if err != nil {
return nil, err
}
log.Debugf("Opening Encrypted Database")
db, err := openEncryptedDatabase(profileDirectory, password)
if db == nil || err != nil {
return nil, fmt.Errorf("unable to open encrypted database: error: %v", err)
}
log.Debugf("Initializing Database")
err = initializeDatabase(db)
if err != nil {
db.Close()
return nil, err
}
log.Debugf("Creating Cwtch Profile Backed By Encrypted Database")
cps, err := NewCwtchProfileStorage(db)
if err != nil {
db.Close()
return nil, err
}
return cps, nil
}
// FromEncryptedDatabase constructs a Cwtch Profile from an existing Encrypted Database
func FromEncryptedDatabase(profileDirectory string, password string) (CwtchPeer, error) {
log.Debugf("Loading Encrypted Profile")