More comments + UpdateMessageAttribute public API
This commit is contained in:
parent
f1caca3adf
commit
847b04e4fc
|
@ -11,12 +11,14 @@ import (
|
|||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"git.openprivacy.ca/cwtch.im/tapir/primitives"
|
||||
"git.openprivacy.ca/openprivacy/connectivity/tor"
|
||||
"git.openprivacy.ca/openprivacy/log"
|
||||
"golang.org/x/crypto/nacl/secretbox"
|
||||
"golang.org/x/crypto/pbkdf2"
|
||||
"io"
|
||||
"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
|
||||
|
@ -229,3 +231,45 @@ func (g *Group) VerifyGroupMessage(onion string, groupID string, message string,
|
|||
}
|
||||
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 (
|
||||
"crypto/rand"
|
||||
"cwtch.im/cwtch/protocol/groups"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"git.openprivacy.ca/cwtch.im/tapir/primitives"
|
||||
"golang.org/x/crypto/ed25519"
|
||||
"io"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// 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
|
||||
|
||||
const (
|
||||
|
@ -28,6 +25,7 @@ const (
|
|||
)
|
||||
|
||||
// PublicProfile is a local copy of a CwtchIdentity
|
||||
// Deprecated - Only used for Importing legacy profile formats
|
||||
type PublicProfile struct {
|
||||
Name string
|
||||
Ed25519PublicKey ed25519.PublicKey
|
||||
|
@ -43,6 +41,7 @@ type PublicProfile struct {
|
|||
}
|
||||
|
||||
// Profile encapsulates all the attributes necessary to be a Cwtch Peer.
|
||||
// Deprecated - Only used for Importing legacy profile formats
|
||||
type Profile struct {
|
||||
PublicProfile
|
||||
Contacts map[string]*PublicProfile
|
||||
|
@ -71,48 +70,6 @@ func GenerateRandomID() string {
|
|||
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)
|
||||
func (p *Profile) GetCopy(timeline bool) *Profile {
|
||||
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)
|
||||
}
|
||||
|
||||
// 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
|
||||
// known to peer.
|
||||
// Status: Ready for 1.5
|
||||
|
@ -385,36 +375,6 @@ func ImportLegacyProfile(profile *model.Profile, cps *CwtchProfileStorage) Cwtch
|
|||
func (cp *cwtchPeer) Init(eventBus event.Manager) {
|
||||
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
|
||||
localName, _ := cp.GetScopedZonedAttribute(attr.LocalScope, 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 {
|
||||
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
|
||||
|
@ -584,6 +529,17 @@ func (cp *cwtchPeer) GetMostRecentMessages(conversation int, channel int, offset
|
|||
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.
|
||||
// Status: TODO change server handle to conversation id...?
|
||||
func (cp *cwtchPeer) StartGroup(name string, server string) (int, error) {
|
||||
|
@ -714,14 +670,8 @@ func (cp *cwtchPeer) SendInviteToConversation(conversationID int, inviteConversa
|
|||
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) {
|
||||
invite = model.MessageWrapper{Overlay: 100, Data: inviteConversationInfo.Handle}
|
||||
invite = model.MessageWrapper{Overlay: model.OverlayInviteContact, Data: inviteConversationInfo.Handle}
|
||||
} else {
|
||||
// Reconstruct Group
|
||||
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")
|
||||
}
|
||||
|
||||
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)
|
||||
|
|
|
@ -51,11 +51,6 @@ type SendMessages interface {
|
|||
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
|
||||
// directly implement a cwtchPeer.
|
||||
type CwtchPeer interface {
|
||||
|
@ -95,7 +90,6 @@ type CwtchPeer interface {
|
|||
ModifyServers
|
||||
|
||||
SendMessages
|
||||
ModifyMessages
|
||||
|
||||
// Import Bundle
|
||||
ImportBundle(string) error
|
||||
|
@ -116,6 +110,7 @@ type CwtchPeer interface {
|
|||
GetChannelMessageCount(conversation int, channel int) (int, error)
|
||||
GetChannelMessageByContentHash(conversation int, channel int, contenthash string) (int, 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)
|
||||
CheckPassword(password string) bool
|
||||
|
|
Loading…
Reference in New Issue