Enforced Zoned Attribute Lookups #394

Merged
dan merged 2 commits from zoned_attributes into master 2021-10-08 20:54:07 +00:00
8 changed files with 179 additions and 59 deletions
Showing only changes of commit aec3c40180 - Show all commits

View File

@ -13,7 +13,7 @@ import (
"git.openprivacy.ca/openprivacy/log" "git.openprivacy.ca/openprivacy/log"
"io/ioutil" "io/ioutil"
"os" "os"
"path" path "path/filepath"
"strconv" "strconv"
"sync" "sync"
) )

View File

@ -9,7 +9,7 @@ import (
"git.openprivacy.ca/cwtch.im/tapir/primitives" "git.openprivacy.ca/cwtch.im/tapir/primitives"
"git.openprivacy.ca/openprivacy/connectivity" "git.openprivacy.ca/openprivacy/connectivity"
"git.openprivacy.ca/openprivacy/log" "git.openprivacy.ca/openprivacy/log"
"path" path "path/filepath"
"strconv" "strconv"
"sync" "sync"
) )

View File

@ -41,9 +41,15 @@ type OverlayMessage struct {
// DownloadFile given a profile, a conversation handle and a file sharing key, start off a download process // DownloadFile given a profile, a conversation handle and a file sharing key, start off a download process
// to downloadFilePath // to downloadFilePath
func (f *Functionality) DownloadFile(profile peer.CwtchPeer, handle string, downloadFilePath string, manifestFilePath string, key string) { 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) // Store local.filesharing.filekey.manifest as the location of the manifest
profile.SendGetValToPeer(handle, attr.PublicScope, fmt.Sprintf("%s.manifest.size", key)) 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 // 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), // 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 // 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)) 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)) profile.ShareFile(key, string(serializedManifest))

View File

@ -1,9 +1,5 @@
package attr package attr
import (
"strings"
)
/* /*
Scope model for peer attributes and requests Scope model for peer attributes and requests
@ -16,45 +12,54 @@ values stored in the LocalScope.
*/ */
// Scope strongly types Scope strings
type Scope string
// scopes for attributes // scopes for attributes
const ( const (
// on a peer, local and peer supplied data // on a peer, local and peer supplied data
LocalScope = "local" LocalScope = Scope("local")
PeerScope = "peer" PeerScope = Scope("peer")
ConversationScope = Scope("conversation")
// on a local profile, public data and private settings // on a local profile, public data and private settings
PublicScope = "public" PublicScope = Scope("public")
SettingsScope = "settings"
) )
// Separator for scope and the rest of path // Separator for scope and the rest of path
const Separator = "." const Separator = "."
// GetPublicScope takes a path and attaches the pubic scope to it // IntoScope converts a string to a Scope
func GetPublicScope(path string) string { func IntoScope(scope string) Scope {
return PublicScope + Separator + path return Scope(scope)
} }
// GetSettingsScope takes a path and attaches the settings scope to it // ConstructScopedZonedPath enforces a scope over a zoned path
func GetSettingsScope(path string) string { func (scope Scope) ConstructScopedZonedPath(zonedPath ZonedPath) string {
sarah marked this conversation as resolved
Review

if we're going type heavy dont we want it to return a like ScopedZonedPath?

if we're going type heavy dont we want it to return a like ScopedZonedPath?
return SettingsScope + Separator + path return string(scope) + Separator + string(zonedPath)
}
// 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
}
sarah marked this conversation as resolved
Review

IsLocal() bool ?

IsLocal() bool ?
// GetPublicScope takes a path and attaches the pubic scope to it
func GetPublicScope(path string) string {
return string(PublicScope) + Separator + path
} }
// GetLocalScope takes a path and attaches the local scope to it // GetLocalScope takes a path and attaches the local scope to it
func GetLocalScope(path string) string { 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 // GetPeerScope takes a path and attaches the peer scope to it
func GetPeerScope(path string) string { func GetPeerScope(path string) string {
return PeerScope + Separator + path return string(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]
} }

52
model/attr/zone.go Normal file
View File

@ -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")
)
// EnforceZone 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) EnforceZone(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]
}
}

View File

@ -54,6 +54,41 @@ type cwtchPeer struct {
eventBus event.Manager 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.EnforceZone(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.EnforceZone(key))
log.Debugf("looking up attribute %v %v %v (%v)", scope, zone, key, scopedZonedKey)
if val, exists := cp.Profile.GetAttribute(scopedZonedKey); 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.EnforceZone(key))
log.Debugf("storing attribute: %v = %v", scopedZonedKey, value)
cp.Profile.SetAttribute(scopedZonedKey, value)
defer cp.mutex.Unlock()
cp.eventBus.Publish(event.NewEvent(event.SetAttribute, map[event.Field]string{
event.Key: scopedZonedKey,
event.Data: value,
}))
}
// SendMessage is a higher level that merges sending messages to contacts and group handles // 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 // If you try to send a message to a handle that doesn't exist, malformed or an incorrect type then
// this function will error // this function will error
@ -163,15 +198,20 @@ type ModifyServers interface {
type SendMessages interface { type SendMessages interface {
SendMessage(handle string, message string) error SendMessage(handle string, message string) error
// Deprecated: is unsafe
SendGetValToPeer(string, string, string) SendGetValToPeer(string, string, string)
SendScopedZonedGetValToContact(handle string, scope attr.Scope, zone attr.Zone, key string)
// Deprecated // Deprecated
SendMessageToPeer(string, string) string SendMessageToPeer(string, string) string
Review

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?
Review

Created: #395

Created: https://git.openprivacy.ca/cwtch.im/cwtch/issues/395
// TODO This should probably not be exposed // TODO This should probably not be exposed
// Deprecated do not use this to store messages
StoreMessage(onion string, messageTxt string, sent time.Time) StoreMessage(onion string, messageTxt string, sent time.Time)
sarah marked this conversation as resolved
Review

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?

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?
// TODO Extract once groups are stable // TODO
// Deprecated use overlays instead
InviteOnionToGroup(string, string) error InviteOnionToGroup(string, string) error
} }
@ -202,9 +242,18 @@ type CwtchPeer interface {
// Relating to local attributes // Relating to local attributes
GetOnion() string GetOnion() string
// Deprecated: SetAttribute is Unsafe used SetScopedZonedAttribute Instead
SetAttribute(string, string) SetAttribute(string, string)
// Deprecated: GetAttribute is Unsafe used GetScopedZonedAttribute Instead
GetAttribute(string) (string, bool) 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 ReadContacts
ModifyContacts ModifyContacts
@ -651,6 +700,7 @@ func (cp *cwtchPeer) SetAttribute(key string, val string) {
})) }))
} }
// Deprecated
// GetAttribute gets an attribute for the profile // GetAttribute gets an attribute for the profile
func (cp *cwtchPeer) GetAttribute(key string) (string, bool) { func (cp *cwtchPeer) GetAttribute(key string) (string, bool) {
cp.mutex.Lock() cp.mutex.Lock()
@ -811,12 +861,14 @@ func (cp *cwtchPeer) eventHandler() {
scope := ev.Data[event.Scope] scope := ev.Data[event.Scope]
path := ev.Data[event.Path] 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) remotePeer := cp.GetContact(onion)
if remotePeer != nil && remotePeer.Authorization == model.AuthApproved { if remotePeer != nil && remotePeer.Authorization == model.AuthApproved {
if scope == attr.PublicScope { scope := attr.IntoScope(scope)
val, exists := cp.GetAttribute(attr.GetPublicScope(path)) 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 := event.NewEvent(event.SendRetValMessageToPeer, map[event.Field]string{event.RemotePeer: onion, event.Exists: strconv.FormatBool(exists)})
resp.EventID = ev.EventID resp.EventID = ev.EventID
if exists { if exists {
@ -838,9 +890,9 @@ func (cp *cwtchPeer) eventHandler() {
fileKey := ev.Data[event.FileKey] fileKey := ev.Data[event.FileKey]
serializedManifest := ev.Data[event.SerializedManifest] 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 { if exists {
downloadFilePath, exists := cp.GetAttribute(attr.GetLocalScope(fileKey)) downloadFilePath, exists := cp.GetScopedZonedAttribute(attr.LocalScope, attr.FilesharingZone, fileKey)
if exists { if exists {
log.Debugf("downloading manifest to %v, file to %v", manifestFilePath, downloadFilePath) log.Debugf("downloading manifest to %v, file to %v", manifestFilePath, downloadFilePath)
var manifest files.Manifest var manifest files.Manifest
@ -884,22 +936,27 @@ func (cp *cwtchPeer) eventHandler() {
exists, _ := strconv.ParseBool(ev.Data[event.Exists]) exists, _ := strconv.ParseBool(ev.Data[event.Exists])
log.Debugf("NewRetValMessageFromPeer %v %v%v %v %v\n", onion, scope, path, exists, val) log.Debugf("NewRetValMessageFromPeer %v %v%v %v %v\n", onion, scope, path, exists, val)
if exists { if exists {
if scope == attr.PublicScope {
if strings.HasSuffix(path, ".manifest.size") { // Handle File Sharing Metadata
fileKey := strings.Replace(path, ".manifest.size", "", 1) // TODO This probably should be broken out to it's own code..
size, err := strconv.Atoi(val) zone, path := attr.ParseZone(path)
// if size is valid and below the maximum size for a manifest if attr.Scope(scope).IsConversation() && zone == attr.FilesharingZone && strings.HasSuffix(path, ".manifest.size") {
// this is to prevent malicious sharers from using large amounts of memory when distributing fileKey := strings.Replace(path, ".manifest.size", "", 1)
// a manifest as we reconstruct this in-memory size, err := strconv.Atoi(val)
if err == nil && size < files.MaxManifestSize { // if size is valid and below the maximum size for a manifest
cp.eventBus.Publish(event.NewEvent(event.ManifestSizeReceived, map[event.Field]string{event.FileKey: fileKey, event.ManifestSize: val, event.Handle: onion})) // this is to prevent malicious sharers from using large amounts of memory when distributing
} else { // a manifest as we reconstruct this in-memory
cp.eventBus.Publish(event.NewEvent(event.ManifestError, map[event.Field]string{event.FileKey: fileKey, event.Handle: onion})) 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 { } 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: case event.PeerStateChange:
cp.mutex.Lock() cp.mutex.Lock()

View File

@ -154,17 +154,17 @@ func TestCwtchPeerIntegration(t *testing.T) {
alice := utils.WaitGetPeer(app, "alice") alice := utils.WaitGetPeer(app, "alice")
fmt.Println("Alice created:", alice.GetOnion()) 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}) alice.AutoHandleEvents([]event.Type{event.PeerStateChange, event.ServerStateChange, event.NewGroupInvite, event.NewRetValMessageFromPeer})
bob := utils.WaitGetPeer(app, "bob") bob := utils.WaitGetPeer(app, "bob")
fmt.Println("Bob created:", bob.GetOnion()) 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}) bob.AutoHandleEvents([]event.Type{event.PeerStateChange, event.ServerStateChange, event.NewGroupInvite, event.NewRetValMessageFromPeer})
carol := utils.WaitGetPeer(appClient, "carol") carol := utils.WaitGetPeer(appClient, "carol")
fmt.Println("Carol created:", carol.GetOnion()) 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}) carol.AutoHandleEvents([]event.Type{event.PeerStateChange, event.ServerStateChange, event.NewGroupInvite, event.NewRetValMessageFromPeer})
app.LaunchPeers() app.LaunchPeers()
@ -217,11 +217,11 @@ func TestCwtchPeerIntegration(t *testing.T) {
fmt.Println("Alice and Bob getVal public.name...") fmt.Println("Alice and Bob getVal public.name...")
alice.SendGetValToPeer(bob.GetOnion(), attr.PublicScope, "name") alice.SendScopedZonedGetValToContact(bob.GetOnion(), attr.PublicScope, attr.ProfileZone, "name")
bob.SendGetValToPeer(alice.GetOnion(), attr.PublicScope, "name") bob.SendScopedZonedGetValToContact(alice.GetOnion(), attr.PublicScope, attr.ProfileZone, "name")
alice.SendGetValToPeer(carol.GetOnion(), attr.PublicScope, "name") alice.SendScopedZonedGetValToContact(carol.GetOnion(), attr.PublicScope, attr.ProfileZone, "name")
carol.SendGetValToPeer(alice.GetOnion(), attr.PublicScope, "name") carol.SendScopedZonedGetValToContact(alice.GetOnion(), attr.PublicScope, attr.ProfileZone, "name")
time.Sleep(10 * time.Second) time.Sleep(10 * time.Second)

View File

@ -40,8 +40,8 @@ func waitForPeerPeerConnection(t *testing.T, peera peer.CwtchPeer, peerb peer.Cw
time.Sleep(time.Second * 5) time.Sleep(time.Second * 5)
continue continue
} else { } else {
peerAName, _ := peera.GetAttribute(attr.GetLocalScope("name")) peerAName, _ := peera.GetScopedZonedAttribute(attr.LocalScope, attr.ProfileZone, "name")
peerBName, _ := peerb.GetAttribute(attr.GetLocalScope("name")) peerBName, _ := peerb.GetScopedZonedAttribute(attr.LocalScope, attr.ProfileZone, "name")
fmt.Printf("%v CONNECTED and AUTHED to %v\n", peerAName, peerBName) fmt.Printf("%v CONNECTED and AUTHED to %v\n", peerAName, peerBName)
break break
} }