Compare commits
5 Commits
Author | SHA1 | Date |
---|---|---|
Sarah Jamie Lewis | a7b885166a | |
Sarah Jamie Lewis | b32b11c711 | |
Sarah Jamie Lewis | 0e96539f22 | |
Sarah Jamie Lewis | e55f342324 | |
Sarah Jamie Lewis | 89aca91b37 |
|
@ -363,6 +363,7 @@ func (app *application) LoadProfiles(password string) {
|
||||||
func (app *application) registerHooks(profile peer.CwtchPeer) {
|
func (app *application) registerHooks(profile peer.CwtchPeer) {
|
||||||
// Register Hooks
|
// Register Hooks
|
||||||
profile.RegisterHook(extensions.ProfileValueExtension{})
|
profile.RegisterHook(extensions.ProfileValueExtension{})
|
||||||
|
profile.RegisterHook(extensions.SendWhenOnlineExtension{})
|
||||||
profile.RegisterHook(new(filesharing.Functionality))
|
profile.RegisterHook(new(filesharing.Functionality))
|
||||||
profile.RegisterHook(new(filesharing.ImagePreviewsFunctionality))
|
profile.RegisterHook(new(filesharing.ImagePreviewsFunctionality))
|
||||||
profile.RegisterHook(new(servers.Functionality))
|
profile.RegisterHook(new(servers.Functionality))
|
||||||
|
|
|
@ -104,6 +104,15 @@ func (pne ProfileValueExtension) OnContactRequestValue(profile peer.CwtchPeer, c
|
||||||
val, exists = profile.GetScopedZonedAttribute(attr.PublicScope, attr.ProfileZone, constants.Name)
|
val, exists = profile.GetScopedZonedAttribute(attr.PublicScope, attr.ProfileZone, constants.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NOTE: Cwtch 1.15+ requires that profiles be able to restrict file downloading to specific contacts. As such we need an ACL check here
|
||||||
|
// on the fileshareing zone.
|
||||||
|
// TODO: Split this functionality into FilesharingFunctionality, and restrict this function to only considering Profile zoned attributes?
|
||||||
|
if zone == attr.FilesharingZone {
|
||||||
|
if !conversation.GetPeerAC().ShareFiles {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Construct a Response
|
// Construct a Response
|
||||||
resp := event.NewEvent(event.SendRetValMessageToPeer, map[event.Field]string{event.ConversationID: strconv.Itoa(conversation.ID), event.RemotePeer: conversation.Handle, event.Exists: strconv.FormatBool(exists)})
|
resp := event.NewEvent(event.SendRetValMessageToPeer, map[event.Field]string{event.ConversationID: strconv.Itoa(conversation.ID), event.RemotePeer: conversation.Handle, event.Exists: strconv.FormatBool(exists)})
|
||||||
resp.EventID = eventID
|
resp.EventID = eventID
|
||||||
|
|
|
@ -0,0 +1,66 @@
|
||||||
|
package extensions
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"cwtch.im/cwtch/event"
|
||||||
|
"cwtch.im/cwtch/model"
|
||||||
|
"cwtch.im/cwtch/model/attr"
|
||||||
|
"cwtch.im/cwtch/model/constants"
|
||||||
|
"cwtch.im/cwtch/peer"
|
||||||
|
"cwtch.im/cwtch/protocol/connections"
|
||||||
|
"cwtch.im/cwtch/settings"
|
||||||
|
"git.openprivacy.ca/openprivacy/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SendWhenOnlineExtension implements automatic sending
|
||||||
|
// Some Considerations:
|
||||||
|
// - There are race conditions inherant in this approach e.g. a peer could go offline just after recieving a message and never sending an ack
|
||||||
|
// - In that case the next time we connect we will send a duplicate message.
|
||||||
|
// - Currently we do not include metadata like sent time in raw peer protocols (however Overlay does now have support for that information)
|
||||||
|
type SendWhenOnlineExtension struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (soe SendWhenOnlineExtension) NotifySettingsUpdate(_ settings.GlobalSettings) {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (soe SendWhenOnlineExtension) EventsToRegister() []event.Type {
|
||||||
|
return []event.Type{event.PeerStateChange}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (soe SendWhenOnlineExtension) ExperimentsToRegister() []string {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (soe SendWhenOnlineExtension) OnEvent(ev event.Event, profile peer.CwtchPeer) {
|
||||||
|
switch ev.EventType {
|
||||||
|
case event.PeerStateChange:
|
||||||
|
ci, err := profile.FetchConversationInfo(ev.Data["RemotePeer"])
|
||||||
|
if err == nil {
|
||||||
|
// if we have re-authenticated with thie peer then request their profile image...
|
||||||
|
if connections.ConnectionStateToType()[ev.Data[event.ConnectionState]] == connections.AUTHENTICATED {
|
||||||
|
// Check the last 100 messages, if any of them are pending, then send them now...
|
||||||
|
messsages, _ := profile.GetMostRecentMessages(ci.ID, 0, 0, uint(100))
|
||||||
|
for _, message := range messsages {
|
||||||
|
if message.Attr[constants.AttrAck] == constants.False {
|
||||||
|
body := message.Body
|
||||||
|
ev := event.NewEvent(event.SendMessageToPeer, map[event.Field]string{event.ConversationID: strconv.Itoa(ci.ID), event.RemotePeer: ci.Handle, event.Data: body})
|
||||||
|
ev.EventID = message.Signature // we need this ensure that we correctly ack this in the db when it comes back
|
||||||
|
// TODO: The EventBus is becoming very noisy...we may want to consider a one-way shortcut to Engine i.e. profile.Engine.SendMessageToPeer
|
||||||
|
log.Debugf("resending message that was sent when peer was offline")
|
||||||
|
profile.PublishEvent(ev)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// OnContactReceiveValue is nop for SendWhenOnnlineExtension
|
||||||
|
func (soe SendWhenOnlineExtension) OnContactReceiveValue(profile peer.CwtchPeer, conversation model.Conversation, szp attr.ScopedZonedPath, value string, exists bool) {
|
||||||
|
}
|
||||||
|
|
||||||
|
// OnContactRequestValue is nop for SendWhenOnnlineExtension
|
||||||
|
func (soe SendWhenOnlineExtension) OnContactRequestValue(profile peer.CwtchPeer, conversation model.Conversation, eventID string, szp attr.ScopedZonedPath) {
|
||||||
|
|
||||||
|
}
|
|
@ -62,10 +62,18 @@ func (i *ImagePreviewsFunctionality) OnEvent(ev event.Event, profile peer.CwtchP
|
||||||
if err == nil {
|
if err == nil {
|
||||||
for _, ci := range conversations {
|
for _, ci := range conversations {
|
||||||
if profile.GetPeerState(ci.Handle) == connections.AUTHENTICATED {
|
if profile.GetPeerState(ci.Handle) == connections.AUTHENTICATED {
|
||||||
|
// if we have enabled file shares for this contact, then send them our profile image
|
||||||
|
// NOTE: In the past, Cwtch treated "profile image" as a public file share. As such, anyone with the file key and who is able
|
||||||
|
// to authenticate with the profile (i.e. non-blocked peers) can download the file (if the global profile images experiment is enabled)
|
||||||
|
// To better allow for fine-grained permissions (and to support hybrid group permissions), we want to enable per-conversation file
|
||||||
|
// sharing permissions. As such, profile images are now only shared with contacts with that permission enabled.
|
||||||
|
// (i.e. all previous accepted contacts, new accepted contacts, and contacts who have this toggle set explictly)
|
||||||
|
if ci.GetPeerAC().ShareFiles {
|
||||||
profile.SendScopedZonedGetValToContact(ci.ID, attr.PublicScope, attr.ProfileZone, constants.CustomProfileImageKey)
|
profile.SendScopedZonedGetValToContact(ci.ID, attr.PublicScope, attr.ProfileZone, constants.CustomProfileImageKey)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
case event.ProtocolEngineCreated:
|
case event.ProtocolEngineCreated:
|
||||||
// Now that the Peer Engine is Activated, Reshare Profile Images
|
// Now that the Peer Engine is Activated, Reshare Profile Images
|
||||||
key, exists := profile.GetScopedZonedAttribute(attr.PublicScope, attr.ProfileZone, constants.CustomProfileImageKey)
|
key, exists := profile.GetScopedZonedAttribute(attr.PublicScope, attr.ProfileZone, constants.CustomProfileImageKey)
|
||||||
|
|
1
go.mod
1
go.mod
|
@ -16,7 +16,6 @@ require (
|
||||||
require (
|
require (
|
||||||
filippo.io/edwards25519 v1.0.0 // indirect
|
filippo.io/edwards25519 v1.0.0 // indirect
|
||||||
git.openprivacy.ca/openprivacy/bine v0.0.5 // indirect
|
git.openprivacy.ca/openprivacy/bine v0.0.5 // indirect
|
||||||
github.com/client9/misspell v0.3.4 // indirect
|
|
||||||
github.com/google/go-cmp v0.5.8 // indirect
|
github.com/google/go-cmp v0.5.8 // indirect
|
||||||
github.com/gtank/merlin v0.1.1 // indirect
|
github.com/gtank/merlin v0.1.1 // indirect
|
||||||
github.com/mimoo/StrobeGo v0.0.0-20220103164710-9a04d6ca976b // indirect
|
github.com/mimoo/StrobeGo v0.0.0-20220103164710-9a04d6ca976b // indirect
|
||||||
|
|
2
go.sum
2
go.sum
|
@ -8,8 +8,6 @@ git.openprivacy.ca/openprivacy/connectivity v1.11.0 h1:roASjaFtQLu+HdH5fa2wx6F00
|
||||||
git.openprivacy.ca/openprivacy/connectivity v1.11.0/go.mod h1:OQO1+7OIz/jLxDrorEMzvZA6SEbpbDyLGpjoFqT3z1Y=
|
git.openprivacy.ca/openprivacy/connectivity v1.11.0/go.mod h1:OQO1+7OIz/jLxDrorEMzvZA6SEbpbDyLGpjoFqT3z1Y=
|
||||||
git.openprivacy.ca/openprivacy/log v1.0.3 h1:E/PMm4LY+Q9s3aDpfySfEDq/vYQontlvNj/scrPaga0=
|
git.openprivacy.ca/openprivacy/log v1.0.3 h1:E/PMm4LY+Q9s3aDpfySfEDq/vYQontlvNj/scrPaga0=
|
||||||
git.openprivacy.ca/openprivacy/log v1.0.3/go.mod h1:gGYK8xHtndRLDymFtmjkG26GaMQNgyhioNS82m812Iw=
|
git.openprivacy.ca/openprivacy/log v1.0.3/go.mod h1:gGYK8xHtndRLDymFtmjkG26GaMQNgyhioNS82m812Iw=
|
||||||
github.com/client9/misspell v0.3.4 h1:ta993UF76GwbvJcIo3Y68y/M3WxlpEHPWIGDkJYwzJI=
|
|
||||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
package model
|
package model
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"time"
|
||||||
|
|
||||||
"cwtch.im/cwtch/model/attr"
|
"cwtch.im/cwtch/model/attr"
|
||||||
"cwtch.im/cwtch/model/constants"
|
"cwtch.im/cwtch/model/constants"
|
||||||
"encoding/json"
|
"git.openprivacy.ca/openprivacy/connectivity/tor"
|
||||||
"git.openprivacy.ca/openprivacy/log"
|
"git.openprivacy.ca/openprivacy/log"
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// AccessControl is a type determining client assigned authorization to a peer
|
// AccessControl is a type determining client assigned authorization to a peer
|
||||||
|
@ -99,6 +101,11 @@ func (ci *Conversation) GetPeerAC() AccessControl {
|
||||||
return DefaultP2PAccessControl()
|
return DefaultP2PAccessControl()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsCwtchPeer is a helper attribute that identifies whether a conversation is a cwtch peer
|
||||||
|
func (ci *Conversation) IsCwtchPeer() bool {
|
||||||
|
return tor.IsValidHostname(ci.Handle)
|
||||||
|
}
|
||||||
|
|
||||||
// IsGroup is a helper attribute that identifies whether a conversation is a legacy group
|
// IsGroup is a helper attribute that identifies whether a conversation is a legacy group
|
||||||
func (ci *Conversation) IsGroup() bool {
|
func (ci *Conversation) IsGroup() bool {
|
||||||
if _, exists := ci.Attributes[attr.LocalScope.ConstructScopedZonedPath(attr.LegacyGroupZone.ConstructZonedPath(constants.GroupID)).ToString()]; exists {
|
if _, exists := ci.Attributes[attr.LocalScope.ConstructScopedZonedPath(attr.LegacyGroupZone.ConstructZonedPath(constants.GroupID)).ToString()]; exists {
|
||||||
|
|
|
@ -310,8 +310,8 @@ func (cp *cwtchPeer) GenerateProtocolEngine(acn connectivity.ACN, bus event.Mana
|
||||||
authorizations := make(map[string]model.Authorization)
|
authorizations := make(map[string]model.Authorization)
|
||||||
for _, conversation := range conversations {
|
for _, conversation := range conversations {
|
||||||
|
|
||||||
if tor.IsValidHostname(conversation.Handle) {
|
// Only perform the following actions for Peer-type Conversaions...
|
||||||
|
if conversation.IsCwtchPeer() {
|
||||||
// if this profile does not have an ACL version, and the profile is accepted (OR the acl version is v1 and the profile is accepted...)
|
// if this profile does not have an ACL version, and the profile is accepted (OR the acl version is v1 and the profile is accepted...)
|
||||||
// then migrate the permissions to the v2 ACL
|
// then migrate the permissions to the v2 ACL
|
||||||
// migrate the old accepted AC to a new fine-grained one
|
// migrate the old accepted AC to a new fine-grained one
|
||||||
|
@ -1610,7 +1610,6 @@ func (cp *cwtchPeer) eventHandler() {
|
||||||
conversationInfo, err := cp.FetchConversationInfo(onion)
|
conversationInfo, err := cp.FetchConversationInfo(onion)
|
||||||
|
|
||||||
log.Debugf("confo info lookup newgetval %v %v %v", onion, conversationInfo, err)
|
log.Debugf("confo info lookup newgetval %v %v %v", onion, conversationInfo, err)
|
||||||
// only accepted contacts can look up information
|
|
||||||
if conversationInfo != nil && conversationInfo.GetPeerAC().ExchangeAttributes {
|
if conversationInfo != nil && conversationInfo.GetPeerAC().ExchangeAttributes {
|
||||||
// Type Safe Scoped/Zoned Path
|
// Type Safe Scoped/Zoned Path
|
||||||
zscope := attr.IntoScope(scope)
|
zscope := attr.IntoScope(scope)
|
||||||
|
@ -1683,6 +1682,7 @@ func (cp *cwtchPeer) eventHandler() {
|
||||||
|
|
||||||
timestamp := time.Now().Format(time.RFC3339Nano)
|
timestamp := time.Now().Format(time.RFC3339Nano)
|
||||||
cp.SetConversationAttribute(cid, attr.LocalScope.ConstructScopedZonedPath(attr.ProfileZone.ConstructZonedPath(constants.AttrLastConnectionTime)), timestamp)
|
cp.SetConversationAttribute(cid, attr.LocalScope.ConstructScopedZonedPath(attr.ProfileZone.ConstructZonedPath(constants.AttrLastConnectionTime)), timestamp)
|
||||||
|
|
||||||
} else if connections.ConnectionStateToType()[ev.Data[event.ConnectionState]] == connections.DISCONNECTED {
|
} else if connections.ConnectionStateToType()[ev.Data[event.ConnectionState]] == connections.DISCONNECTED {
|
||||||
ci, err := cp.FetchConversationInfo(handle)
|
ci, err := cp.FetchConversationInfo(handle)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
|
|
@ -58,7 +58,7 @@ func TestFileSharing(t *testing.T) {
|
||||||
os.RemoveAll("cwtch.out.png")
|
os.RemoveAll("cwtch.out.png")
|
||||||
os.RemoveAll("cwtch.out.png.manifest")
|
os.RemoveAll("cwtch.out.png.manifest")
|
||||||
|
|
||||||
log.SetLevel(log.LevelInfo)
|
log.SetLevel(log.LevelDebug)
|
||||||
log.ExcludeFromPattern("tapir")
|
log.ExcludeFromPattern("tapir")
|
||||||
|
|
||||||
os.Mkdir("tordir", 0700)
|
os.Mkdir("tordir", 0700)
|
||||||
|
@ -151,13 +151,6 @@ func TestFileSharing(t *testing.T) {
|
||||||
|
|
||||||
bob.NewContactConversation(alice.GetOnion(), model.DefaultP2PAccessControl(), true)
|
bob.NewContactConversation(alice.GetOnion(), model.DefaultP2PAccessControl(), true)
|
||||||
alice.NewContactConversation(bob.GetOnion(), model.DefaultP2PAccessControl(), true)
|
alice.NewContactConversation(bob.GetOnion(), model.DefaultP2PAccessControl(), true)
|
||||||
alice.PeerWithOnion(bob.GetOnion())
|
|
||||||
|
|
||||||
t.Logf("Waiting for alice and Bob to peer...")
|
|
||||||
waitForPeerPeerConnection(t, alice, bob)
|
|
||||||
alice.AcceptConversation(1)
|
|
||||||
|
|
||||||
t.Logf("Alice and Bob are Connected!!")
|
|
||||||
|
|
||||||
filesharingFunctionality := filesharing.FunctionalityGate()
|
filesharingFunctionality := filesharing.FunctionalityGate()
|
||||||
|
|
||||||
|
@ -167,10 +160,10 @@ func TestFileSharing(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
alice.SendMessage(1, fileSharingMessage)
|
alice.SendMessage(1, fileSharingMessage)
|
||||||
bob.AcceptConversation(1)
|
|
||||||
|
|
||||||
// Wait for the messages to arrive...
|
// Ok this is fun...we just Sent a Message we may not have a connection yet...
|
||||||
time.Sleep(time.Second * 10)
|
// so this test will only pass if sending offline works...
|
||||||
|
waitForPeerPeerConnection(t, bob, alice)
|
||||||
|
|
||||||
bob.SendMessage(1, "this is a test message")
|
bob.SendMessage(1, "this is a test message")
|
||||||
bob.SendMessage(1, "this is another test message")
|
bob.SendMessage(1, "this is another test message")
|
||||||
|
|
Loading…
Reference in New Issue