Enforced Zoned Attribute Lookups #394
|
@ -13,7 +13,7 @@ import (
|
|||
"git.openprivacy.ca/openprivacy/log"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
path "path/filepath"
|
||||
"strconv"
|
||||
"sync"
|
||||
)
|
||||
|
|
|
@ -9,7 +9,7 @@ import (
|
|||
"git.openprivacy.ca/cwtch.im/tapir/primitives"
|
||||
"git.openprivacy.ca/openprivacy/connectivity"
|
||||
"git.openprivacy.ca/openprivacy/log"
|
||||
"path"
|
||||
path "path/filepath"
|
||||
"strconv"
|
||||
"sync"
|
||||
)
|
||||
|
|
|
@ -41,9 +41,15 @@ type OverlayMessage struct {
|
|||
// DownloadFile given a profile, a conversation handle and a file sharing key, start off a download process
|
||||
// to downloadFilePath
|
||||
func (f *Functionality) DownloadFile(profile peer.CwtchPeer, handle string, downloadFilePath string, manifestFilePath string, key string) {
|
||||
profile.SetAttribute(attr.GetLocalScope(fmt.Sprintf("%s.manifest", key)), manifestFilePath)
|
||||
profile.SetAttribute(attr.GetLocalScope(key), downloadFilePath)
|
||||
profile.SendGetValToPeer(handle, attr.PublicScope, fmt.Sprintf("%s.manifest.size", key))
|
||||
|
||||
// Store local.filesharing.filekey.manifest as the location of the manifest
|
||||
profile.SetScopedZonedAttribute(attr.LocalScope, attr.FilesharingZone, fmt.Sprintf("%s.manifest", key), manifestFilePath)
|
||||
|
||||
// Store local.filesharing.filekey as the location of the download
|
||||
profile.SetScopedZonedAttribute(attr.LocalScope, attr.FilesharingZone, key, downloadFilePath)
|
||||
|
||||
// Get the value of conversation.filesharing.filekey.manifest.size from `handle`
|
||||
profile.SendScopedZonedGetValToContact(handle, attr.ConversationScope, attr.FilesharingZone, fmt.Sprintf("%s.manifest.size", key))
|
||||
}
|
||||
|
||||
// ShareFile given a profile and a conversation handle, sets up a file sharing process to share the file
|
||||
|
@ -83,7 +89,7 @@ func (f *Functionality) ShareFile(filepath string, profile peer.CwtchPeer, handl
|
|||
// manifest.FileName gets redacted in filesharing_subsystem (to remove the system-specific file hierarchy),
|
||||
// but we need to *store* the full path because the sender also uses it to locate the file
|
||||
lenDiff := len(filepath) - len(path.Base(filepath))
|
||||
profile.SetAttribute(attr.GetPublicScope(fmt.Sprintf("%s.manifest.size", key)), strconv.Itoa(int(math.Ceil(float64(len(serializedManifest)-lenDiff)/float64(files.DefaultChunkSize)))))
|
||||
profile.SetScopedZonedAttribute(attr.ConversationScope, attr.FilesharingZone, fmt.Sprintf("%s.manifest.size", key), strconv.Itoa(int(math.Ceil(float64(len(serializedManifest)-lenDiff)/float64(files.DefaultChunkSize)))))
|
||||
|
||||
profile.ShareFile(key, string(serializedManifest))
|
||||
|
||||
|
|
|
@ -1,9 +1,5 @@
|
|||
package attr
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
/*
|
||||
Scope model for peer attributes and requests
|
||||
|
||||
|
@ -16,45 +12,79 @@ values stored in the LocalScope.
|
|||
|
||||
*/
|
||||
|
||||
// Scope strongly types Scope strings
|
||||
type Scope string
|
||||
|
||||
// ScopedZonedPath typed path with a scope and a zone
|
||||
type ScopedZonedPath string
|
||||
|
||||
// scopes for attributes
|
||||
const (
|
||||
// on a peer, local and peer supplied data
|
||||
LocalScope = "local"
|
||||
PeerScope = "peer"
|
||||
LocalScope = Scope("local")
|
||||
PeerScope = Scope("peer")
|
||||
ConversationScope = Scope("conversation")
|
||||
|
||||
// on a local profile, public data and private settings
|
||||
PublicScope = "public"
|
||||
SettingsScope = "settings"
|
||||
PublicScope = Scope("public")
|
||||
|
||||
UnknownScope = Scope("unknown")
|
||||
)
|
||||
|
||||
// Separator for scope and the rest of path
|
||||
const Separator = "."
|
||||
|
||||
// GetPublicScope takes a path and attaches the pubic scope to it
|
||||
func GetPublicScope(path string) string {
|
||||
return PublicScope + Separator + path
|
||||
// IntoScope converts a string to a Scope
|
||||
func IntoScope(scope string) Scope {
|
||||
sarah marked this conversation as resolved
|
||||
switch scope {
|
||||
case "local":
|
||||
return LocalScope
|
||||
case "peer":
|
||||
return PeerScope
|
||||
case "conversation":
|
||||
return ConversationScope
|
||||
case "public":
|
||||
return PublicScope
|
||||
}
|
||||
return UnknownScope
|
||||
}
|
||||
|
||||
sarah marked this conversation as resolved
dan
commented
IsLocal() bool ? IsLocal() bool ?
|
||||
// GetSettingsScope takes a path and attaches the settings scope to it
|
||||
func GetSettingsScope(path string) string {
|
||||
return SettingsScope + Separator + path
|
||||
// ConstructScopedZonedPath enforces a scope over a zoned path
|
||||
func (scope Scope) ConstructScopedZonedPath(zonedPath ZonedPath) ScopedZonedPath {
|
||||
return ScopedZonedPath(string(scope) + Separator + string(zonedPath))
|
||||
}
|
||||
|
||||
// ToString converts a ScopedZonedPath to a string
|
||||
func (szp ScopedZonedPath) ToString() string {
|
||||
return string(szp)
|
||||
}
|
||||
|
||||
// IsLocal returns true if the scope is a local scope
|
||||
func (scope Scope) IsLocal() bool {
|
||||
return scope == LocalScope
|
||||
}
|
||||
|
||||
// IsPeer returns true if the scope is a peer scope
|
||||
func (scope Scope) IsPeer() bool {
|
||||
return scope == PeerScope
|
||||
}
|
||||
|
||||
// IsPublic returns true if the scope is a public scope
|
||||
func (scope Scope) IsPublic() bool {
|
||||
return scope == PublicScope
|
||||
}
|
||||
|
||||
// IsConversation returns true if the scope is a conversation scope
|
||||
func (scope Scope) IsConversation() bool {
|
||||
return scope == ConversationScope
|
||||
}
|
||||
|
||||
// GetLocalScope takes a path and attaches the local scope to it
|
||||
func GetLocalScope(path string) string {
|
||||
return LocalScope + Separator + path
|
||||
return string(LocalScope) + Separator + path
|
||||
}
|
||||
|
||||
// GetPeerScope takes a path and attaches the peer scope to it
|
||||
func GetPeerScope(path string) string {
|
||||
return PeerScope + Separator + path
|
||||
}
|
||||
|
||||
// GetScopePath take a full path and returns the scope and the scope-less path
|
||||
func GetScopePath(fullPath string) (string, string) {
|
||||
parts := strings.SplitN(fullPath, Separator, 1)
|
||||
if len(parts) != 2 {
|
||||
return "", ""
|
||||
}
|
||||
return parts[0], parts[1]
|
||||
return string(PeerScope) + Separator + path
|
||||
}
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
package attr
|
||||
|
||||
import (
|
||||
"git.openprivacy.ca/openprivacy/log"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Zone forces attributes to belong to a given subsystem e.g profile or filesharing
|
||||
// Note: Zone is different from Scope which deals with public visibility of a given attribute
|
||||
type Zone string
|
||||
|
||||
// ZonedPath explicitly types paths that contain a zone for strongly typed APIs
|
||||
type ZonedPath string
|
||||
|
||||
const (
|
||||
|
||||
// ProfileZone for attributes related to profile details like name and profile image
|
||||
ProfileZone = Zone("profile")
|
||||
|
||||
// FilesharingZone for attributes related to file sharing
|
||||
FilesharingZone = Zone("filesharing")
|
||||
|
||||
// UnknownZone is a catch all useful for error handling
|
||||
UnknownZone = Zone("unknown")
|
||||
)
|
||||
|
||||
// ConstructZonedPath takes a path and attaches a zone to it.
|
||||
// Note that this returns a ZonedPath which isn't directly usable, it must be given to ConstructScopedZonedPath
|
||||
// in order to be realized into an actual attribute path.
|
||||
func (zone Zone) ConstructZonedPath(path string) ZonedPath {
|
||||
return ZonedPath(string(zone) + Separator + path)
|
||||
}
|
||||
|
||||
// ParseZone takes in an untyped string and returns an explicit Zone along with the rest of the untyped path
|
||||
func ParseZone(path string) (Zone, string) {
|
||||
parts := strings.SplitN(path, Separator, 2)
|
||||
|
||||
log.Debugf("parsed zone: %v %v", parts, path)
|
||||
|
||||
if len(parts) != 2 {
|
||||
return UnknownZone, ""
|
||||
}
|
||||
|
||||
switch Zone(parts[0]) {
|
||||
case ProfileZone:
|
||||
return ProfileZone, parts[1]
|
||||
case FilesharingZone:
|
||||
return FilesharingZone, parts[1]
|
||||
default:
|
||||
return UnknownZone, parts[1]
|
||||
}
|
||||
}
|
|
@ -54,6 +54,41 @@ type cwtchPeer struct {
|
|||
eventBus event.Manager
|
||||
}
|
||||
|
||||
func (cp *cwtchPeer) SendScopedZonedGetValToContact(handle string, scope attr.Scope, zone attr.Zone, path string) {
|
||||
event := event.NewEventList(event.SendGetValMessageToPeer, event.RemotePeer, handle, event.Scope, string(scope), event.Path, string(zone.ConstructZonedPath(path)))
|
||||
cp.eventBus.Publish(event)
|
||||
}
|
||||
|
||||
func (cp *cwtchPeer) GetScopedZonedAttribute(scope attr.Scope, zone attr.Zone, key string) (string, bool) {
|
||||
cp.mutex.Lock()
|
||||
defer cp.mutex.Unlock()
|
||||
scopedZonedKey := scope.ConstructScopedZonedPath(zone.ConstructZonedPath(key))
|
||||
|
||||
log.Debugf("looking up attribute %v %v %v (%v)", scope, zone, key, scopedZonedKey)
|
||||
|
||||
if val, exists := cp.Profile.GetAttribute(scopedZonedKey.ToString()); exists {
|
||||
return val, true
|
||||
}
|
||||
|
||||
if key == attr.GetLocalScope("name") {
|
||||
return cp.Profile.Name, true
|
||||
}
|
||||
|
||||
return "", false
|
||||
}
|
||||
|
||||
func (cp *cwtchPeer) SetScopedZonedAttribute(scope attr.Scope, zone attr.Zone, key string, value string) {
|
||||
cp.mutex.Lock()
|
||||
scopedZonedKey := scope.ConstructScopedZonedPath(zone.ConstructZonedPath(key))
|
||||
log.Debugf("storing attribute: %v = %v", scopedZonedKey, value)
|
||||
cp.Profile.SetAttribute(scopedZonedKey.ToString(), value)
|
||||
defer cp.mutex.Unlock()
|
||||
cp.eventBus.Publish(event.NewEvent(event.SetAttribute, map[event.Field]string{
|
||||
event.Key: scopedZonedKey.ToString(),
|
||||
event.Data: value,
|
||||
}))
|
||||
}
|
||||
|
||||
// SendMessage is a higher level that merges sending messages to contacts and group handles
|
||||
// If you try to send a message to a handle that doesn't exist, malformed or an incorrect type then
|
||||
// this function will error
|
||||
|
@ -163,15 +198,16 @@ type ModifyServers interface {
|
|||
type SendMessages interface {
|
||||
SendMessage(handle string, message string) error
|
||||
|
||||
// Deprecated: is unsafe
|
||||
SendGetValToPeer(string, string, string)
|
||||
|
||||
SendScopedZonedGetValToContact(handle string, scope attr.Scope, zone attr.Zone, key string)
|
||||
|
||||
// Deprecated
|
||||
SendMessageToPeer(string, string) string
|
||||
dan
commented
this looks like it has lots of use? we want to create a issue to start on that? this looks like it has lots of use? we want to create a issue to start on that?
sarah
commented
Created: #395 Created: https://git.openprivacy.ca/cwtch.im/cwtch/issues/395
|
||||
|
||||
// TODO This should probably not be exposed
|
||||
StoreMessage(onion string, messageTxt string, sent time.Time)
|
||||
|
||||
// TODO Extract once groups are stable
|
||||
// TODO
|
||||
// Deprecated use overlays instead
|
||||
InviteOnionToGroup(string, string) error
|
||||
sarah marked this conversation as resolved
dan
commented
What is still using this?
What is still using this?
```case event.NewMessageFromPeer:``` is but it can at least be dropped from the interface than as that's internal only. it looks like libcwtch-go doesn't use it. so i think it can be droped from the interface?
|
||||
}
|
||||
|
||||
|
@ -202,9 +238,18 @@ type CwtchPeer interface {
|
|||
|
||||
// Relating to local attributes
|
||||
GetOnion() string
|
||||
|
||||
// Deprecated: SetAttribute is Unsafe used SetScopedZonedAttribute Instead
|
||||
SetAttribute(string, string)
|
||||
|
||||
// Deprecated: GetAttribute is Unsafe used GetScopedZonedAttribute Instead
|
||||
GetAttribute(string) (string, bool)
|
||||
|
||||
// scope.zone.key = value
|
||||
SetScopedZonedAttribute(scope attr.Scope, zone attr.Zone, key string, value string)
|
||||
// scope.zone.key = value
|
||||
GetScopedZonedAttribute(scope attr.Scope, zone attr.Zone, key string) (string, bool)
|
||||
|
||||
ReadContacts
|
||||
ModifyContacts
|
||||
|
||||
|
@ -651,6 +696,7 @@ func (cp *cwtchPeer) SetAttribute(key string, val string) {
|
|||
}))
|
||||
}
|
||||
|
||||
// Deprecated
|
||||
// GetAttribute gets an attribute for the profile
|
||||
func (cp *cwtchPeer) GetAttribute(key string) (string, bool) {
|
||||
cp.mutex.Lock()
|
||||
|
@ -726,7 +772,7 @@ func (cp *cwtchPeer) Shutdown() {
|
|||
cp.queue.Shutdown()
|
||||
}
|
||||
|
||||
func (cp *cwtchPeer) StoreMessage(onion string, messageTxt string, sent time.Time) {
|
||||
func (cp *cwtchPeer) storeMessage(onion string, messageTxt string, sent time.Time) {
|
||||
if cp.GetContact(onion) == nil {
|
||||
cp.AddContact(onion, onion, model.AuthUnknown)
|
||||
}
|
||||
|
@ -779,7 +825,7 @@ func (cp *cwtchPeer) eventHandler() {
|
|||
}
|
||||
case event.NewMessageFromPeer: //event.TimestampReceived, event.RemotePeer, event.Data
|
||||
ts, _ := time.Parse(time.RFC3339Nano, ev.Data[event.TimestampReceived])
|
||||
cp.StoreMessage(ev.Data[event.RemotePeer], ev.Data[event.Data], ts)
|
||||
cp.storeMessage(ev.Data[event.RemotePeer], ev.Data[event.Data], ts)
|
||||
|
||||
case event.PeerAcknowledgement:
|
||||
cp.mutex.Lock()
|
||||
|
@ -811,12 +857,14 @@ func (cp *cwtchPeer) eventHandler() {
|
|||
scope := ev.Data[event.Scope]
|
||||
path := ev.Data[event.Path]
|
||||
|
||||
log.Debugf("NewGetValMessageFromPeer for %v%v from %v\n", scope, path, onion)
|
||||
log.Debugf("NewGetValMessageFromPeer for %v.%v from %v\n", scope, path, onion)
|
||||
|
||||
remotePeer := cp.GetContact(onion)
|
||||
if remotePeer != nil && remotePeer.Authorization == model.AuthApproved {
|
||||
if scope == attr.PublicScope {
|
||||
val, exists := cp.GetAttribute(attr.GetPublicScope(path))
|
||||
scope := attr.IntoScope(scope)
|
||||
if scope.IsPublic() || scope.IsConversation() {
|
||||
zone, path := attr.ParseZone(path)
|
||||
val, exists := cp.GetScopedZonedAttribute(scope, zone, path)
|
||||
resp := event.NewEvent(event.SendRetValMessageToPeer, map[event.Field]string{event.RemotePeer: onion, event.Exists: strconv.FormatBool(exists)})
|
||||
resp.EventID = ev.EventID
|
||||
if exists {
|
||||
|
@ -838,9 +886,9 @@ func (cp *cwtchPeer) eventHandler() {
|
|||
fileKey := ev.Data[event.FileKey]
|
||||
serializedManifest := ev.Data[event.SerializedManifest]
|
||||
|
||||
manifestFilePath, exists := cp.GetAttribute(attr.GetLocalScope(fmt.Sprintf("%v.manifest", fileKey)))
|
||||
manifestFilePath, exists := cp.GetScopedZonedAttribute(attr.LocalScope, attr.FilesharingZone, fmt.Sprintf("%v.manifest", fileKey))
|
||||
if exists {
|
||||
downloadFilePath, exists := cp.GetAttribute(attr.GetLocalScope(fileKey))
|
||||
downloadFilePath, exists := cp.GetScopedZonedAttribute(attr.LocalScope, attr.FilesharingZone, fileKey)
|
||||
if exists {
|
||||
log.Debugf("downloading manifest to %v, file to %v", manifestFilePath, downloadFilePath)
|
||||
var manifest files.Manifest
|
||||
|
@ -884,22 +932,27 @@ func (cp *cwtchPeer) eventHandler() {
|
|||
exists, _ := strconv.ParseBool(ev.Data[event.Exists])
|
||||
log.Debugf("NewRetValMessageFromPeer %v %v%v %v %v\n", onion, scope, path, exists, val)
|
||||
if exists {
|
||||
if scope == attr.PublicScope {
|
||||
if strings.HasSuffix(path, ".manifest.size") {
|
||||
fileKey := strings.Replace(path, ".manifest.size", "", 1)
|
||||
size, err := strconv.Atoi(val)
|
||||
// if size is valid and below the maximum size for a manifest
|
||||
// this is to prevent malicious sharers from using large amounts of memory when distributing
|
||||
// a manifest as we reconstruct this in-memory
|
||||
if err == nil && size < files.MaxManifestSize {
|
||||
cp.eventBus.Publish(event.NewEvent(event.ManifestSizeReceived, map[event.Field]string{event.FileKey: fileKey, event.ManifestSize: val, event.Handle: onion}))
|
||||
} else {
|
||||
cp.eventBus.Publish(event.NewEvent(event.ManifestError, map[event.Field]string{event.FileKey: fileKey, event.Handle: onion}))
|
||||
}
|
||||
|
||||
// Handle File Sharing Metadata
|
||||
// TODO This probably should be broken out to it's own code..
|
||||
zone, path := attr.ParseZone(path)
|
||||
if attr.Scope(scope).IsConversation() && zone == attr.FilesharingZone && strings.HasSuffix(path, ".manifest.size") {
|
||||
fileKey := strings.Replace(path, ".manifest.size", "", 1)
|
||||
size, err := strconv.Atoi(val)
|
||||
// if size is valid and below the maximum size for a manifest
|
||||
// this is to prevent malicious sharers from using large amounts of memory when distributing
|
||||
// a manifest as we reconstruct this in-memory
|
||||
if err == nil && size < files.MaxManifestSize {
|
||||
cp.eventBus.Publish(event.NewEvent(event.ManifestSizeReceived, map[event.Field]string{event.FileKey: fileKey, event.ManifestSize: val, event.Handle: onion}))
|
||||
} else {
|
||||
cp.SetContactAttribute(onion, attr.GetPeerScope(path), val)
|
||||
cp.eventBus.Publish(event.NewEvent(event.ManifestError, map[event.Field]string{event.FileKey: fileKey, event.Handle: onion}))
|
||||
}
|
||||
}
|
||||
|
||||
// Allow public profile parameters to be added as peer specific attributes...
|
||||
if attr.Scope(scope).IsPublic() && zone == attr.ProfileZone {
|
||||
cp.SetContactAttribute(onion, attr.GetPeerScope(path), val)
|
||||
}
|
||||
}
|
||||
case event.PeerStateChange:
|
||||
cp.mutex.Lock()
|
||||
|
|
|
@ -154,17 +154,17 @@ func TestCwtchPeerIntegration(t *testing.T) {
|
|||
|
||||
alice := utils.WaitGetPeer(app, "alice")
|
||||
fmt.Println("Alice created:", alice.GetOnion())
|
||||
alice.SetAttribute(attr.GetPublicScope("name"), "Alice")
|
||||
alice.SetScopedZonedAttribute(attr.PublicScope, attr.ProfileZone, "name", "Alice")
|
||||
alice.AutoHandleEvents([]event.Type{event.PeerStateChange, event.ServerStateChange, event.NewGroupInvite, event.NewRetValMessageFromPeer})
|
||||
|
||||
bob := utils.WaitGetPeer(app, "bob")
|
||||
fmt.Println("Bob created:", bob.GetOnion())
|
||||
bob.SetAttribute(attr.GetPublicScope("name"), "Bob")
|
||||
bob.SetScopedZonedAttribute(attr.PublicScope, attr.ProfileZone, "name", "Bob")
|
||||
bob.AutoHandleEvents([]event.Type{event.PeerStateChange, event.ServerStateChange, event.NewGroupInvite, event.NewRetValMessageFromPeer})
|
||||
|
||||
carol := utils.WaitGetPeer(appClient, "carol")
|
||||
fmt.Println("Carol created:", carol.GetOnion())
|
||||
carol.SetAttribute(attr.GetPublicScope("name"), "Carol")
|
||||
carol.SetScopedZonedAttribute(attr.PublicScope, attr.ProfileZone, "name", "Carol")
|
||||
carol.AutoHandleEvents([]event.Type{event.PeerStateChange, event.ServerStateChange, event.NewGroupInvite, event.NewRetValMessageFromPeer})
|
||||
|
||||
app.LaunchPeers()
|
||||
|
@ -217,11 +217,11 @@ func TestCwtchPeerIntegration(t *testing.T) {
|
|||
|
||||
fmt.Println("Alice and Bob getVal public.name...")
|
||||
|
||||
alice.SendGetValToPeer(bob.GetOnion(), attr.PublicScope, "name")
|
||||
bob.SendGetValToPeer(alice.GetOnion(), attr.PublicScope, "name")
|
||||
alice.SendScopedZonedGetValToContact(bob.GetOnion(), attr.PublicScope, attr.ProfileZone, "name")
|
||||
bob.SendScopedZonedGetValToContact(alice.GetOnion(), attr.PublicScope, attr.ProfileZone, "name")
|
||||
|
||||
alice.SendGetValToPeer(carol.GetOnion(), attr.PublicScope, "name")
|
||||
carol.SendGetValToPeer(alice.GetOnion(), attr.PublicScope, "name")
|
||||
alice.SendScopedZonedGetValToContact(carol.GetOnion(), attr.PublicScope, attr.ProfileZone, "name")
|
||||
carol.SendScopedZonedGetValToContact(alice.GetOnion(), attr.PublicScope, attr.ProfileZone, "name")
|
||||
|
||||
time.Sleep(10 * time.Second)
|
||||
|
||||
|
|
|
@ -40,8 +40,8 @@ func waitForPeerPeerConnection(t *testing.T, peera peer.CwtchPeer, peerb peer.Cw
|
|||
time.Sleep(time.Second * 5)
|
||||
continue
|
||||
} else {
|
||||
peerAName, _ := peera.GetAttribute(attr.GetLocalScope("name"))
|
||||
peerBName, _ := peerb.GetAttribute(attr.GetLocalScope("name"))
|
||||
peerAName, _ := peera.GetScopedZonedAttribute(attr.LocalScope, attr.ProfileZone, "name")
|
||||
peerBName, _ := peerb.GetScopedZonedAttribute(attr.LocalScope, attr.ProfileZone, "name")
|
||||
fmt.Printf("%v CONNECTED and AUTHED to %v\n", peerAName, peerBName)
|
||||
break
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
if we're going type heavy dont we want it to return a like ScopedZonedPath?