package model import ( "cwtch.im/cwtch/model/attr" "cwtch.im/cwtch/model/constants" "encoding/json" "git.openprivacy.ca/openprivacy/log" "time" ) // AccessControl is a type determining client assigned authorization to a peer // for a given conversation type AccessControl struct { Blocked bool // Any attempts from this handle to connect are blocked overrides all other settings // Basic Conversation Rights Read bool // Allows a handle to access the conversation Append bool // Allows a handle to append new messages to the conversation AutoConnect bool // Profile should automatically try to connect with peer ExchangeAttributes bool // Profile should automatically exchange attributes like Name, Profile Image, etc. // Extension Related Permissions ShareFiles bool // Allows a handle to share files to a conversation RenderImages bool // Indicates that certain filetypes should be autodownloaded and rendered when shared by this contact } // DefaultP2PAccessControl defaults to a semi-trusted peer with no access to special extensions. func DefaultP2PAccessControl() AccessControl { return AccessControl{Read: true, Append: true, ExchangeAttributes: true, Blocked: false, AutoConnect: true, ShareFiles: false, RenderImages: false} } // AccessControlList represents an access control list for a conversation. Mapping handles to conversation // functions type AccessControlList map[string]AccessControl // Serialize transforms the ACL into json. func (acl *AccessControlList) Serialize() []byte { data, _ := json.Marshal(acl) return data } // DeserializeAccessControlList takes in JSON and returns an AccessControlList func DeserializeAccessControlList(data []byte) (AccessControlList, error) { var acl AccessControlList err := json.Unmarshal(data, &acl) return acl, err } // Attributes a type-driven encapsulation of an Attribute map. type Attributes map[string]string // Serialize transforms an Attributes map into a JSON struct func (a *Attributes) Serialize() []byte { data, _ := json.Marshal(a) return data } // DeserializeAttributes converts a JSON struct into an Attributes map func DeserializeAttributes(data []byte) Attributes { attributes := make(Attributes) err := json.Unmarshal(data, &attributes) if err != nil { log.Error("error deserializing attributes (this is likely a programming error): %v", err) return make(Attributes) } return attributes } // Conversation encapsulates high-level information about a conversation, including the // handle, any set attributes, the access control list associated with the message tree and the // accepted status of the conversation (whether the user has consented into the conversation). type Conversation struct { ID int Handle string Attributes Attributes ACL AccessControlList // Deprecated, please use ACL for permissions related functions Accepted bool } // GetAttribute is a helper function that fetches a conversation attribute by scope, zone and key func (ci *Conversation) GetAttribute(scope attr.Scope, zone attr.Zone, key string) (string, bool) { if value, exists := ci.Attributes[scope.ConstructScopedZonedPath(zone.ConstructZonedPath(key)).ToString()]; exists { return value, true } return "", false } // GetPeerAC returns a suitable Access Control object for a the given peer conversation // If this is called for a group conversation, this method will error and return a safe default AC. func (ci *Conversation) GetPeerAC() AccessControl { if acl, exists := ci.ACL[ci.Handle]; exists { return acl } log.Errorf("attempted to access a Peer Access Control object from %v but peer ACL is undefined. This is likely a programming error", ci.Handle) return DefaultP2PAccessControl() } // IsGroup is a helper attribute that identifies whether a conversation is a legacy group func (ci *Conversation) IsGroup() bool { if _, exists := ci.Attributes[attr.LocalScope.ConstructScopedZonedPath(attr.LegacyGroupZone.ConstructZonedPath(constants.GroupID)).ToString()]; exists { return true } return false } // IsServer is a helper attribute that identifies whether a conversation is with a server func (ci *Conversation) IsServer() bool { if _, exists := ci.Attributes[attr.PublicScope.ConstructScopedZonedPath(attr.ServerKeyZone.ConstructZonedPath(string(BundleType))).ToString()]; exists { return true } return false } // ServerSyncProgress is only valid during a server being in the AUTHENTICATED state and therefor in the syncing process // it returns a double (0-1) representing the estimated progress of the syncing func (ci *Conversation) ServerSyncProgress() float64 { startTimeStr, startExists := ci.Attributes[attr.LocalScope.ConstructScopedZonedPath(attr.LegacyGroupZone.ConstructZonedPath(constants.SyncPreLastMessageTime)).ToString()] recentTimeStr, recentExists := ci.Attributes[attr.LocalScope.ConstructScopedZonedPath(attr.LegacyGroupZone.ConstructZonedPath(constants.SyncMostRecentMessageTime)).ToString()] if !startExists || !recentExists { return 0.0 } startTime, err := time.Parse(startTimeStr, time.RFC3339Nano) if err != nil { return 0.0 } recentTime, err := time.Parse(recentTimeStr, time.RFC3339Nano) if err != nil { return 0.0 } syncRange := time.Since(startTime) pointFromStart := startTime.Sub(recentTime) return pointFromStart.Seconds() / syncRange.Seconds() } // ConversationMessage bundles an instance of a conversation message row type ConversationMessage struct { ID int Body string Attr Attributes Signature string ContentHash string }