From 4f68c5eee23000110ea566dba0d842ceaee51f4c Mon Sep 17 00:00:00 2001 From: Mickael Remond Date: Tue, 1 Oct 2019 11:30:23 +0200 Subject: [PATCH] Add X-OAUTH2 authentication and example --- _examples/xmpp_echo/xmpp_echo.go | 3 -- _examples/xmpp_oauth2/xmpp_oauth2.go | 48 ++++++++++++++++++++++++++++ auth.go | 36 ++++++++++++++------- 3 files changed, 72 insertions(+), 15 deletions(-) create mode 100644 _examples/xmpp_oauth2/xmpp_oauth2.go diff --git a/_examples/xmpp_echo/xmpp_echo.go b/_examples/xmpp_echo/xmpp_echo.go index f987ac5..3ee9b2e 100644 --- a/_examples/xmpp_echo/xmpp_echo.go +++ b/_examples/xmpp_echo/xmpp_echo.go @@ -48,6 +48,3 @@ func handleMessage(s xmpp.Sender, p stanza.Packet) { reply := stanza.Message{Attrs: stanza.Attrs{To: msg.From}, Body: msg.Body} _ = s.Send(reply) } - -// TODO create default command line client to send message or to send an arbitrary XMPP sequence from a file, -// (using templates ?) diff --git a/_examples/xmpp_oauth2/xmpp_oauth2.go b/_examples/xmpp_oauth2/xmpp_oauth2.go new file mode 100644 index 0000000..a5cace5 --- /dev/null +++ b/_examples/xmpp_oauth2/xmpp_oauth2.go @@ -0,0 +1,48 @@ +/* +xmpp_oauth2 is a demo client that connect on an XMPP server using OAuth2 and prints received messages. +*/ + +package main + +import ( + "fmt" + "log" + "os" + + "gosrc.io/xmpp" + "gosrc.io/xmpp/stanza" +) + +func main() { + config := xmpp.Config{ + Address: "localhost:5222", + Jid: "test@localhost", + Credential: xmpp.OAuthToken("OdAIsBlY83SLBaqQoClAn7vrZSHxixT8"), + StreamLogger: os.Stdout, + // Insecure: true, + // TLSConfig: tls.Config{InsecureSkipVerify: true}, + } + + router := xmpp.NewRouter() + router.HandleFunc("message", handleMessage) + + client, err := xmpp.NewClient(config, router) + if err != nil { + log.Fatalf("%+v", err) + } + + // If you pass the client to a connection manager, it will handle the reconnect policy + // for you automatically. + cm := xmpp.NewStreamManager(client, nil) + log.Fatal(cm.Run()) +} + +func handleMessage(s xmpp.Sender, p stanza.Packet) { + msg, ok := p.(stanza.Message) + if !ok { + _, _ = fmt.Fprintf(os.Stdout, "Ignoring packet: %T\n", p) + return + } + + _, _ = fmt.Fprintf(os.Stdout, "Body = %s - from = %s\n", msg.Body, msg.From) +} diff --git a/auth.go b/auth.go index 0d5b79d..726e15a 100644 --- a/auth.go +++ b/auth.go @@ -37,28 +37,30 @@ func OAuthToken(token string) Credential { // Authentication flow for SASL mechanisms func authSASL(socket io.ReadWriter, decoder *xml.Decoder, f stanza.StreamFeatures, user string, credential Credential) (err error) { - // TODO: Implement other type of SASL mechanisms - havePlain := false - for _, m := range f.Mechanisms.Mechanism { - if m == "PLAIN" { - havePlain = true + var matchingMech string + for _, mech := range credential.mechanisms { + if isSupportedMech(mech, f.Mechanisms.Mechanism) { + matchingMech = mech break } } - if !havePlain { - err := fmt.Errorf("PLAIN authentication is not supported by server: %v", f.Mechanisms.Mechanism) + + switch matchingMech { + case "PLAIN", "X-OAUTH2": + // TODO: Implement other type of SASL mechanisms + return authPlain(socket, decoder, matchingMech, user, credential.secret) + default: + err := fmt.Errorf("no matching authentication (%v) supported by server: %v", credential.mechanisms, f.Mechanisms.Mechanism) return NewConnError(err, true) } - - return authPlain(socket, decoder, user, credential) } // Plain authentication: send base64-encoded \x00 user \x00 password -func authPlain(socket io.ReadWriter, decoder *xml.Decoder, user string, credential Credential) error { - raw := "\x00" + user + "\x00" + credential.secret +func authPlain(socket io.ReadWriter, decoder *xml.Decoder, mech string, user string, secret string) error { + raw := "\x00" + user + "\x00" + secret enc := make([]byte, base64.StdEncoding.EncodedLen(len(raw))) base64.StdEncoding.Encode(enc, []byte(raw)) - fmt.Fprintf(socket, "%s", stanza.NSSASL, enc) + fmt.Fprintf(socket, "%s", stanza.NSSASL, mech, enc) // Next message should be either success or failure. val, err := stanza.NextPacket(decoder) @@ -77,3 +79,13 @@ func authPlain(socket io.ReadWriter, decoder *xml.Decoder, user string, credenti } return err } + +// isSupportedMech returns true if the mechanism is supported in the provided list. +func isSupportedMech(mech string, mechanisms []string) bool { + for _, m := range mechanisms { + if mech == m { + return true + } + } + return false +}