More comments + UpdateMessageAttribute public API
This commit is contained in:
parent
f1caca3adf
commit
847b04e4fc
|
@ -11,12 +11,14 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"git.openprivacy.ca/cwtch.im/tapir/primitives"
|
||||||
"git.openprivacy.ca/openprivacy/connectivity/tor"
|
"git.openprivacy.ca/openprivacy/connectivity/tor"
|
||||||
"git.openprivacy.ca/openprivacy/log"
|
"git.openprivacy.ca/openprivacy/log"
|
||||||
"golang.org/x/crypto/nacl/secretbox"
|
"golang.org/x/crypto/nacl/secretbox"
|
||||||
"golang.org/x/crypto/pbkdf2"
|
"golang.org/x/crypto/pbkdf2"
|
||||||
"io"
|
"io"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CurrentGroupVersion is used to set the version of newly created groups and make sure group structs stored are correct and up to date
|
// CurrentGroupVersion is used to set the version of newly created groups and make sure group structs stored are correct and up to date
|
||||||
|
@ -229,3 +231,45 @@ func (g *Group) VerifyGroupMessage(onion string, groupID string, message string,
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// EncryptMessageToGroup when given a message and a group, encrypts and signs the message under the group and
|
||||||
|
// profile
|
||||||
|
func EncryptMessageToGroup(message string, author primitives.Identity, group *Group) ([]byte, []byte, *groups.DecryptedGroupMessage, error) {
|
||||||
|
if len(message) > MaxGroupMessageLength {
|
||||||
|
return nil, nil, nil, errors.New("group message is too long")
|
||||||
|
}
|
||||||
|
timestamp := time.Now().Unix()
|
||||||
|
|
||||||
|
// Select the latest message from the timeline as a reference point.
|
||||||
|
var prevSig []byte
|
||||||
|
if len(group.Timeline.Messages) > 0 {
|
||||||
|
prevSig = group.Timeline.Messages[len(group.Timeline.Messages)-1].Signature
|
||||||
|
} else {
|
||||||
|
prevSig = []byte(group.GroupID)
|
||||||
|
}
|
||||||
|
|
||||||
|
lenPadding := MaxGroupMessageLength - len(message)
|
||||||
|
padding := make([]byte, lenPadding)
|
||||||
|
getRandomness(&padding)
|
||||||
|
hexGroupID, err := hex.DecodeString(group.GroupID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
dm := &groups.DecryptedGroupMessage{
|
||||||
|
Onion: author.Hostname(),
|
||||||
|
Text: message,
|
||||||
|
SignedGroupID: hexGroupID,
|
||||||
|
Timestamp: uint64(timestamp),
|
||||||
|
PreviousMessageSig: prevSig,
|
||||||
|
Padding: padding[:],
|
||||||
|
}
|
||||||
|
|
||||||
|
ciphertext, err := group.EncryptMessage(dm)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, err
|
||||||
|
}
|
||||||
|
serialized, _ := json.Marshal(dm)
|
||||||
|
signature := author.Sign([]byte(group.GroupID + group.GroupServer + base64.StdEncoding.EncodeToString(serialized)))
|
||||||
|
return ciphertext, signature, dm, nil
|
||||||
|
}
|
||||||
|
|
|
@ -2,20 +2,17 @@ package model
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"cwtch.im/cwtch/protocol/groups"
|
|
||||||
"encoding/base64"
|
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
|
||||||
"git.openprivacy.ca/cwtch.im/tapir/primitives"
|
|
||||||
"golang.org/x/crypto/ed25519"
|
"golang.org/x/crypto/ed25519"
|
||||||
"io"
|
"io"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Authorization is a type determining client assigned authorization to a peer
|
// Authorization is a type determining client assigned authorization to a peer
|
||||||
|
// Deprecated - Only used for Importing legacy profile formats
|
||||||
|
// Still used in some APIs in UI but will be replaced prior to full deprecation
|
||||||
type Authorization string
|
type Authorization string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -28,6 +25,7 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
// PublicProfile is a local copy of a CwtchIdentity
|
// PublicProfile is a local copy of a CwtchIdentity
|
||||||
|
// Deprecated - Only used for Importing legacy profile formats
|
||||||
type PublicProfile struct {
|
type PublicProfile struct {
|
||||||
Name string
|
Name string
|
||||||
Ed25519PublicKey ed25519.PublicKey
|
Ed25519PublicKey ed25519.PublicKey
|
||||||
|
@ -43,6 +41,7 @@ type PublicProfile struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Profile encapsulates all the attributes necessary to be a Cwtch Peer.
|
// Profile encapsulates all the attributes necessary to be a Cwtch Peer.
|
||||||
|
// Deprecated - Only used for Importing legacy profile formats
|
||||||
type Profile struct {
|
type Profile struct {
|
||||||
PublicProfile
|
PublicProfile
|
||||||
Contacts map[string]*PublicProfile
|
Contacts map[string]*PublicProfile
|
||||||
|
@ -71,48 +70,6 @@ func GenerateRandomID() string {
|
||||||
return filepath.Join(hex.EncodeToString(randBytes))
|
return filepath.Join(hex.EncodeToString(randBytes))
|
||||||
}
|
}
|
||||||
|
|
||||||
// EncryptMessageToGroup when given a message and a group, encrypts and signs the message under the group and
|
|
||||||
// profile
|
|
||||||
func EncryptMessageToGroup(message string, author primitives.Identity, group *Group) ([]byte, []byte, *groups.DecryptedGroupMessage, error) {
|
|
||||||
if len(message) > MaxGroupMessageLength {
|
|
||||||
return nil, nil, nil, errors.New("group message is too long")
|
|
||||||
}
|
|
||||||
timestamp := time.Now().Unix()
|
|
||||||
|
|
||||||
// Select the latest message from the timeline as a reference point.
|
|
||||||
var prevSig []byte
|
|
||||||
if len(group.Timeline.Messages) > 0 {
|
|
||||||
prevSig = group.Timeline.Messages[len(group.Timeline.Messages)-1].Signature
|
|
||||||
} else {
|
|
||||||
prevSig = []byte(group.GroupID)
|
|
||||||
}
|
|
||||||
|
|
||||||
lenPadding := MaxGroupMessageLength - len(message)
|
|
||||||
padding := make([]byte, lenPadding)
|
|
||||||
getRandomness(&padding)
|
|
||||||
hexGroupID, err := hex.DecodeString(group.GroupID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
dm := &groups.DecryptedGroupMessage{
|
|
||||||
Onion: author.Hostname(),
|
|
||||||
Text: message,
|
|
||||||
SignedGroupID: hexGroupID,
|
|
||||||
Timestamp: uint64(timestamp),
|
|
||||||
PreviousMessageSig: prevSig,
|
|
||||||
Padding: padding[:],
|
|
||||||
}
|
|
||||||
|
|
||||||
ciphertext, err := group.EncryptMessage(dm)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, nil, err
|
|
||||||
}
|
|
||||||
serialized, _ := json.Marshal(dm)
|
|
||||||
signature := author.Sign([]byte(group.GroupID + group.GroupServer + base64.StdEncoding.EncodeToString(serialized)))
|
|
||||||
return ciphertext, signature, dm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetCopy returns a full deep copy of the Profile struct and its members (timeline inclusion control by arg)
|
// GetCopy returns a full deep copy of the Profile struct and its members (timeline inclusion control by arg)
|
||||||
func (p *Profile) GetCopy(timeline bool) *Profile {
|
func (p *Profile) GetCopy(timeline bool) *Profile {
|
||||||
p.lock.Lock()
|
p.lock.Lock()
|
||||||
|
|
|
@ -223,16 +223,6 @@ func (cp *cwtchPeer) SendMessage(conversation int, message string) error {
|
||||||
return fmt.Errorf("error sending message to conversation %v", err)
|
return fmt.Errorf("error sending message to conversation %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateMessageFlags
|
|
||||||
// Status: TODO
|
|
||||||
func (cp *cwtchPeer) UpdateMessageFlags(handle string, mIdx int, flags uint64) {
|
|
||||||
cp.mutex.Lock()
|
|
||||||
defer cp.mutex.Unlock()
|
|
||||||
log.Debugf("Updating Flags for %v %v %v", handle, mIdx, flags)
|
|
||||||
//cp.Profile.UpdateMessageFlags(handle, mIdx, flags)
|
|
||||||
cp.eventBus.Publish(event.NewEvent(event.UpdateMessageFlags, map[event.Field]string{event.Handle: handle, event.Index: strconv.Itoa(mIdx), event.Flags: strconv.FormatUint(flags, 2)}))
|
|
||||||
}
|
|
||||||
|
|
||||||
// BlockUnknownConnections will auto disconnect from connections if authentication doesn't resolve a hostname
|
// BlockUnknownConnections will auto disconnect from connections if authentication doesn't resolve a hostname
|
||||||
// known to peer.
|
// known to peer.
|
||||||
// Status: Ready for 1.5
|
// Status: Ready for 1.5
|
||||||
|
@ -385,36 +375,6 @@ func ImportLegacyProfile(profile *model.Profile, cps *CwtchProfileStorage) Cwtch
|
||||||
func (cp *cwtchPeer) Init(eventBus event.Manager) {
|
func (cp *cwtchPeer) Init(eventBus event.Manager) {
|
||||||
cp.InitForEvents(eventBus, DefaultEventsToHandle)
|
cp.InitForEvents(eventBus, DefaultEventsToHandle)
|
||||||
|
|
||||||
// Upgrade the Cwtch Peer if necessary
|
|
||||||
// It would be nice to do these checks in the storage engine itself, but it is easier to do them here
|
|
||||||
// rather than duplicating the logic to construct/reconstruct attributes in storage engine...
|
|
||||||
// TODO: Remove these checks after Cwtch ~1.5 storage engine is implemented
|
|
||||||
// TODO: Move this to import script
|
|
||||||
//if _, exists := cp.GetScopedZonedAttribute(attr.PublicScope, attr.ProfileZone, constants.Name); !exists {
|
|
||||||
// // If public.profile.name does not exist, and we have an existing public.name then:
|
|
||||||
// // set public.profile.name from public.name
|
|
||||||
// // set local.profile.name from public.name
|
|
||||||
// if name, exists := cp.GetAttribute(attr.GetPublicScope(constants.Name)); exists {
|
|
||||||
// cp.SetScopedZonedAttribute(attr.PublicScope, attr.ProfileZone, constants.Name, name)
|
|
||||||
// cp.SetScopedZonedAttribute(attr.LocalScope, attr.ProfileZone, constants.Name, name)
|
|
||||||
// } else {
|
|
||||||
// // Otherwise check if local.name exists and set it from that
|
|
||||||
// // If not, then check the very old unzoned, unscoped name.
|
|
||||||
// // If not, then set directly from Profile.Name...
|
|
||||||
// if name, exists := cp.Profile.GetAttribute(attr.GetLocalScope(constants.Name)); exists {
|
|
||||||
// cp.SetScopedZonedAttribute(attr.PublicScope, attr.ProfileZone, constants.Name, name)
|
|
||||||
// cp.SetScopedZonedAttribute(attr.LocalScope, attr.ProfileZone, constants.Name, name)
|
|
||||||
// } else if name, exists := cp.Profile.GetAttribute(constants.Name); exists {
|
|
||||||
// cp.SetScopedZonedAttribute(attr.PublicScope, attr.ProfileZone, constants.Name, name)
|
|
||||||
// cp.SetScopedZonedAttribute(attr.LocalScope, attr.ProfileZone, constants.Name, name)
|
|
||||||
// } else {
|
|
||||||
// // Profile.Name is very deprecated at this point...
|
|
||||||
// cp.SetScopedZonedAttribute(attr.PublicScope, attr.ProfileZone, constants.Name, cp.Profile.Name)
|
|
||||||
// cp.SetScopedZonedAttribute(attr.LocalScope, attr.ProfileZone, constants.Name, cp.Profile.Name)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
|
|
||||||
// At this point we can safely assume that public.profile.name exists
|
// At this point we can safely assume that public.profile.name exists
|
||||||
localName, _ := cp.GetScopedZonedAttribute(attr.LocalScope, attr.ProfileZone, constants.Name)
|
localName, _ := cp.GetScopedZonedAttribute(attr.LocalScope, attr.ProfileZone, constants.Name)
|
||||||
publicName, _ := cp.GetScopedZonedAttribute(attr.PublicScope, attr.ProfileZone, constants.Name)
|
publicName, _ := cp.GetScopedZonedAttribute(attr.PublicScope, attr.ProfileZone, constants.Name)
|
||||||
|
@ -422,21 +382,6 @@ func (cp *cwtchPeer) Init(eventBus event.Manager) {
|
||||||
if localName != publicName {
|
if localName != publicName {
|
||||||
cp.SetScopedZonedAttribute(attr.LocalScope, attr.ProfileZone, constants.Name, publicName)
|
cp.SetScopedZonedAttribute(attr.LocalScope, attr.ProfileZone, constants.Name, publicName)
|
||||||
}
|
}
|
||||||
|
|
||||||
// At this point we can safely assume that public.profile.name exists AND is consistent with
|
|
||||||
// local.profile.name - regardless of whatever Cwtch version we have upgraded from. This will
|
|
||||||
// be important after Cwtch 1.5 when we purge all previous references to local.profile.name and
|
|
||||||
// profile-> name - and remove all name processing code from libcwtch-go.
|
|
||||||
|
|
||||||
// If local.profile.tag does not exist then set it from deprecated GetAttribute
|
|
||||||
//if _, exists := cp.GetScopedZonedAttribute(attr.LocalScope, attr.ProfileZone, constants.Tag); !exists {
|
|
||||||
// if tag, exists := cp.Profile.GetAttribute(constants.Tag); exists {
|
|
||||||
// cp.SetScopedZonedAttribute(attr.LocalScope, attr.ProfileZone, constants.Tag, tag)
|
|
||||||
// } else {
|
|
||||||
// // Assume a default password, which will allow the older profile to have it's password reset by the UI
|
|
||||||
// cp.SetScopedZonedAttribute(attr.LocalScope, attr.ProfileZone, constants.Tag, constants.ProfileTypeV1DefaultPassword)
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// InitForEvents
|
// InitForEvents
|
||||||
|
@ -584,6 +529,17 @@ func (cp *cwtchPeer) GetMostRecentMessages(conversation int, channel int, offset
|
||||||
return cp.storage.GetMostRecentMessages(conversation, channel, offset, limit)
|
return cp.storage.GetMostRecentMessages(conversation, channel, offset, limit)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (cp *cwtchPeer) UpdateMessageAttribute(conversation int, channel int, id int, key string, value string) error {
|
||||||
|
_, attr, err := cp.GetChannelMessage(conversation, channel, id)
|
||||||
|
if err == nil {
|
||||||
|
cp.mutex.Lock()
|
||||||
|
defer cp.mutex.Unlock()
|
||||||
|
attr[key] = value
|
||||||
|
return cp.storage.UpdateMessageAttributes(conversation, channel, id, attr)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// StartGroup create a new group linked to the given server and returns the group ID, an invite or an error.
|
// StartGroup create a new group linked to the given server and returns the group ID, an invite or an error.
|
||||||
// Status: TODO change server handle to conversation id...?
|
// Status: TODO change server handle to conversation id...?
|
||||||
func (cp *cwtchPeer) StartGroup(name string, server string) (int, error) {
|
func (cp *cwtchPeer) StartGroup(name string, server string) (int, error) {
|
||||||
|
@ -714,14 +670,8 @@ func (cp *cwtchPeer) SendInviteToConversation(conversationID int, inviteConversa
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// groupServer, isGroup := conversationInfo.Attributes[event.GroupServer]; isGroup {
|
|
||||||
//bundle, _ := cp.Get(profile.GetGroup(target).GroupServer).GetAttribute(string(model.BundleType))
|
|
||||||
//inviteStr, err := profile.GetGroup(target).Invite()
|
|
||||||
//if err == nil {
|
|
||||||
// invite = model.MessageWrapper{Overlay: 101, Data: fmt.Sprintf("tofubundle:server:%s||%s", base64.StdEncoding.EncodeToString([]byte(bundle)), inviteStr)}
|
|
||||||
//}
|
|
||||||
if tor.IsValidHostname(inviteConversationInfo.Handle) {
|
if tor.IsValidHostname(inviteConversationInfo.Handle) {
|
||||||
invite = model.MessageWrapper{Overlay: 100, Data: inviteConversationInfo.Handle}
|
invite = model.MessageWrapper{Overlay: model.OverlayInviteContact, Data: inviteConversationInfo.Handle}
|
||||||
} else {
|
} else {
|
||||||
// Reconstruct Group
|
// Reconstruct Group
|
||||||
groupID, ok := inviteConversationInfo.Attributes[attr.LocalScope.ConstructScopedZonedPath(attr.LegacyGroupZone.ConstructZonedPath(constants.GroupID)).ToString()]
|
groupID, ok := inviteConversationInfo.Attributes[attr.LocalScope.ConstructScopedZonedPath(attr.LegacyGroupZone.ConstructZonedPath(constants.GroupID)).ToString()]
|
||||||
|
@ -771,7 +721,7 @@ func (cp *cwtchPeer) SendInviteToConversation(conversationID int, inviteConversa
|
||||||
return errors.New("server bundle not found")
|
return errors.New("server bundle not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
invite = model.MessageWrapper{Overlay: 101, Data: fmt.Sprintf("tofubundle:server:%s||%s", base64.StdEncoding.EncodeToString([]byte(bundle)), groupInvite)}
|
invite = model.MessageWrapper{Overlay: model.OverlayInviteContact, Data: fmt.Sprintf("tofubundle:server:%s||%s", base64.StdEncoding.EncodeToString([]byte(bundle)), groupInvite)}
|
||||||
}
|
}
|
||||||
|
|
||||||
inviteBytes, err := json.Marshal(invite)
|
inviteBytes, err := json.Marshal(invite)
|
||||||
|
|
|
@ -51,11 +51,6 @@ type SendMessages interface {
|
||||||
SendScopedZonedGetValToContact(conversationID int, scope attr.Scope, zone attr.Zone, key string)
|
SendScopedZonedGetValToContact(conversationID int, scope attr.Scope, zone attr.Zone, key string)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ModifyMessages enables a caller to modify the messages in a timeline
|
|
||||||
type ModifyMessages interface {
|
|
||||||
UpdateMessageFlags(string, int, uint64)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CwtchPeer provides us with a way of testing systems built on top of cwtch without having to
|
// CwtchPeer provides us with a way of testing systems built on top of cwtch without having to
|
||||||
// directly implement a cwtchPeer.
|
// directly implement a cwtchPeer.
|
||||||
type CwtchPeer interface {
|
type CwtchPeer interface {
|
||||||
|
@ -95,7 +90,6 @@ type CwtchPeer interface {
|
||||||
ModifyServers
|
ModifyServers
|
||||||
|
|
||||||
SendMessages
|
SendMessages
|
||||||
ModifyMessages
|
|
||||||
|
|
||||||
// Import Bundle
|
// Import Bundle
|
||||||
ImportBundle(string) error
|
ImportBundle(string) error
|
||||||
|
@ -116,6 +110,7 @@ type CwtchPeer interface {
|
||||||
GetChannelMessageCount(conversation int, channel int) (int, error)
|
GetChannelMessageCount(conversation int, channel int) (int, error)
|
||||||
GetChannelMessageByContentHash(conversation int, channel int, contenthash string) (int, error)
|
GetChannelMessageByContentHash(conversation int, channel int, contenthash string) (int, error)
|
||||||
GetMostRecentMessages(conversation int, channel int, offset int, limit int) ([]model.ConversationMessage, error)
|
GetMostRecentMessages(conversation int, channel int, offset int, limit int) ([]model.ConversationMessage, error)
|
||||||
|
UpdateMessageAttribute(conversation int, channel int, id int, key string, value string) error
|
||||||
|
|
||||||
ShareFile(fileKey string, serializedManifest string)
|
ShareFile(fileKey string, serializedManifest string)
|
||||||
CheckPassword(password string) bool
|
CheckPassword(password string) bool
|
||||||
|
|
Loading…
Reference in New Issue