// Copyright 2016 The Mellium Contributors.
// Use of this source code is governed by the BSD 2-clause
// license that can be found in the LICENSE file.
package xmpp_test
import (
"bytes"
"context"
"encoding/xml"
"strings"
"testing"
"mellium.im/sasl"
"mellium.im/xmpp"
"mellium.im/xmpp/internal/ns"
"mellium.im/xmpp/internal/saslerr"
"mellium.im/xmpp/internal/xmpptest"
)
func TestSASLPanicsNoMechanisms(t *testing.T) {
defer func() {
if r := recover(); r == nil {
t.Error("Expected call to SASL() with no mechanisms to panic")
}
}()
_ = xmpp.SASL("", "")
}
func TestSASLList(t *testing.T) {
b := &bytes.Buffer{}
e := xml.NewEncoder(b)
start := xml.StartElement{Name: xml.Name{Space: ns.SASL, Local: "mechanisms"}}
s := xmpp.SASL("", "", sasl.Plain, sasl.ScramSha256)
req, err := s.List(context.Background(), e, start)
switch {
case err != nil:
t.Fatal(err)
case req != true:
t.Error("Expected SASL to be a required feature")
}
if err = e.Flush(); err != nil {
t.Fatal(err)
}
// Mechanisms should be printed exactly thus:
if !bytes.Contains(b.Bytes(), []byte(`PLAIN`)) {
t.Error("Expected mechanisms list to include PLAIN")
}
if !bytes.Contains(b.Bytes(), []byte(`SCRAM-SHA-256`)) {
t.Error("Expected mechanisms list to include SCRAM-SHA-256")
}
// The wrapper can be a bit more flexible as long as the mechanisms are there.
d := xml.NewDecoder(b)
tok, err := d.Token()
if err != nil {
t.Fatal(err)
}
se := tok.(xml.StartElement)
if se.Name.Local != "mechanisms" || se.Name.Space != ns.SASL {
t.Errorf("Unexpected name for mechanisms start element: %+v", se.Name)
}
// Skip two mechanisms
_, err = d.Token()
if err != nil {
t.Fatal(err)
}
err = d.Skip()
if err != nil {
t.Fatal(err)
}
_, err = d.Token()
if err != nil {
t.Fatal(err)
}
err = d.Skip()
if err != nil {
t.Fatal(err)
}
// Check the end token.
tok, err = d.Token()
if err != nil {
t.Fatal(err)
}
_ = tok.(xml.EndElement)
}
func TestSASLParse(t *testing.T) {
s := xmpp.SASL("", "", sasl.Plain)
for _, test := range []struct {
xml string
items []string
err bool
}{
{`
EXTERNAL
SCRAM-SHA-1-PLUS
SCRAM-SHA-1
PLAIN
`, []string{"EXTERNAL", "PLAIN", "SCRAM-SHA-1-PLUS", "SCRAM-SHA-1"}, false},
{`PLAIN`, nil, true},
{`PLAIN`, nil, true},
{`PLAIN`, []string{}, false},
} {
r := strings.NewReader(test.xml)
d := xml.NewDecoder(r)
tok, _ := d.Token()
start := tok.(xml.StartElement)
req, list, err := s.Parse(context.Background(), d, &start)
switch {
case test.err && err == nil:
t.Error("Expected sasl mechanism parsing to error")
case !test.err && err != nil:
t.Error(err)
case req != true:
t.Error("Expected parsed SASL feature to be required")
case len(list.([]string)) != len(test.items):
t.Errorf("Expected data to contain 4 items, got %d", len(list.([]string)))
}
for _, m := range test.items {
matched := false
for _, m2 := range list.([]string) {
if m == m2 {
matched = true
break
}
}
if !matched {
t.Fatalf("Expected data to contain %v", m)
}
}
}
}
func panicPerms(*sasl.Negotiator) bool {
panic("permissions function should not be called")
}
var saslTestCases = [...]xmpptest.FeatureTestCase{
// Simple client tests with plain.
0: {
Feature: xmpp.SASL("", "", sasl.Plain),
In: ``,
Out: `AHRlc3QA`,
Err: xmpp.ErrUnexpectedPayload,
},
1: {
Feature: xmpp.SASL("", "", sasl.Plain),
In: ``,
Out: `AHRlc3QA`,
Err: xmpp.ErrUnexpectedPayload,
},
2: {
Feature: xmpp.SASL("", "", sasl.Plain),
In: ``,
Out: `AHRlc3QA`,
Err: saslerr.Failure{Condition: saslerr.NotAuthorized},
},
3: {
Feature: xmpp.SASL("", "", sasl.Plain),
In: ``,
Out: `AHRlc3QA`,
FinalState: xmpp.Authn,
Err: saslerr.Failure{Condition: saslerr.NotAuthorized},
},
// Simple server tests with plain.
4: {
State: xmpp.Received,
Feature: xmpp.SASLServer(panicPerms, sasl.Plain),
In: ``,
Out: ``,
Err: xmpp.ErrUnexpectedPayload,
},
5: {
State: xmpp.Received,
Feature: xmpp.SASLServer(panicPerms, sasl.Plain),
In: ``,
Err: xmpp.ErrUnexpectedPayload,
},
6: {
// TODO: can the client send failure?
State: xmpp.Received,
Feature: xmpp.SASLServer(panicPerms, sasl.Plain),
In: ``,
Err: saslerr.Failure{Condition: saslerr.NotAuthorized},
},
7: {
State: xmpp.Received,
Feature: xmpp.SASLServer(panicPerms, sasl.Plain),
In: ``,
Out: ``,
Err: xmpp.ErrTerminated,
},
8: {
State: xmpp.Received,
Feature: xmpp.SASLServer(func(*sasl.Negotiator) bool {
return false
}, sasl.Plain),
In: `AHRlc3QA`,
Out: ``,
Err: sasl.ErrAuthn,
},
9: {
State: xmpp.Received,
Feature: xmpp.SASLServer(func(*sasl.Negotiator) bool {
return true
}, sasl.Plain),
In: `AHRlc3QA`,
Out: ``,
FinalState: xmpp.Authn,
},
}
func TestSASL(t *testing.T) {
xmpptest.RunFeatureTests(t, saslTestCases[:])
}