1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171
|
package integration
import (
"context"
"fmt"
"os"
"testing"
"go.mongodb.org/mongo-driver/bson/bsontype"
"go.mongodb.org/mongo-driver/internal/testutil"
"go.mongodb.org/mongo-driver/mongo/description"
"go.mongodb.org/mongo-driver/mongo/writeconcern"
"go.mongodb.org/mongo-driver/x/bsonx/bsoncore"
"go.mongodb.org/mongo-driver/x/mongo/driver"
"go.mongodb.org/mongo-driver/x/mongo/driver/connstring"
)
type scramTestCase struct {
username string
password string
mechanisms []string
altPassword string
}
func TestSCRAM(t *testing.T) {
if os.Getenv("AUTH") != "auth" {
t.Skip("Skipping because authentication is required")
}
server, err := testutil.Topology(t).SelectServer(context.Background(), description.WriteSelector())
noerr(t, err)
serverConnection, err := server.Connection(context.Background())
noerr(t, err)
defer serverConnection.Close()
if !serverConnection.Description().WireVersion.Includes(7) {
t.Skip("Skipping because MongoDB 4.0 is needed for SCRAM-SHA-256")
}
// Unicode constants for testing
var romanFour = "\u2163" // ROMAN NUMERAL FOUR -> SASL prepped is "IV"
var romanNine = "\u2168" // ROMAN NUMERAL NINE -> SASL prepped is "IX"
testUsers := []scramTestCase{
// SCRAM spec test steps 1-3
{username: "sha1", password: "sha1", mechanisms: []string{"SCRAM-SHA-1"}},
{username: "sha256", password: "sha256", mechanisms: []string{"SCRAM-SHA-256"}},
{username: "both", password: "both", mechanisms: []string{"SCRAM-SHA-1", "SCRAM-SHA-256"}},
// SCRAM spec test step 4
{username: "IX", password: "IX", mechanisms: []string{"SCRAM-SHA-256"}, altPassword: "I\u00ADX"},
{username: romanNine, password: romanFour, mechanisms: []string{"SCRAM-SHA-256"}, altPassword: "I\u00ADV"},
}
// Verify that test (root) user is authenticated. If this fails, the
// rest of the test can't succeed.
wc := writeconcern.New(writeconcern.WMajority())
collOne := testutil.ColName(t)
testutil.DropCollection(t, testutil.DBName(t), collOne)
testutil.InsertDocs(t, testutil.DBName(t),
collOne, wc, bsoncore.BuildDocument(nil, bsoncore.AppendStringElement(nil, "name", "scram_test")),
)
// Test step 1: Create users for test cases
err = createScramUsers(t, server, testUsers)
if err != nil {
t.Fatal(err)
}
// Step 2 and 3a: For each auth mechanism, "SCRAM-SHA-1", "SCRAM-SHA-256"
// and "negotiate" (a fake, placeholder mechanism), iterate over each user
// and ensure that each mechanism that should succeed does so and each
// that should fail does so.
for _, m := range []string{"SCRAM-SHA-1", "SCRAM-SHA-256", "negotiate"} {
for _, c := range testUsers {
t.Run(
fmt.Sprintf("%s %s", c.username, m),
func(t *testing.T) {
err := testScramUserAuthWithMech(t, c, m)
if m == "negotiate" || hasAuthMech(c.mechanisms, m) {
noerr(t, err)
} else {
autherr(t, err)
}
},
)
}
}
// Step 3b: test non-existing user with negotiation fails with
// an auth.Error type.
bogus := scramTestCase{username: "eliot", password: "trustno1"}
err = testScramUserAuthWithMech(t, bogus, "negotiate")
autherr(t, err)
// XXX Step 4: test alternate password forms
for _, c := range testUsers {
if c.altPassword == "" {
continue
}
c.password = c.altPassword
t.Run(
fmt.Sprintf("%s alternate password", c.username),
func(t *testing.T) {
err := testScramUserAuthWithMech(t, c, "SCRAM-SHA-256")
noerr(t, err)
},
)
}
}
func hasAuthMech(mechs []string, m string) bool {
for _, v := range mechs {
if v == m {
return true
}
}
return false
}
func testScramUserAuthWithMech(t *testing.T, c scramTestCase, mech string) error {
t.Helper()
cs := testutil.ConnString(t)
cs.Username = c.username
cs.Password = c.password
cs.AuthSource = testutil.DBName(t)
switch mech {
case "negotiate":
cs.AuthMechanism = ""
default:
cs.AuthMechanism = mech
}
return runScramAuthTest(t, cs)
}
func runScramAuthTest(t *testing.T, cs connstring.ConnString) error {
t.Helper()
topology := testutil.TopologyWithConnString(t, cs)
server, err := topology.SelectServer(context.Background(), description.WriteSelector())
noerr(t, err)
cmd := bsoncore.BuildDocument(nil, bsoncore.AppendInt32Element(nil, "dbstats", 1))
_, err = testutil.RunCommand(t, server, testutil.DBName(t), cmd)
return err
}
func createScramUsers(t *testing.T, s driver.Server, cases []scramTestCase) error {
db := testutil.DBName(t)
for _, c := range cases {
var values []bsoncore.Value
for _, v := range c.mechanisms {
values = append(values, bsoncore.Value{Type: bsontype.String, Data: bsoncore.AppendString(nil, v)})
}
newUserCmd := bsoncore.BuildDocumentFromElements(nil,
bsoncore.AppendStringElement(nil, "createUser", c.username),
bsoncore.AppendStringElement(nil, "pwd", c.password),
bsoncore.AppendArrayElement(nil, "roles", bsoncore.BuildArray(nil,
bsoncore.Value{Type: bsontype.EmbeddedDocument, Data: bsoncore.BuildDocumentFromElements(nil,
bsoncore.AppendStringElement(nil, "role", "readWrite"),
bsoncore.AppendStringElement(nil, "db", db),
)},
)),
bsoncore.AppendArrayElement(nil, "mechanisms", bsoncore.BuildArray(nil, values...)),
)
_, err := testutil.RunCommand(t, s, db, newUserCmd)
if err != nil {
return fmt.Errorf("Couldn't create user '%s' on db '%s': %v", c.username, testutil.DBName(t), err)
}
}
return nil
}
|