diff --git a/app/app.go b/app/app.go index 3f9ceb2..b21821d 100644 --- a/app/app.go +++ b/app/app.go @@ -121,10 +121,10 @@ func (app *application) CreateTaggedPeer(name string, password string, tag strin p := peer.FromProfile(pc) p.Init(app.eventBuses[profile.Onion]) - blockedPeers := profile.BlockedPeers() + peerAuthorizations := profile.ContactsAuthorizations() // TODO: Would be nice if ProtocolEngine did not need to explicitly be given the Private Key. identity := primitives.InitializeIdentity(profile.Name, &profile.Ed25519PrivateKey, &profile.Ed25519PublicKey) - engine := connections.NewProtocolEngine(identity, profile.Ed25519PrivateKey, app.acn, app.eventBuses[profile.Onion], profile.GetContacts(), blockedPeers) + engine := connections.NewProtocolEngine(identity, profile.Ed25519PrivateKey, app.acn, app.eventBuses[profile.Onion], peerAuthorizations) app.peers[profile.Onion] = p app.engines[profile.Onion] = engine @@ -213,9 +213,9 @@ func (app *application) LoadProfiles(password string) { peer := peer.FromProfile(profile) peer.Init(app.eventBuses[profile.Onion]) - blockedPeers := profile.BlockedPeers() + peerAuthorizations := profile.ContactsAuthorizations() identity := primitives.InitializeIdentity(profile.Name, &profile.Ed25519PrivateKey, &profile.Ed25519PublicKey) - engine := connections.NewProtocolEngine(identity, profile.Ed25519PrivateKey, app.acn, app.eventBuses[profile.Onion], profile.GetContacts(), blockedPeers) + engine := connections.NewProtocolEngine(identity, profile.Ed25519PrivateKey, app.acn, app.eventBuses[profile.Onion], peerAuthorizations) app.appmutex.Lock() app.peers[profile.Onion] = peer app.storage[profile.Onion] = profileStore @@ -246,10 +246,12 @@ func (ac *applicationCore) GetEventBus(onion string) event.Manager { func (app *application) getACNStatusHandler() func(int, string) { return func(progress int, status string) { progStr := strconv.Itoa(progress) + app.peerLock.Lock() app.appBus.Publish(event.NewEventList(event.ACNStatus, event.Progreess, progStr, event.Status, status)) for _, bus := range app.eventBuses { bus.Publish(event.NewEventList(event.ACNStatus, event.Progreess, progStr, event.Status, status)) } + app.peerLock.Unlock() } } diff --git a/app/appService.go b/app/appService.go index 3437f62..ea03105 100644 --- a/app/appService.go +++ b/app/appService.go @@ -107,10 +107,10 @@ func (as *applicationService) createPeer(name, password, tag string) { profileStore := storage.CreateProfileWriterStore(as.eventBuses[profile.Onion], path.Join(as.directory, "profiles", profile.LocalID), password, profile) - blockedPeers := profile.BlockedPeers() + peerAuthorizations := profile.ContactsAuthorizations() // TODO: Would be nice if ProtocolEngine did not need to explicitly be given the Private Key. identity := primitives.InitializeIdentity(profile.Name, &profile.Ed25519PrivateKey, &profile.Ed25519PublicKey) - engine := connections.NewProtocolEngine(identity, profile.Ed25519PrivateKey, as.acn, as.eventBuses[profile.Onion], profile.GetContacts(), blockedPeers) + engine := connections.NewProtocolEngine(identity, profile.Ed25519PrivateKey, as.acn, as.eventBuses[profile.Onion], peerAuthorizations) as.storage[profile.Onion] = profileStore as.engines[profile.Onion] = engine @@ -126,9 +126,9 @@ func (as *applicationService) loadProfiles(password string) { as.applicationCore.LoadProfiles(password, false, func(profile *model.Profile, profileStore storage.ProfileStore) { as.eventBuses[profile.Onion] = event.IPCEventManagerFrom(as.bridge, profile.Onion, as.eventBuses[profile.Onion]) - blockedPeers := profile.BlockedPeers() + peerAuthorizations := profile.ContactsAuthorizations() identity := primitives.InitializeIdentity(profile.Name, &profile.Ed25519PrivateKey, &profile.Ed25519PublicKey) - engine := connections.NewProtocolEngine(identity, profile.Ed25519PrivateKey, as.acn, as.eventBuses[profile.Onion], profile.GetContacts(), blockedPeers) + engine := connections.NewProtocolEngine(identity, profile.Ed25519PrivateKey, as.acn, as.eventBuses[profile.Onion], peerAuthorizations) as.asmutex.Lock() as.storage[profile.Onion] = profileStore as.engines[profile.Onion] = engine diff --git a/app/cli/main.go b/app/cli/main.go index f09cb07..4475818 100644 --- a/app/cli/main.go +++ b/app/cli/main.go @@ -48,7 +48,6 @@ var suggestionsSelectedProfile = []prompt.Suggest{ /*{Text: "/list-servers", Description: "retrieve a list of servers and their connection status"}, {Text: "/list-peers", Description: "retrieve a list of peers and their connection status"},*/ {Text: "/export-group", Description: "export a group invite: prints as a string"}, - {Text: "/trust", Description: "trust a peer"}, {Text: "/block", Description: "block a peer - you will no longer see messages or connect to this peer"}, } @@ -411,7 +410,7 @@ func main() { // think over. for _, name := range p.GetContacts() { profile := p.GetContact(name) - if profile.Trusted && !profile.Blocked { + if profile.Authorization == model.AuthApproved { p.PeerWithOnion(profile.Onion) } } @@ -453,22 +452,16 @@ func main() { contacts := peer.GetContacts() for _, onion := range contacts { c := peer.GetContact(onion) - fmt.Printf("Name: %v Onion: %v Trusted: %v\n", c.Name, c.Onion, c.Trusted) + fmt.Printf("Name: %v Onion: %v Authorization: %v\n", c.Name, c.Onion, c.Authorization) } case "/list-groups": for _, gid := range peer.GetGroups() { g := peer.GetGroup(gid) fmt.Printf("Group Id: %v Owner: %v Accepted:%v\n", gid, g.Owner, g.Accepted) } - case "/trust": - if len(commands) == 2 { - peer.TrustPeer(commands[1]) - } else { - fmt.Printf("Error trusting peer, usage: %s\n", usages[commands[0]]) - } case "/block": if len(commands) == 2 { - peer.BlockPeer(commands[1]) + peer.SetContactAuthorization(commands[1], model.AuthBlocked) } else { fmt.Printf("Error blocking peer, usage: %s\n", usages[commands[0]]) } diff --git a/event/common.go b/event/common.go index ee1387d..6e299d9 100644 --- a/event/common.go +++ b/event/common.go @@ -19,11 +19,9 @@ const ( // RemotePeer: [eg "chpr7qm6op5vfcg2pi4vllco3h6aa7exexc4rqwnlupqhoogx2zgd6qd" RetryPeerRequest = Type("RetryPeerRequest") - // Blocking Events both Block and Unblock have the same structure - // attributes: - // RemotePeer: [eg "chpr7qm6op5vfcg2pi4vllco3h6aa7exexc4rqwnlupqhoogx2zgd6qd" - BlockPeer = Type("BlockPeer") - UnblockPeer = Type("UnblockPeer") + // RemotePeer + // Authorization(model.peer.Auth_...) + SetPeerAuthorization = Type("UpdatePeerAuthorization") // Turn on/off blocking of unknown peers (if peers aren't in the contact list then they will be autoblocked BlockUnknownPeers = Type("BlockUnknownPeers") @@ -92,6 +90,7 @@ const ( // a peer contact has been added // attributes: // RemotePeer [eg ""] + // Authorization PeerCreated = Type("PeerCreated") // Password, NewPassword @@ -244,6 +243,8 @@ const ( Status = Field("Status") EventID = Field("EventID") EventContext = Field("EventContext") + + Authorization = Field("Authorization") ) // Defining Common errors diff --git a/go.mod b/go.mod index 38bff70..96b2f3a 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.14 require ( cwtch.im/tapir v0.1.18 - git.openprivacy.ca/openprivacy/connectivity v1.1.1 + git.openprivacy.ca/openprivacy/connectivity v1.1.2 git.openprivacy.ca/openprivacy/libricochet-go v1.0.13 git.openprivacy.ca/openprivacy/log v1.0.0 github.com/c-bata/go-prompt v0.2.3 @@ -19,7 +19,6 @@ require ( golang.org/x/crypto v0.0.0-20200420104511-884d27f42877 golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e golang.org/x/sys v0.0.0-20200331124033-c3d80250170d // indirect - golang.org/x/tools v0.0.0-20200420001825-978e26b7c37c // indirect gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect google.golang.org/genproto v0.0.0-20180831171423-11092d34479b // indirect ) diff --git a/go.sum b/go.sum index 7f79ad5..3dd7367 100644 --- a/go.sum +++ b/go.sum @@ -3,6 +3,8 @@ cwtch.im/tapir v0.1.18/go.mod h1:/IrAI6CBHfgzsfgRT8WHVb1P9fCCz7+45hfsdkKn8Zg= git.openprivacy.ca/openprivacy/connectivity v1.1.0/go.mod h1:4P8mirZZslKbo2zBrXXVjgEdqGwHo/6qoFBwFQW6d6E= git.openprivacy.ca/openprivacy/connectivity v1.1.1 h1:hKxBOmxP7Jdu3K1BJ93mRtKNiWUoP6YHt/o2snE2Z0w= git.openprivacy.ca/openprivacy/connectivity v1.1.1/go.mod h1:4P8mirZZslKbo2zBrXXVjgEdqGwHo/6qoFBwFQW6d6E= +git.openprivacy.ca/openprivacy/connectivity v1.1.2 h1:Bk8ul3+4/awpQGvskfLpp7/K3Lj8OAxBwlmQqeZy3Ok= +git.openprivacy.ca/openprivacy/connectivity v1.1.2/go.mod h1:4P8mirZZslKbo2zBrXXVjgEdqGwHo/6qoFBwFQW6d6E= git.openprivacy.ca/openprivacy/libricochet-go v1.0.13 h1:Z86uL9K47onznY1wP1P/wWfWMbbyvk6xnCp94R180os= git.openprivacy.ca/openprivacy/libricochet-go v1.0.13/go.mod h1:ZUuX1SOrgV4K18IEcp0hQJNPKszRr2oGb3UeK2iYe5U= git.openprivacy.ca/openprivacy/log v1.0.0 h1:Rvqm1weUdR4AOnJ79b1upHCc9vC/QF1rhSD2Um7sr1Y= diff --git a/model/profile.go b/model/profile.go index 75312a8..38bf513 100644 --- a/model/profile.go +++ b/model/profile.go @@ -18,12 +18,24 @@ import ( "time" ) +// Authorization is a type determining client assigned authorization to a peer +type Authorization string + +const ( + // AuthUnknown is an inital state for a new unseen peer + AuthUnknown Authorization = "unknown" + // AuthApproved means the client has approved the peer, it can send messages to us, perform GetVals, etc + AuthApproved Authorization = "approved" + // AuthBlocked means the client has blocked the peer, it's messages and connections should be rejected + AuthBlocked Authorization = "blocked" +) + // PublicProfile is a local copy of a CwtchIdentity type PublicProfile struct { Name string Ed25519PublicKey ed25519.PublicKey - Trusted bool - Blocked bool + Authorization Authorization + DeprecatedBlocked bool `json:"Blocked"` Onion string Attributes map[string]string Timeline Timeline `json:"-"` @@ -239,64 +251,38 @@ func (p *Profile) GetContacts() []string { return keys } -// BlockPeer blocks a contact -func (p *Profile) BlockPeer(onion string) (err error) { +// SetContactAuthorization sets the authoirization level of a peer +func (p *Profile) SetContactAuthorization(onion string, auth Authorization) (err error) { p.lock.Lock() defer p.lock.Unlock() contact, ok := p.Contacts[onion] if ok { - contact.Blocked = true + contact.Authorization = auth } else { err = errors.New("peer does not exist") } return } -// UnblockPeer unblocks a contact -func (p *Profile) UnblockPeer(onion string) (err error) { +// GetContactAuthorization returns the contact's authorization level +func (p *Profile) GetContactAuthorization(onion string) Authorization { p.lock.Lock() defer p.lock.Unlock() contact, ok := p.Contacts[onion] if ok { - contact.Blocked = false - } else { - err = errors.New("peer does not exist") + return contact.Authorization } - return + return AuthUnknown } -// BlockedPeers calculates a list of Peers who have been Blocked. -func (p *Profile) BlockedPeers() []string { - blockedPeers := []string{} +// ContactsAuthorizations calculates a list of Peers who are at the supplied auth levels +func (p *Profile) ContactsAuthorizations(authorizationFilter ...Authorization) map[string]Authorization { + authorizations := map[string]Authorization{} for _, contact := range p.GetContacts() { c, _ := p.GetContact(contact) - if c.Blocked { - blockedPeers = append(blockedPeers, c.Onion) - } + authorizations[c.Onion] = c.Authorization } - return blockedPeers -} - -// TrustPeer sets a contact to trusted -func (p *Profile) TrustPeer(onion string) (err error) { - p.lock.Lock() - defer p.lock.Unlock() - contact, ok := p.Contacts[onion] - if ok { - contact.Trusted = true - } else { - err = errors.New("peer does not exist") - } - return -} - -// IsBlocked returns true if the contact has been blocked, false otherwise -func (p *Profile) IsBlocked(onion string) bool { - contact, ok := p.GetContact(onion) - if ok { - return contact.Blocked - } - return false + return authorizations } // GetContact returns a contact if the profile has it diff --git a/model/profile_test.go b/model/profile_test.go index 8484d95..467d07e 100644 --- a/model/profile_test.go +++ b/model/profile_test.go @@ -31,13 +31,9 @@ func TestTrustPeer(t *testing.T) { alice := GenerateNewProfile("Alice") sarah.AddContact(alice.Onion, &alice.PublicProfile) alice.AddContact(sarah.Onion, &sarah.PublicProfile) - alice.TrustPeer(sarah.Onion) - if alice.IsBlocked(sarah.Onion) { - t.Errorf("peer should not be blocked") - } - - if alice.TrustPeer("") == nil { - t.Errorf("trusting a non existent peer should error") + alice.SetContactAuthorization(sarah.Onion, AuthApproved) + if alice.GetContactAuthorization(sarah.Onion) != AuthApproved { + t.Errorf("peer should be approved") } } @@ -46,13 +42,13 @@ func TestBlockPeer(t *testing.T) { alice := GenerateNewProfile("Alice") sarah.AddContact(alice.Onion, &alice.PublicProfile) alice.AddContact(sarah.Onion, &sarah.PublicProfile) - alice.BlockPeer(sarah.Onion) - if !alice.IsBlocked(sarah.Onion) { - t.Errorf("peer should not be blocked") + alice.SetContactAuthorization(sarah.Onion, AuthBlocked) + if alice.GetContactAuthorization(sarah.Onion) != AuthBlocked { + t.Errorf("peer should be blocked") } - if alice.BlockPeer("") == nil { - t.Errorf("blocking a non existent peer should error") + if alice.SetContactAuthorization("", AuthUnknown) == nil { + t.Errorf("Seting Auth level of a non existent peer should error") } } diff --git a/peer/cwtch_peer.go b/peer/cwtch_peer.go index 4baeac2..d52bd3e 100644 --- a/peer/cwtch_peer.go +++ b/peer/cwtch_peer.go @@ -41,9 +41,7 @@ type CwtchPeer interface { SendMessageToPeer(string, string) string SendGetValToPeer(string, string, string) - TrustPeer(string) error - BlockPeer(string) error - UnblockPeer(string) error + SetContactAuthorization(string, model.Authorization) error ProcessInvite(string, string) (string, error) AcceptInvite(string) error RejectInvite(string) @@ -68,7 +66,7 @@ type CwtchPeer interface { GetGroup(string) *model.Group GetGroupState(string) (connections.ConnectionState, bool) GetGroups() []string - AddContact(nick, onion string, trusted bool) + AddContact(nick, onion string, authorization model.Authorization) GetContacts() []string GetContact(string) *model.PublicProfile @@ -191,17 +189,16 @@ func (cp *cwtchPeer) GetGroup(groupID string) *model.Group { return cp.Profile.GetGroup(groupID) } -func (cp *cwtchPeer) AddContact(nick, onion string, trusted bool) { +func (cp *cwtchPeer) AddContact(nick, onion string, authorization model.Authorization) { decodedPub, _ := base32.StdEncoding.DecodeString(strings.ToUpper(onion)) - pp := &model.PublicProfile{Name: nick, Ed25519PublicKey: decodedPub, Trusted: trusted, Blocked: false, Onion: onion, Attributes: map[string]string{"nick": nick}} - cp.mutex.Lock() + pp := &model.PublicProfile{Name: nick, Ed25519PublicKey: decodedPub, Authorization: authorization, Onion: onion, Attributes: map[string]string{"nick": nick}} cp.Profile.AddContact(onion, pp) - cp.mutex.Unlock() pd, _ := json.Marshal(pp) cp.eventBus.Publish(event.NewEvent(event.PeerCreated, map[event.Field]string{ event.Data: string(pd), event.RemotePeer: onion, })) + cp.eventBus.Publish(event.NewEventList(event.SetPeerAuthorization, event.RemotePeer, onion, event.Authorization, string(authorization))) } // GetContacts returns an unordered list of onions @@ -257,10 +254,10 @@ func (cp *cwtchPeer) GetGroupState(groupid string) (connections.ConnectionState, // PeerWithOnion is the entry point for cwtchPeer relationships func (cp *cwtchPeer) PeerWithOnion(onion string) { cp.mutex.Lock() - if _, exists := cp.Profile.GetContact(onion); !exists { - cp.AddContact(onion, onion, false) - } defer cp.mutex.Unlock() + if _, exists := cp.Profile.GetContact(onion); !exists { + cp.AddContact(onion, onion, model.AuthApproved) + } cp.eventBus.Publish(event.NewEvent(event.PeerRequest, map[event.Field]string{event.RemotePeer: onion})) } @@ -343,32 +340,12 @@ func (cp *cwtchPeer) SendGetValToPeer(onion string, scope string, path string) { cp.eventBus.Publish(event) } -// TrustPeer sets an existing peer relationship to trusted -func (cp *cwtchPeer) TrustPeer(peer string) error { - cp.mutex.Lock() - defer cp.mutex.Unlock() - err := cp.Profile.TrustPeer(peer) - if err == nil { - cp.PeerWithOnion(peer) - } - return err -} - // BlockPeer blocks an existing peer relationship. -func (cp *cwtchPeer) BlockPeer(peer string) error { +func (cp *cwtchPeer) SetContactAuthorization(peer string, authorization model.Authorization) error { cp.mutex.Lock() - err := cp.Profile.BlockPeer(peer) + err := cp.Profile.SetContactAuthorization(peer, authorization) cp.mutex.Unlock() - cp.eventBus.Publish(event.NewEvent(event.BlockPeer, map[event.Field]string{event.RemotePeer: peer})) - return err -} - -// UnblockPeer blocks an existing peer relationship. -func (cp *cwtchPeer) UnblockPeer(peer string) error { - cp.mutex.Lock() - err := cp.Profile.UnblockPeer(peer) - cp.mutex.Unlock() - cp.eventBus.Publish(event.NewEvent(event.UnblockPeer, map[event.Field]string{event.RemotePeer: peer})) + cp.eventBus.Publish(event.NewEvent(event.SetPeerAuthorization, map[event.Field]string{event.RemotePeer: peer, event.Authorization: string(authorization)})) return err } diff --git a/protocol/ControlChannel.pb.go b/protocol/ControlChannel.pb.go index 5e50ce9..1c7c302 100644 --- a/protocol/ControlChannel.pb.go +++ b/protocol/ControlChannel.pb.go @@ -82,7 +82,9 @@ func (x *ChannelResult_CommonError) UnmarshalJSON(data []byte) error { *x = ChannelResult_CommonError(value) return nil } -func (ChannelResult_CommonError) EnumDescriptor() ([]byte, []int) { return fileDescriptor0, []int{2, 0} } +func (ChannelResult_CommonError) EnumDescriptor() ([]byte, []int) { + return fileDescriptor0, []int{2, 0} +} type Packet struct { // Must contain exactly one field diff --git a/protocol/connections/engine.go b/protocol/connections/engine.go index a89f6a1..7044e61 100644 --- a/protocol/connections/engine.go +++ b/protocol/connections/engine.go @@ -2,6 +2,7 @@ package connections import ( "cwtch.im/cwtch/event" + "cwtch.im/cwtch/model" "cwtch.im/cwtch/protocol" "cwtch.im/tapir" "cwtch.im/tapir/networks/tor" @@ -28,8 +29,8 @@ type engine struct { // Engine State started bool - // Blocklist - blocked sync.Map + // Authorization list of contacts to authorization status + authorizations sync.Map // string(onion) => model.Authorization // Block Unknown Contacts blockUnknownContacts bool @@ -55,7 +56,7 @@ type Engine interface { } // NewProtocolEngine initializes a new engine that runs Cwtch using the given parameters -func NewProtocolEngine(identity primitives.Identity, privateKey ed25519.PrivateKey, acn connectivity.ACN, eventManager event.Manager, knownPeers []string, blockedPeers []string) Engine { +func NewProtocolEngine(identity primitives.Identity, privateKey ed25519.PrivateKey, acn connectivity.ACN, eventManager event.Manager, peerAuthorizations map[string]model.Authorization) Engine { engine := new(engine) engine.identity = identity engine.privateKey = privateKey @@ -83,17 +84,12 @@ func NewProtocolEngine(identity primitives.Identity, privateKey ed25519.PrivateK engine.eventManager.Subscribe(event.DeleteContact, engine.queue) engine.eventManager.Subscribe(event.DeleteGroup, engine.queue) - engine.eventManager.Subscribe(event.BlockPeer, engine.queue) - engine.eventManager.Subscribe(event.UnblockPeer, engine.queue) + engine.eventManager.Subscribe(event.SetPeerAuthorization, engine.queue) engine.eventManager.Subscribe(event.BlockUnknownPeers, engine.queue) engine.eventManager.Subscribe(event.AllowUnknownPeers, engine.queue) - for _, peer := range knownPeers { - engine.blocked.Store(peer, false) - } - - for _, peer := range blockedPeers { - engine.blocked.Store(peer, true) + for peer, authorization := range peerAuthorizations { + engine.authorizations.Store(peer, authorization) } return engine } @@ -115,7 +111,6 @@ func (e *engine) eventHandler() { e.eventManager.Publish(event.Event{EventType: event.ProtocolEngineStatus, EventID: ev.EventID}) case event.PeerRequest: if torProdider.IsValidHostname(ev.Data[event.RemotePeer]) { - e.blocked.Store(ev.Data[event.RemotePeer], false) go e.peerWithOnion(ev.Data[event.RemotePeer]) } case event.RetryPeerRequest: @@ -131,7 +126,7 @@ func (e *engine) eventHandler() { case event.DeleteContact: onion := ev.Data[event.RemotePeer] // We remove this peer from out blocklist which will prevent them from contacting us if we have "block unknown peers" turned on. - e.blocked.Delete(ev.Data[event.RemotePeer]) + e.authorizations.Delete(ev.Data[event.RemotePeer]) e.deleteConnection(onion) case event.DeleteGroup: // TODO: There isn't a way here to determine if other Groups are using a server connection... @@ -151,21 +146,19 @@ func (e *engine) eventHandler() { e.sendGetValToPeer(ev.EventID, ev.Data[event.RemotePeer], ev.Data[event.Scope], ev.Data[event.Path]) case event.SendRetValMessageToPeer: e.sendRetValToPeer(ev.EventID, ev.Data[event.RemotePeer], ev.Data[event.Data], ev.Data[event.Exists]) - case event.UnblockPeer: - // We simply remove the peer from our blocklist - // The UI has the responsibility to reinitiate contact with the peer. - // (this should happen periodically in any case) - e.blocked.Store(ev.Data[event.RemotePeer], false) - case event.BlockPeer: - e.blocked.Store(ev.Data[event.RemotePeer], true) - connection, err := e.service.GetConnection(ev.Data[event.RemotePeer]) - if connection != nil && err == nil { - connection.Close() + case event.SetPeerAuthorization: + auth := model.Authorization(ev.Data[event.Authorization]) + e.authorizations.Store(ev.Data[event.RemotePeer], auth) + if auth == model.AuthBlocked { + connection, err := e.service.GetConnection(ev.Data[event.RemotePeer]) + if connection != nil && err == nil { + connection.Close() + } + // Explicitly send a disconnected event (if we don't do this here then the UI can wait for a while before + // an ongoing Open() connection fails and so the user will see a blocked peer as still connecting (because + // there isn't an active connection and we are stuck waiting for tor to time out) + e.peerDisconnected(ev.Data[event.RemotePeer]) } - // Explicitly send a disconnected event (if we don't do this here then the UI can wait for a while before - // an ongoing Open() connection fails and so the user will see a blocked peer as still connecting (because - // there isn't an active connection and we are stuck waiting for tor to time out) - e.peerDisconnected(ev.Data[event.RemotePeer]) case event.AllowUnknownPeers: e.blockUnknownContacts = false case event.BlockUnknownPeers: @@ -178,16 +171,27 @@ func (e *engine) eventHandler() { } } +func (e *engine) isBlocked(onion string) bool { + authorization, known := e.authorizations.Load(onion) + if !known { + // if we block unknown peers we will block this contact + return e.blockUnknownContacts + } + return authorization.(model.Authorization) == model.AuthBlocked +} + +func (e *engine) isApproved(onion string) bool { + authorization, known := e.authorizations.Load(onion) + if !known { + return false + } + return authorization.(model.Authorization) == model.AuthApproved +} + func (e *engine) createPeerTemplate() *PeerApp { peerAppTemplate := new(PeerApp) - peerAppTemplate.IsBlocked = func(onion string) bool { - blocked, known := e.blocked.Load(onion) - if !known { - // if we block unknown peers we will block this contact - return e.blockUnknownContacts - } - return blocked.(bool) - } + peerAppTemplate.IsBlocked = e.isBlocked + peerAppTemplate.IsApproved = e.isApproved peerAppTemplate.MessageHandler = e.handlePeerMessage peerAppTemplate.OnAcknowledgement = e.ignoreOnShutdown2(e.peerAck) peerAppTemplate.OnAuth = e.ignoreOnShutdown(e.peerAuthed) @@ -217,8 +221,7 @@ func (e *engine) Shutdown() { // peerWithOnion is the entry point for cwtchPeer relationships // needs to be run in a goroutine as will block on Open. func (e *engine) peerWithOnion(onion string) { - blocked, known := e.blocked.Load(onion) - if known && !(blocked.(bool)) { + if !e.isBlocked(onion) { e.ignoreOnShutdown(e.peerConnecting)(onion) connected, err := e.service.Connect(onion, e.createPeerTemplate()) @@ -258,6 +261,10 @@ func (e *engine) ignoreOnShutdown2(f func(string, string)) func(string, string) } func (e *engine) peerAuthed(onion string) { + _, known := e.authorizations.Load(onion) + if !known { + e.authorizations.Store(onion, model.AuthUnknown) + } e.eventManager.Publish(event.NewEvent(event.PeerStateChange, map[event.Field]string{ event.RemotePeer: string(onion), event.ConnectionState: ConnectionStateName[AUTHENTICATED], diff --git a/protocol/connections/peerapp.go b/protocol/connections/peerapp.go index 111fc2a..581d971 100644 --- a/protocol/connections/peerapp.go +++ b/protocol/connections/peerapp.go @@ -18,6 +18,7 @@ type PeerApp struct { MessageHandler func(string, string, string, []byte) RetValHandler func(string, []byte, []byte) IsBlocked func(string) bool + IsApproved func(string) bool OnAcknowledgement func(string, string) OnAuth func(string) OnClose func(string) @@ -47,6 +48,7 @@ func (pa *PeerApp) NewInstance() tapir.Application { newApp := new(PeerApp) newApp.MessageHandler = pa.MessageHandler newApp.IsBlocked = pa.IsBlocked + newApp.IsApproved = pa.IsApproved newApp.OnAcknowledgement = pa.OnAcknowledgement newApp.OnAuth = pa.OnAuth newApp.OnClose = pa.OnClose @@ -99,10 +101,12 @@ func (pa *PeerApp) listen() { pa.getValRequests.Delete(peerMessage.ID) } default: - pa.MessageHandler(pa.connection.Hostname(), peerMessage.ID, peerMessage.Context, peerMessage.Data) + if pa.IsApproved(pa.connection.Hostname()) { + pa.MessageHandler(pa.connection.Hostname(), peerMessage.ID, peerMessage.Context, peerMessage.Data) - // Acknowledge the message - pa.SendMessage(PeerMessage{peerMessage.ID, event.ContextAck, []byte{}}) + // Acknowledge the message + pa.SendMessage(PeerMessage{peerMessage.ID, event.ContextAck, []byte{}}) + } } } else { log.Errorf("Error unmarshalling PeerMessage package: %x %v", message, err) diff --git a/protocol/connections/peerserverconnection_test.go b/protocol/connections/peerserverconnection_test.go index 645b560..e29f9af 100644 --- a/protocol/connections/peerserverconnection_test.go +++ b/protocol/connections/peerserverconnection_test.go @@ -78,7 +78,7 @@ func TestPeerServerConnection(t *testing.T) { onionAddr := identity.Hostname() manager := event.NewEventManager() - engine := NewProtocolEngine(identity, priv, connectivity.NewLocalACN(), manager, nil, nil) + engine := NewProtocolEngine(identity, priv, connectivity.NewLocalACN(), manager, nil) psc := NewPeerServerConnection(engine, "127.0.0.1:5451|"+onionAddr) numcalls := 0 diff --git a/storage/v1/profile_store.go b/storage/v1/profile_store.go index 24b82e4..d329aa0 100644 --- a/storage/v1/profile_store.go +++ b/storage/v1/profile_store.go @@ -66,8 +66,7 @@ func (ps *ProfileStoreV1) initProfileWriterStore() { ps.queue = event.NewQueue() go ps.eventHandler() - ps.eventManager.Subscribe(event.BlockPeer, ps.queue) - ps.eventManager.Subscribe(event.UnblockPeer, ps.queue) + ps.eventManager.Subscribe(event.SetPeerAuthorization, ps.queue) ps.eventManager.Subscribe(event.PeerCreated, ps.queue) ps.eventManager.Subscribe(event.GroupCreated, ps.queue) ps.eventManager.Subscribe(event.SetProfileName, ps.queue) @@ -249,6 +248,18 @@ func (ps *ProfileStoreV1) load() error { if err == nil { ps.profile = cp + // TODO 2020.06.09: v1 update, Remove on v2 + // if we already have the contact it can be assumed "approved" unless blocked + for _, contact := range cp.Contacts { + if contact.Authorization == "" { + if contact.DeprecatedBlocked { + contact.Authorization = model.AuthBlocked + } else { + contact.Authorization = model.AuthApproved + } + } + } + for gid, group := range cp.Groups { ss := NewStreamStore(ps.directory, group.LocalID, ps.key) @@ -272,16 +283,9 @@ func (ps *ProfileStoreV1) eventHandler() { log.Debugf("eventHandler event %v %v\n", ev.EventType, ev.EventID) switch ev.EventType { - case event.BlockPeer: - contact, exists := ps.profile.GetContact(ev.Data[event.RemotePeer]) - if exists { - contact.Blocked = true - ps.save() - } - case event.UnblockPeer: - contact, exists := ps.profile.GetContact(ev.Data[event.RemotePeer]) - if exists { - contact.Blocked = false + case event.SetPeerAuthorization: + err := ps.profile.SetContactAuthorization(ev.Data[event.RemotePeer], model.Authorization(ev.Data[event.Authorization])) + if err == nil { ps.save() } case event.PeerCreated: diff --git a/testing/cwtch_peer_server_integration_test.go b/testing/cwtch_peer_server_integration_test.go index 9992b33..f3ab4fc 100644 --- a/testing/cwtch_peer_server_integration_test.go +++ b/testing/cwtch_peer_server_integration_test.go @@ -188,9 +188,11 @@ func TestCwtchPeerIntegration(t *testing.T) { app.LaunchPeers() appClient.LaunchPeers() - fmt.Println("Waiting for Alice, Bob, and Carol to connect with onion network...") - time.Sleep(time.Second * 60) + waitTime := time.Duration(60) * time.Second + t.Logf("** Waiting for Alice, Bob, and Carol to connect with onion network... (%v)\n", waitTime) + time.Sleep(waitTime) numGoRoutinesPostPeerStart := runtime.NumGoroutine() + fmt.Println("** Wait Done!") // ***** Peering, server joining, group creation / invite ***** @@ -198,11 +200,9 @@ func TestCwtchPeerIntegration(t *testing.T) { alice.JoinServer(serverAddr) fmt.Println("Alice peering with Bob...") - alice.AddContact("Bob", bob.GetOnion(), false) // Add contact so we can track connection state alice.PeerWithOnion(bob.GetOnion()) fmt.Println("Alice peering with Carol...") - alice.AddContact("Carol", carol.GetOnion(), false) alice.PeerWithOnion(carol.GetOnion()) fmt.Println("Creating group on ", serverAddr, "...") @@ -218,9 +218,15 @@ func TestCwtchPeerIntegration(t *testing.T) { fmt.Println("Waiting for alice and Bob to peer...") waitForPeerPeerConnection(t, alice, bob) - bob.AddContact("alice?", alice.GetOnion(), true) + // Need to add contact else SetContactAuth fails on peer peer doesnt exist + // Normal flow would be Bob app monitors for the new connection (a new connection state change to Auth + // and the adds the user to peer, and then approves or blocks it + bob.AddContact("alice?", alice.GetOnion(), model.AuthApproved) + bob.SetContactAuthorization(alice.GetOnion(), model.AuthApproved) + waitForPeerPeerConnection(t, alice, carol) - carol.AddContact("alice?", alice.GetOnion(), true) + carol.AddContact("alice?", alice.GetOnion(), model.AuthApproved) + carol.SetContactAuthorization(alice.GetOnion(), model.AuthApproved) fmt.Println("Alice and Bob getVal public.name...") @@ -234,13 +240,13 @@ func TestCwtchPeerIntegration(t *testing.T) { aliceName, exists := bob.GetContactAttribute(alice.GetOnion(), attr.GetPeerScope("name")) if !exists || aliceName != "Alice" { - t.Fatalf("alice GetKeyVal error on alice peer.name %v\n", exists) + t.Fatalf("Bob: alice GetKeyVal error on alice peer.name %v\n", exists) } fmt.Printf("Bob has alice's name as '%v'\n", aliceName) bobName, exists := alice.GetContactAttribute(bob.GetOnion(), attr.GetPeerScope("name")) if !exists || bobName != "Bob" { - t.Fatalf("bob GetKeyVal error, alice peer.name\n") + t.Fatalf("Alice: bob GetKeyVal error on bob peer.name\n") } fmt.Printf("Alice has bob's name as '%v'\n", bobName)