From 9219bf5aa94b1e845dd4f6cb8593f55ac925ba6b Mon Sep 17 00:00:00 2001 From: Mickael Remond Date: Tue, 18 Jun 2019 14:24:33 +0200 Subject: [PATCH] Add namespace delegation and priviledged entity example --- _examples/delegation/README.md | 5 + _examples/delegation/delegation.go | 227 +++++++++++++++++++++++++++++ 2 files changed, 232 insertions(+) create mode 100644 _examples/delegation/README.md create mode 100644 _examples/delegation/delegation.go diff --git a/_examples/delegation/README.md b/_examples/delegation/README.md new file mode 100644 index 0000000..4389a36 --- /dev/null +++ b/_examples/delegation/README.md @@ -0,0 +1,5 @@ +# Advanced component: delegation + +`delegation` is an example of advanced component supporting Namespace Delegation +([XEP-0355](https://xmpp.org/extensions/xep-0355.html)) and privileged entity +([XEP-356](https://xmpp.org/extensions/xep-0356.html)). diff --git a/_examples/delegation/delegation.go b/_examples/delegation/delegation.go new file mode 100644 index 0000000..6f40b2a --- /dev/null +++ b/_examples/delegation/delegation.go @@ -0,0 +1,227 @@ +package main + +import ( + "encoding/xml" + "fmt" + "log" + + "gosrc.io/xmpp" +) + +func main() { + opts := xmpp.ComponentOptions{ + Domain: "service.localhost", + Secret: "mypass", + Address: "localhost:9999", + + // TODO: Move that part to a component discovery handler + Name: "Test Component", + Category: "gateway", + Type: "service", + } + + router := xmpp.NewRouter() + router.HandleFunc("message", HandleMessage) + router.NewRoute(). + IQNamespaces(xmpp.NSDiscoInfo). + HandlerFunc(func(s xmpp.Sender, p xmpp.Packet) { + DiscoInfo(s, p, opts) + }) + router.NewRoute(). + IQNamespaces("urn:xmpp:delegation:1"). + HandlerFunc(HandleDelegation) + + component, err := xmpp.NewComponent(opts, router) + if err != nil { + log.Fatalf("%+v", err) + } + + // If you pass the component to a stream manager, it will handle the reconnect policy + // for you automatically. + // TODO: Post Connect could be a feature of the router or the client. Move it somewhere else. + cm := xmpp.NewStreamManager(component, nil) + log.Fatal(cm.Run()) +} + +func HandleMessage(_ xmpp.Sender, p xmpp.Packet) { + msg, ok := p.(xmpp.Message) + if !ok { + return + } + var msgProcessed bool + for _, ext := range msg.Extensions { + delegation, ok := ext.(*xmpp.Delegation) + if ok { + msgProcessed = true + fmt.Printf("Delegation confirmed for namespace %s\n", delegation.Delegated.Namespace) + } + } + // TODO: Decode privilege message + // + + if !msgProcessed { + fmt.Printf("Ignored received message, not related to delegation: %v\n", msg) + } +} + +const ( + pubsubNode = "urn:xmpp:delegation:1::http://jabber.org/protocol/pubsub" + pepNode = "urn:xmpp:delegation:1:bare:http://jabber.org/protocol/pubsub" +) + +// TODO: replace xmpp.Sender by ctx xmpp.Context ? +// ctx.Stream.Send / SendRaw +// ctx.Opts +func DiscoInfo(c xmpp.Sender, p xmpp.Packet, opts xmpp.ComponentOptions) { + // Type conversion & sanity checks + iq, ok := p.(xmpp.IQ) + if !ok { + return + } + info, ok := iq.Payload[0].(*xmpp.DiscoInfo) + if !ok { + return + } + + iqResp := xmpp.NewIQ("result", iq.To, iq.From, iq.Id, "en") + + switch info.Node { + case "": + DiscoInfoRoot(&iqResp, opts) + case pubsubNode: + DiscoInfoPubSub(&iqResp) + case pepNode: + DiscoInfoPEP(&iqResp) + } + + _ = c.Send(iqResp) +} + +func DiscoInfoRoot(iqResp *xmpp.IQ, opts xmpp.ComponentOptions) { + // Higher level discovery + identity := xmpp.Identity{ + Name: opts.Name, + Category: opts.Category, + Type: opts.Type, + } + payload := xmpp.DiscoInfo{ + XMLName: xml.Name{ + Space: xmpp.NSDiscoInfo, + Local: "query", + }, + Identity: identity, + Features: []xmpp.Feature{ + {Var: xmpp.NSDiscoInfo}, + {Var: xmpp.NSDiscoItems}, + {Var: "jabber:iq:version"}, + {Var: "urn:xmpp:delegation:1"}, + }, + } + iqResp.AddPayload(&payload) +} + +func DiscoInfoPubSub(iqResp *xmpp.IQ) { + payload := xmpp.DiscoInfo{ + XMLName: xml.Name{ + Space: xmpp.NSDiscoInfo, + Local: "query", + }, + Node: pubsubNode, + Features: []xmpp.Feature{ + {Var: "http://jabber.org/protocol/pubsub"}, + {Var: "http://jabber.org/protocol/pubsub#publish"}, + {Var: "http://jabber.org/protocol/pubsub#subscribe"}, + {Var: "http://jabber.org/protocol/pubsub#publish-options"}, + }, + } + iqResp.AddPayload(&payload) +} + +func DiscoInfoPEP(iqResp *xmpp.IQ) { + identity := xmpp.Identity{ + Category: "pubsub", + Type: "pep", + } + payload := xmpp.DiscoInfo{ + XMLName: xml.Name{ + Space: xmpp.NSDiscoInfo, + Local: "query", + }, + Identity: identity, + Node: pepNode, + Features: []xmpp.Feature{ + {Var: "http://jabber.org/protocol/pubsub#access-presence"}, + {Var: "http://jabber.org/protocol/pubsub#auto-create"}, + {Var: "http://jabber.org/protocol/pubsub#auto-subscribe"}, + {Var: "http://jabber.org/protocol/pubsub#config-node"}, + {Var: "http://jabber.org/protocol/pubsub#create-and-configure"}, + {Var: "http://jabber.org/protocol/pubsub#create-nodes"}, + {Var: "http://jabber.org/protocol/pubsub#filtered-notifications"}, + {Var: "http://jabber.org/protocol/pubsub#persistent-items"}, + {Var: "http://jabber.org/protocol/pubsub#publish"}, + {Var: "http://jabber.org/protocol/pubsub#retrieve-items"}, + {Var: "http://jabber.org/protocol/pubsub#subscribe"}, + }, + } + iqResp.AddPayload(&payload) +} + +func HandleDelegation(s xmpp.Sender, p xmpp.Packet) { + // Type conversion & sanity checks + iq, ok := p.(xmpp.IQ) + if !ok { + return + } + + payload1 := iq.Payload[0] + delegation, ok := payload1.(*xmpp.Delegation) + if !ok { + return + } + forwardedPacket := delegation.Forwarded.Stanza + fmt.Println(forwardedPacket) + forwardedIQ, ok := forwardedPacket.(xmpp.IQ) + if !ok { + return + } + payload := forwardedIQ.Payload + if len(payload) == 0 { + return + } + + pubsub, ok := payload[0].(*xmpp.PubSub) + if !ok { + // We only support pubsub delegation + return + } + + if pubsub.Publish.XMLName.Local == "publish" { + // Prepare pubsub IQ reply + iqResp := xmpp.NewIQ("result", forwardedIQ.To, forwardedIQ.From, forwardedIQ.Id, "en") + payload := xmpp.PubSub{ + XMLName: xml.Name{ + Space: "http://jabber.org/protocol/pubsub", + Local: "pubsub", + }, + } + iqResp.AddPayload(&payload) + // Wrap the reply in delegation 'forward' + iqForward := xmpp.NewIQ("result", iq.To, iq.From, iq.Id, "en") + delegPayload := xmpp.Delegation{ + XMLName: xml.Name{ + Space: "urn:xmpp:delegation:1", + Local: "delegation", + }, + Forwarded: &xmpp.Forwarded{ + XMLName: xml.Name{ + Space: "urn:xmpp:forward:0", + Local: "forward", + }, + Stanza: iqResp, + }, + } + iqForward.AddPayload(&delegPayload) + _ = s.Send(iqForward) + // TODO: The component should actually broadcast the mood to subscribers + } +}