diff --git a/api/authenticatedChannel.go b/api/authenticatedChannel.go index 71eae7b11ac30335a48835f9b3c49f07cc07b56b..424d3f830174445334a48de0ced69eaa5424f191 100644 --- a/api/authenticatedChannel.go +++ b/api/authenticatedChannel.go @@ -4,6 +4,7 @@ import ( "github.com/pkg/errors" jww "github.com/spf13/jwalterweatherman" "gitlab.com/elixxir/client/auth" + "gitlab.com/elixxir/client/interfaces" "gitlab.com/elixxir/client/interfaces/contact" "gitlab.com/elixxir/client/storage/e2e" "gitlab.com/xx_network/primitives/id" @@ -30,26 +31,12 @@ func (c *Client) RequestAuthenticatedChannel(recipient, me contact.Contact, c.storage, c.network) } -// RegisterAuthCallbacks registers both callbacks for authenticated channels. -// This can only be called once -func (c *Client) RegisterAuthCallbacks(request auth.RequestCallback, - confirm auth.ConfirmCallback) error { - jww.INFO.Printf("RegisterAuthCallbacks(...)") +// GetAuthRegistrar gets the object which allows the registration of auth +// callbacks +func (c *Client) GetAuthRegistrar() interfaces.Auth { + jww.INFO.Printf("GetAuthRegistrar(...)") - exicuted := false - - c.authOnce.Do(func() { - stop := auth.RegisterCallbacks(request, confirm, c.switchboard, - c.storage, c.network) - c.runner.Add(stop) - exicuted = true - }) - - if !exicuted { - return errors.New("Cannot register auth callbacks more than " + - "once") - } - return nil + return c.auth } // GetAuthenticatedChannelRequest returns the contact received in a request if diff --git a/api/client.go b/api/client.go index c2580f87058d44488539c9b14aa7c35fe436babf..e9c7e5cb693e7486783f3c3f20fba3442cbe24e6 100644 --- a/api/client.go +++ b/api/client.go @@ -9,6 +9,7 @@ package api import ( "github.com/pkg/errors" jww "github.com/spf13/jwalterweatherman" + "gitlab.com/elixxir/client/auth" "gitlab.com/elixxir/client/interfaces" "gitlab.com/elixxir/client/interfaces/params" "gitlab.com/elixxir/client/interfaces/user" @@ -25,7 +26,6 @@ import ( "gitlab.com/elixxir/crypto/large" "gitlab.com/xx_network/crypto/signature/rsa" "gitlab.com/xx_network/primitives/ndf" - "sync" "time" ) @@ -46,14 +46,12 @@ type Client struct { network interfaces.NetworkManager //object used to register and communicate with permissioning permissioning *permissioning.Permissioning + //object containing auth interactions + auth *auth.Manager //contains stopables for all running threads runner *stoppable.Multi status *statusTracker - - // contains the sync once used to ensure authenticated channel callbacks are - // only registered once - authOnce sync.Once } // NewClient creates client storage, generates keys, connects, and registers @@ -224,6 +222,9 @@ func loadClient(session *storage.Session, rngStreamGen *fastRNG.StreamGenerator) return nil, err } + //initilize the auth tracker + c.auth = auth.NewManager(c.switchboard, c.storage, c.network) + return c, nil } @@ -256,6 +257,8 @@ func loadClient(session *storage.Session, rngStreamGen *fastRNG.StreamGenerator) // Responds to sent rekeys and executes them // - KeyExchange Confirm (/keyExchange/confirm.go) // Responds to confirmations of successful rekey operations +// - Auth Callback (/auth/callback.go) +// Handles both auth confirm and requests func (c *Client) StartNetworkFollower() error { jww.INFO.Printf("StartNetworkFollower()") @@ -264,6 +267,9 @@ func (c *Client) StartNetworkFollower() error { return errors.WithMessage(err, "Failed to Start the Network Follower") } + stopAuth := c.auth.StartProcessies() + c.runner.Add(stopAuth) + stopFollow, err := c.network.Follow() if err != nil { return errors.WithMessage(err, "Failed to start following "+ diff --git a/auth/callback.go b/auth/callback.go index a8eed00f1c5b30b51634a6bfb826ca183606d368..6e90610ca75ebd361e456c407a773a99b2366d03 100644 --- a/auth/callback.go +++ b/auth/callback.go @@ -3,39 +3,30 @@ package auth import ( "github.com/pkg/errors" jww "github.com/spf13/jwalterweatherman" - "gitlab.com/elixxir/client/interfaces" "gitlab.com/elixxir/client/interfaces/contact" - "gitlab.com/elixxir/client/interfaces/message" "gitlab.com/elixxir/client/stoppable" - "gitlab.com/elixxir/client/storage" "gitlab.com/elixxir/client/storage/auth" "gitlab.com/elixxir/client/storage/e2e" "gitlab.com/elixxir/crypto/cyclic" cAuth "gitlab.com/elixxir/crypto/e2e/auth" "gitlab.com/elixxir/primitives/format" - "gitlab.com/xx_network/primitives/id" "strings" ) type RequestCallback func(requestor contact.Contact, message string) type ConfirmCallback func(partner contact.Contact) -func RegisterCallbacks(rcb RequestCallback, ccb ConfirmCallback, - sw interfaces.Switchboard, storage *storage.Session, - net interfaces.NetworkManager) stoppable.Stoppable { - - rawMessages := make(chan message.Receive, 1000) - sw.RegisterChannel("Auth", &id.ID{}, message.Raw, rawMessages) +func (m *Manager) StartProcessies() stoppable.Stoppable { stop := stoppable.NewSingle("Auth") - authStore := storage.Auth() - grp := storage.E2e().GetGroup() + authStore := m.storage.Auth() + grp := m.storage.E2e().GetGroup() go func() { select { case <-stop.Quit(): return - case msg := <-rawMessages: + case msg := <-m.rawMessages: //lookup the message, check if it is an auth request cmixMsg := format.Unmarshal(msg.Payload) fp := cmixMsg.GetKeyFP() @@ -50,26 +41,24 @@ func RegisterCallbacks(rcb RequestCallback, ccb ConfirmCallback, } //denote that the message is not garbled - storage.GetGarbledMessages().Remove(cmixMsg) + m.storage.GetGarbledMessages().Remove(cmixMsg) switch fpType { // if it is general, that means a new request has been received case auth.General: - handleRequest(cmixMsg, myHistoricalPrivKey, grp, storage, rcb, - ccb, net) + m.handleRequest(cmixMsg, myHistoricalPrivKey, grp) // if it is specific, that means the original request was sent // by this users and a confirmation has been received case auth.Specific: - handleConfirm(cmixMsg, sr, ccb, storage, grp, net) + m.handleConfirm(cmixMsg, sr, grp) } } }() return stop } -func handleRequest(cmixMsg format.Message, myHistoricalPrivKey *cyclic.Int, - grp *cyclic.Group, storage *storage.Session, rcb RequestCallback, - ccb ConfirmCallback, net interfaces.NetworkManager) { +func (m *Manager) handleRequest(cmixMsg format.Message, + myHistoricalPrivKey *cyclic.Int, grp *cyclic.Group) { //decode the outer format baseFmt, partnerPubKey, err := handleBaseFormat(cmixMsg, grp) if err != nil { @@ -116,14 +105,14 @@ func handleRequest(cmixMsg format.Message, myHistoricalPrivKey *cyclic.Int, // if it does and the keys used are the same as we have, send a // confirmation in case there are state issues. // do not store - if _, err := storage.E2e().GetPartner(partnerID); err == nil { + if _, err := m.storage.E2e().GetPartner(partnerID); err == nil { jww.WARN.Printf("Recieved Auth request for %s, "+ "channel already exists. Ignoring", partnerID) //exit return } else { //check if the relationship already exists, - rType, sr2, _, err := storage.Auth().GetRequest(partnerID) + rType, sr2, _, err := m.storage.Auth().GetRequest(partnerID) if err != nil && !strings.Contains(err.Error(), auth.NoRequest) { // if another error is recieved, print it and exist jww.WARN.Printf("Recieved new Auth request for %s, "+ @@ -142,8 +131,8 @@ func handleRequest(cmixMsg format.Message, myHistoricalPrivKey *cyclic.Int, // then exit, nothing else needed case auth.Sent: // do the confirmation - if err := doConfirm(sr2, grp, partnerPubKey, ecrFmt.GetOwnership(), - storage, ccb, net); err != nil { + if err := m.doConfirm(sr2, grp, partnerPubKey, + ecrFmt.GetOwnership()); err != nil { jww.WARN.Printf("Confirmation failed: %s", err) } //exit @@ -172,26 +161,29 @@ func handleRequest(cmixMsg format.Message, myHistoricalPrivKey *cyclic.Int, // fixme: the client will never be notified of the channel creation if a // crash occurs after the store but before the conclusion of the callback //create the auth storage - if err = storage.Auth().AddReceived(c); err != nil { + if err = m.storage.Auth().AddReceived(c); err != nil { jww.WARN.Printf("failed to store contact Auth "+ "Request: %s", err) return } - //call the callback - - go rcb(c, msg) + // fixme: if a crash occurs before or during the calls, the notification + // will never be sent. + cbList := m.requestCallbacks.Get(c.ID) + for _, cb := range cbList { + rcb := cb.(RequestCallback) + go rcb(c, msg) + } return } -func handleConfirm(cmixMsg format.Message, sr *auth.SentRequest, - ccb ConfirmCallback, storage *storage.Session, grp *cyclic.Group, - net interfaces.NetworkManager) { +func (m *Manager) handleConfirm(cmixMsg format.Message, sr *auth.SentRequest, + grp *cyclic.Group) { // check if relationship already exists - if m, err := storage.E2e().GetPartner(sr.GetPartner()); m != nil || err == nil { + if mgr, err := m.storage.E2e().GetPartner(sr.GetPartner()); mgr != nil || err == nil { jww.WARN.Printf("Cannot confirm auth for %s, channel already "+ "exists.", sr.GetPartner()) - storage.Auth().Fail(sr.GetPartner()) + m.storage.Auth().Fail(sr.GetPartner()) return } @@ -199,7 +191,7 @@ func handleConfirm(cmixMsg format.Message, sr *auth.SentRequest, baseFmt, partnerPubKey, err := handleBaseFormat(cmixMsg, grp) if err != nil { jww.WARN.Printf("Failed to handle auth confirm: %s", err) - storage.Auth().Fail(sr.GetPartner()) + m.storage.Auth().Fail(sr.GetPartner()) return } @@ -211,7 +203,7 @@ func handleConfirm(cmixMsg format.Message, sr *auth.SentRequest, if !success { jww.WARN.Printf("Recieved auth confirmation failed its mac " + "check") - storage.Auth().Fail(sr.GetPartner()) + m.storage.Auth().Fail(sr.GetPartner()) return } @@ -219,22 +211,20 @@ func handleConfirm(cmixMsg format.Message, sr *auth.SentRequest, if err != nil { jww.WARN.Printf("Failed to unmarshal auth confirmation's "+ "encrypted payload: %s", err) - storage.Auth().Fail(sr.GetPartner()) + m.storage.Auth().Fail(sr.GetPartner()) return } // finalize the confirmation - if err := doConfirm(sr, grp, partnerPubKey, ecrFmt.GetOwnership(), - storage, ccb, net); err != nil { + if err := m.doConfirm(sr, grp, partnerPubKey, ecrFmt.GetOwnership()); err != nil { jww.WARN.Printf("Confirmation failed: %s", err) - storage.Auth().Fail(sr.GetPartner()) + m.storage.Auth().Fail(sr.GetPartner()) return } } -func doConfirm(sr *auth.SentRequest, grp *cyclic.Group, - partnerPubKey *cyclic.Int, ownershipProof []byte, storage *storage.Session, - ccb ConfirmCallback, net interfaces.NetworkManager) error { +func (m *Manager) doConfirm(sr *auth.SentRequest, grp *cyclic.Group, + partnerPubKey *cyclic.Int, ownershipProof []byte) error { // verify the message came from the intended recipient if !cAuth.VerifyOwnershipProof(sr.GetMyPrivKey(), sr.GetPartnerHistoricalPubKey(), grp, ownershipProof) { @@ -245,7 +235,7 @@ func doConfirm(sr *auth.SentRequest, grp *cyclic.Group, // fixme: channel can get into a bricked state if the first save occurs and // the second does not p := e2e.GetDefaultSessionParams() - if err := storage.E2e().AddPartner(sr.GetPartner(), + if err := m.storage.E2e().AddPartner(sr.GetPartner(), partnerPubKey, sr.GetMyPrivKey(), p, p); err != nil { return errors.Errorf("Failed to create channel with partner (%s) "+ "after confirmation: %+v", @@ -254,7 +244,7 @@ func doConfirm(sr *auth.SentRequest, grp *cyclic.Group, // delete the in progress negotiation // this undoes the request lock - if err := storage.Auth().Delete(sr.GetPartner()); err != nil { + if err := m.storage.Auth().Delete(sr.GetPartner()); err != nil { return errors.Errorf("UNRECOVERABLE! Failed to delete in "+ "progress negotiation with partner (%s) after confirmation: %+v", sr.GetPartner(), err) @@ -268,11 +258,15 @@ func doConfirm(sr *auth.SentRequest, grp *cyclic.Group, Facts: make([]contact.Fact, 0), } - // fixme: if a crash occurs before or during the call, the notification + // fixme: if a crash occurs before or during the calls, the notification // will never be sent. - go ccb(c) + cbList := m.confirmCallbacks.Get(c.ID) + for _, cb := range cbList { + ccb := cb.(ConfirmCallback) + go ccb(c) + } - net.CheckGarbledMessages() + m.net.CheckGarbledMessages() return nil } diff --git a/auth/callbacks.go b/auth/callbacks.go new file mode 100644 index 0000000000000000000000000000000000000000..ab6a2ead168f2ba363373371e6d9c43a61707cb0 --- /dev/null +++ b/auth/callbacks.go @@ -0,0 +1,70 @@ +package auth + +import ( + "gitlab.com/xx_network/primitives/id" + "sync" +) + +type callbackMap struct { + generalCallback []interface{} + specificCallback map[id.ID]interface{} + overrideCallback []interface{} + mux sync.RWMutex +} + +func newCallbackMap() *callbackMap { + return &callbackMap{ + generalCallback: make([]interface{}, 0), + specificCallback: make(map[id.ID]interface{}), + overrideCallback: make([]interface{}, 0), + } +} + +//adds a general callback. This will be preempted by any specific callback +func (cm *callbackMap) AddGeneral(cb interface{}) { + cm.mux.Lock() + cm.generalCallback = append(cm.generalCallback, cb) + cm.mux.Unlock() +} + +//adds an override callback. This will NOT be preempted by any callback +func (cm *callbackMap) AddOverride(cb interface{}) { + cm.mux.Lock() + cm.overrideCallback = append(cm.overrideCallback, cb) + cm.mux.Unlock() +} + +// adds a callback for a specific user ID. Only only callback can exist for a +// user ID. False will be returned if a callback already exists and the new +// one was not added +func (cm *callbackMap) AddSpecific(id *id.ID, cb interface{}) bool { + cm.mux.Lock() + defer cm.mux.Unlock() + if _, ok := cm.specificCallback[*id]; ok { + return false + } + cm.specificCallback[*id] = cb + return true +} + +// removes a callback for a specific user ID if it exists. +func (cm *callbackMap) RemoveSpecific(id *id.ID) { + cm.mux.Lock() + defer cm.mux.Unlock() + delete(cm.specificCallback, *id) +} + +//get all callback which fit with the passed id +func (cm *callbackMap) Get(id *id.ID) []interface{} { + cm.mux.RLock() + defer cm.mux.RUnlock() + cbList := cm.overrideCallback + + if specific, ok := cm.specificCallback[*id]; ok { + cbList = append(cbList, specific) + } else { + cbList = append(cbList, cm.generalCallback) + } + + return cbList +} diff --git a/auth/manager.go b/auth/manager.go new file mode 100644 index 0000000000000000000000000000000000000000..9577e99133bb0ff44d7dce2b2786795da120ba30 --- /dev/null +++ b/auth/manager.go @@ -0,0 +1,83 @@ +package auth + +import ( + "gitlab.com/elixxir/client/interfaces" + "gitlab.com/elixxir/client/interfaces/message" + "gitlab.com/elixxir/client/storage" + "gitlab.com/xx_network/primitives/id" +) + +type Manager struct { + requestCallbacks *callbackMap + confirmCallbacks *callbackMap + + rawMessages chan message.Receive + + storage *storage.Session + net interfaces.NetworkManager +} + +func NewManager(sw interfaces.Switchboard, storage *storage.Session, + net interfaces.NetworkManager) *Manager { + m := &Manager{ + requestCallbacks: newCallbackMap(), + confirmCallbacks: newCallbackMap(), + rawMessages: make(chan message.Receive, 1000), + storage: storage, + net: net, + } + + sw.RegisterChannel("Auth", &id.ID{}, message.Raw, m.rawMessages) + + return m +} + +// Adds a general callback to be used on auth requests. This will be preempted +// by any specific callback +func (m *Manager) AddGeneralRequestCallback(cb RequestCallback) { + m.requestCallbacks.AddGeneral(cb) +} + +// Adds a general callback to be used on auth requests. This will not be +// preempted by any specific callback. It is recommended that the specific +// callbacks are used, this is primarily for debugging. +func (m *Manager) AddOverrideRequestCallback(cb RequestCallback) { + m.requestCallbacks.AddOverride(cb) +} + +// Adds a specific callback to be used on auth requests. This will preempt a +// general callback, meaning the request will be heard on this callback and not +// the general. Request will still be heard on override callbacks. +func (m *Manager) AddSpecificRequestCallback(id *id.ID, cb RequestCallback) { + m.requestCallbacks.AddSpecific(id, cb) +} + +// Removes a specific callback to be used on auth requests. +func (m *Manager) RemoveSpecificRequestCallback(id *id.ID) { + m.requestCallbacks.RemoveSpecific(id) +} + +// Adds a general callback to be used on auth confirms. This will be preempted +// by any specific callback +func (m *Manager) AddGeneralConfirmCallback(cb ConfirmCallback) { + m.confirmCallbacks.AddGeneral(cb) +} + +// Adds a general callback to be used on auth confirms. This will not be +// preempted by any specific callback. It is recommended that the specific +// callbacks are used, this is primarily for debugging. +func (m *Manager) AddOverrideConfirmCallback(cb ConfirmCallback) { + m.confirmCallbacks.AddOverride(cb) +} + +// Adds a specific callback to be used on auth confirms. This will preempt a +// general callback, meaning the request will be heard on this callback and not +// the general. Request will still be heard on override callbacks. +func (m *Manager) AddSpecificConfirmCallback(id *id.ID, cb ConfirmCallback) { + m.confirmCallbacks.AddSpecific(id, cb) +} + +// Removes a specific callback to be used on auth confirm. +func (m *Manager) RemoveSpecificConfirmCallback(id *id.ID) { + m.confirmCallbacks.RemoveSpecific(id) +} diff --git a/interfaces/auth.go b/interfaces/auth.go new file mode 100644 index 0000000000000000000000000000000000000000..836ba0cf95d56d58151b5d99c6dd72cc4a12538f --- /dev/null +++ b/interfaces/auth.go @@ -0,0 +1,35 @@ +package interfaces + +import ( + "gitlab.com/elixxir/client/auth" + "gitlab.com/xx_network/primitives/id" +) + +type Auth interface { + // Adds a general callback to be used on auth requests. This will be preempted + // by any specific callback + AddGeneralRequestCallback(cb auth.RequestCallback) + // Adds a general callback to be used on auth requests. This will not be + // preempted by any specific callback. It is recommended that the specific + // callbacks are used, this is primarily for debugging. + AddOverrideRequestCallback(cb auth.RequestCallback) + // Adds a specific callback to be used on auth requests. This will preempt a + // general callback, meaning the request will be heard on this callback and not + // the general. Request will still be heard on override callbacks. + AddSpecificRequestCallback(id *id.ID, cb auth.RequestCallback) + // Removes a specific callback to be used on auth requests. + RemoveSpecificRequestCallback(id *id.ID) + // Adds a general callback to be used on auth confirms. This will be preempted + // by any specific callback + AddGeneralConfirmCallback(cb auth.ConfirmCallback) + // Adds a general callback to be used on auth confirms. This will not be + // preempted by any specific callback. It is recommended that the specific + // callbacks are used, this is primarily for debugging. + AddOverrideConfirmCallback(cb auth.ConfirmCallback) + // Adds a specific callback to be used on auth confirms. This will preempt a + // general callback, meaning the request will be heard on this callback and not + // the general. Request will still be heard on override callbacks. + AddSpecificConfirmCallback(id *id.ID, cb auth.ConfirmCallback) + // Removes a specific callback to be used on auth confirm. + RemoveSpecificConfirmCallback(id *id.ID) +}