3 Commits

Author SHA1 Message Date
Mickael Remond
2781563ea7 Update go.sum file 2019-10-01 11:40:31 +02:00
Mickael Remond
4f68c5eee2 Add X-OAUTH2 authentication and example 2019-10-01 11:40:31 +02:00
Mickael Remond
9c8353d081 Introduce Credential structure to define auth type
For now we are planning to support Password and OAuthToken.
In the future, we would like to add certificate-based authentication.
2019-10-01 11:40:31 +02:00
12 changed files with 119 additions and 34 deletions

View File

@@ -34,8 +34,8 @@ Here is an example code to configure a client to allow connecting to a server wi
config := xmpp.Config{
Address: "localhost:5222",
Jid: "test@localhost",
Password: "test",
TLSConfig: tls.Config{InsecureSkipVerify: true},
Credential: xmpp.Password("Test"),
TLSConfig: tls.Config{InsecureSkipVerify: true},
}
```
@@ -96,7 +96,7 @@ func main() {
config := xmpp.Config{
Address: "localhost:5222",
Jid: "test@localhost",
Password: "test",
Credential: xmpp.Password("Test"),
StreamLogger: os.Stdout,
Insecure: true,
}

View File

@@ -4,4 +4,5 @@ github.com/processone/soundcloud v1.0.0/go.mod h1:kDLeWpkRtN3C8kIReQdxoiRi92P9xR
golang.org/x/net v0.0.0-20190110200230-915654e7eabc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522 h1:bhOzK9QyoD0ogCnFro1m2mz41+Ib0oOhfJnBp5MR4K4=
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 h1:9zdDQZ7Thm29KFXgAX/+yaf3eVbP7djjWp/dXAppNCc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

View File

@@ -17,7 +17,7 @@ func main() {
config := xmpp.Config{
Address: "localhost:5222",
Jid: "test@localhost",
Password: "test",
Credential: xmpp.Password("test"),
StreamLogger: os.Stdout,
Insecure: true,
// TLSConfig: tls.Config{InsecureSkipVerify: true},
@@ -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 ?)

View File

@@ -32,9 +32,9 @@ func main() {
// 2. Prepare XMPP client
config := xmpp.Config{
Address: *address,
Jid: *jid,
Password: *password,
Address: *address,
Jid: *jid,
Credential: xmpp.Password(*password),
// StreamLogger: os.Stdout,
Insecure: true,
}

View File

@@ -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)
}

64
auth.go
View File

@@ -10,29 +10,57 @@ import (
"gosrc.io/xmpp/stanza"
)
func authSASL(socket io.ReadWriter, decoder *xml.Decoder, f stanza.StreamFeatures, user string, password string) (err error) {
// TODO: Implement other type of SASL Authentication
havePlain := false
for _, m := range f.Mechanisms.Mechanism {
if m == "PLAIN" {
havePlain = true
// Credential is used to pass the type of secret that will be used to connect to XMPP server.
// It can be either a password or an OAuth 2 bearer token.
type Credential struct {
secret string
mechanisms []string
}
func Password(pwd string) Credential {
credential := Credential{
secret: pwd,
mechanisms: []string{"PLAIN"},
}
return credential
}
func OAuthToken(token string) Credential {
credential := Credential{
secret: token,
mechanisms: []string{"X-OAUTH2"},
}
return credential
}
// ============================================================================
// Authentication flow for SASL mechanisms
func authSASL(socket io.ReadWriter, decoder *xml.Decoder, f stanza.StreamFeatures, user string, credential Credential) (err error) {
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, password)
}
// Plain authentication: send base64-encoded \x00 user \x00 password
func authPlain(socket io.ReadWriter, decoder *xml.Decoder, user string, password string) error {
raw := "\x00" + user + "\x00" + password
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, "<auth xmlns='%s' mechanism='PLAIN'>%s</auth>", stanza.NSSASL, enc)
fmt.Fprintf(socket, "<auth xmlns='%s' mechanism='%s'>%s</auth>", stanza.NSSASL, mech, enc)
// Next message should be either success or failure.
val, err := stanza.NextPacket(decoder)
@@ -51,3 +79,13 @@ func authPlain(socket io.ReadWriter, decoder *xml.Decoder, user string, password
}
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
}

View File

@@ -111,8 +111,8 @@ func NewClient(config Config, r *Router) (c *Client, err error) {
return nil, NewConnError(err, true)
}
if config.Password == "" {
err = errors.New("missing password")
if config.Credential.secret == "" {
err = errors.New("missing credential")
return nil, NewConnError(err, true)
}

View File

@@ -25,7 +25,7 @@ func TestClient_Connect(t *testing.T) {
mock.Start(t, testXMPPAddress, handlerConnectSuccess)
// Test / Check result
config := Config{Address: testXMPPAddress, Jid: "test@localhost", Password: "test", Insecure: true}
config := Config{Address: testXMPPAddress, Jid: "test@localhost", Credential: Password("test"), Insecure: true}
var client *Client
var err error
@@ -47,7 +47,7 @@ func TestClient_NoInsecure(t *testing.T) {
mock.Start(t, testXMPPAddress, handlerAbortTLS)
// Test / Check result
config := Config{Address: testXMPPAddress, Jid: "test@localhost", Password: "test"}
config := Config{Address: testXMPPAddress, Jid: "test@localhost", Credential: Password("test")}
var client *Client
var err error
@@ -71,7 +71,7 @@ func TestClient_FeaturesTracking(t *testing.T) {
mock.Start(t, testXMPPAddress, handlerAbortTLS)
// Test / Check result
config := Config{Address: testXMPPAddress, Jid: "test@localhost", Password: "test"}
config := Config{Address: testXMPPAddress, Jid: "test@localhost", Credential: Password("test")}
var client *Client
var err error
@@ -94,7 +94,7 @@ func TestClient_RFC3921Session(t *testing.T) {
mock.Start(t, testXMPPAddress, handlerConnectWithSession)
// Test / Check result
config := Config{Address: testXMPPAddress, Jid: "test@localhost", Password: "test", Insecure: true}
config := Config{Address: testXMPPAddress, Jid: "test@localhost", Credential: Password("test"), Insecure: true}
var client *Client
var err error

View File

@@ -32,9 +32,9 @@ func sendxmpp(cmd *cobra.Command, args []string) {
var err error
client, err := xmpp.NewClient(xmpp.Config{
Jid: viper.GetString("jid"),
Address: viper.GetString("addr"),
Password: viper.GetString("password"),
Jid: viper.GetString("jid"),
Address: viper.GetString("addr"),
Credential: xmpp.Password(viper.GetString("password")),
}, xmpp.NewRouter())
if err != nil {

View File

@@ -143,6 +143,7 @@ golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGm
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522 h1:bhOzK9QyoD0ogCnFro1m2mz41+Ib0oOhfJnBp5MR4K4=
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 h1:9zdDQZ7Thm29KFXgAX/+yaf3eVbP7djjWp/dXAppNCc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=

View File

@@ -10,7 +10,7 @@ type Config struct {
Address string
Jid string
parsedJid *Jid // For easier manipulation
Password string
Credential Credential
StreamLogger *os.File // Used for debugging
Lang string // TODO: should default to 'en'
ConnectTimeout int // Client timeout in seconds. Default to 15

View File

@@ -168,7 +168,7 @@ func (s *Session) auth(o Config) {
return
}
s.err = authSASL(s.streamLogger, s.decoder, s.Features, o.parsedJid.Node, o.Password)
s.err = authSASL(s.streamLogger, s.decoder, s.Features, o.parsedJid.Node, o.Credential)
}
// Attempt to resume session using stream management