...

Source file src/go.mongodb.org/mongo-driver/x/mongo/driver/integration/scram_test.go

Documentation: go.mongodb.org/mongo-driver/x/mongo/driver/integration

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

View as plain text