Skip to content

Commit

Permalink
Support localhost exception (#4156)
Browse files Browse the repository at this point in the history
Closes #4100.
  • Loading branch information
chilagrow committed Mar 12, 2024
1 parent eb93ab1 commit 75f5a74
Show file tree
Hide file tree
Showing 19 changed files with 302 additions and 200 deletions.
3 changes: 2 additions & 1 deletion CONTRIBUTING.md
Expand Up @@ -183,7 +183,8 @@ you can run those with `task test-unit` after starting the environment as descri

We also have a set of "integration" tests in the `integration` directory.
They use the Go MongoDB driver like a regular user application.
They could test any MongoDB-compatible database (such as FerretDB or MongoDB itself) via a regular TCP or TLS port or Unix socket.
They could test any MongoDB-compatible database (such as FerretDB or MongoDB itself) via a regular TCP or TLS port
or Unix domain socket.
They also could test in-process FerretDB instances
(meaning that integration tests start and stop them themselves) with a given backend.
Finally, some integration tests (so-called compatibility or "compat" tests) connect to two systems
Expand Down
2 changes: 1 addition & 1 deletion ferretdb/ferretdb.go
Expand Up @@ -214,7 +214,7 @@ func (f *FerretDB) MongoDBURI() string {
Path: "/",
}
case f.config.Listener.Unix != "":
// MongoDB really wants Unix socket path in the host part of the URI
// MongoDB really wants Unix domain socket path in the host part of the URI
u = &url.URL{
Scheme: "mongodb",
Host: f.l.UnixAddr().String(),
Expand Down
125 changes: 82 additions & 43 deletions integration/commands_authentication_test.go
Expand Up @@ -17,23 +17,36 @@ package integration
import (
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"

"github.com/FerretDB/FerretDB/integration/setup"
"github.com/FerretDB/FerretDB/internal/types"
"github.com/FerretDB/FerretDB/internal/util/must"
"github.com/FerretDB/FerretDB/internal/util/testutil"
)

func TestCommandsAuthenticationLogout(t *testing.T) {
t.Parallel()

ctx, collection := setup.Setup(t)
db := collection.Database()
s := setup.SetupWithOpts(t, nil)
ctx, db := s.Ctx, s.Collection.Database()

opts := options.Client().ApplyURI(s.MongoDBURI)
client, err := mongo.Connect(ctx, opts)
require.NoError(t, err)

t.Cleanup(func() {
require.NoError(t, client.Disconnect(ctx))
})

db = client.Database(db.Name())

// the test user logs out
var res bson.D
err := db.RunCommand(ctx, bson.D{{"logout", 1}}).Decode(&res)
assert.NoError(t, err)
err = db.RunCommand(ctx, bson.D{{"logout", 1}}).Decode(&res)
require.NoError(t, err)

actual := ConvertDocument(t, res)
actual.Remove("$clusterTime")
Expand All @@ -44,7 +57,7 @@ func TestCommandsAuthenticationLogout(t *testing.T) {

// the test user logs out again, it has no effect
err = db.RunCommand(ctx, bson.D{{"logout", 1}}).Decode(&res)
assert.NoError(t, err)
require.NoError(t, err)

actual = ConvertDocument(t, res)
actual.Remove("$clusterTime")
Expand All @@ -53,47 +66,73 @@ func TestCommandsAuthenticationLogout(t *testing.T) {
testutil.AssertEqual(t, expected, actual)
}

func TestCommandsAuthenticationLogoutTLS(t *testing.T) {
setup.SkipForMongoDB(t, "tls is not enabled for mongodb backend")

func TestCommandsAuthenticationLogoutAuthenticatedUser(t *testing.T) {
t.Parallel()

ctx, collection := setup.Setup(t)
db := collection.Database()

// the test user is authenticated
expectedAuthenticated := bson.D{
{
"authInfo", bson.D{
{"authenticatedUsers", bson.A{bson.D{{"user", "username"}}}},
{"authenticatedUserRoles", bson.A{}},
{"authenticatedUserPrivileges", bson.A{}},
},
},
{"ok", float64(1)},
s := setup.SetupWithOpts(t, &setup.SetupOpts{SetupUser: true})
ctx, db := s.Ctx, s.Collection.Database()
username, password, mechanism := "testuser", "testpass", "SCRAM-SHA-256"

err := db.RunCommand(ctx, bson.D{
{"createUser", username},
{"roles", bson.A{}},
{"pwd", password},
{"mechanisms", bson.A{mechanism}},
}).Err()
require.NoError(t, err, "cannot create user")

credential := options.Credential{
AuthMechanism: mechanism,
AuthSource: db.Name(),
Username: username,
Password: password,
}

opts := options.Client().ApplyURI(s.MongoDBURI).SetAuth(credential)
client, err := mongo.Connect(ctx, opts)
require.NoError(t, err)

t.Cleanup(func() {
require.NoError(t, client.Disconnect(ctx))
})

db = client.Database(db.Name())

var res bson.D
err := db.RunCommand(ctx, bson.D{{"connectionStatus", 1}}).Decode(&res)
assert.NoError(t, err)
assert.Equal(t, expectedAuthenticated, res)
err = db.RunCommand(ctx, bson.D{{"connectionStatus", 1}}).Decode(&res)
require.NoError(t, err)

actualAuth, err := ConvertDocument(t, res).Get("authInfo")
require.NoError(t, err)

actualUsersV, err := actualAuth.(*types.Document).Get("authenticatedUsers")
require.NoError(t, err)

actualUsers := actualUsersV.(*types.Array)
require.Equal(t, 1, actualUsers.Len())

actualUser := must.NotFail(actualUsers.Get(0)).(*types.Document)
user, err := actualUser.Get("user")
require.NoError(t, err)
require.Equal(t, username, user)

// the test user logs out
err = db.RunCommand(ctx, bson.D{{"logout", 1}}).Decode(&res)
assert.NoError(t, err)
assert.Equal(t, bson.D{{"ok", float64(1)}}, res)

// the test user is no longer authenticated
expectedUnauthenticated := bson.D{
{
"authInfo", bson.D{
{"authenticatedUsers", bson.A{}},
{"authenticatedUserRoles", bson.A{}},
{"authenticatedUserPrivileges", bson.A{}},
},
},
{"ok", float64(1)},
}
require.NoError(t, err)

actual := ConvertDocument(t, res)
actual.Remove("$clusterTime")
actual.Remove("operationTime")

expected := ConvertDocument(t, bson.D{{"ok", float64(1)}})
testutil.AssertEqual(t, expected, actual)

err = db.RunCommand(ctx, bson.D{{"connectionStatus", 1}}).Decode(&res)
assert.NoError(t, err)
assert.Equal(t, expectedUnauthenticated, res)
require.NoError(t, err)

actualAuth, err = ConvertDocument(t, res).Get("authInfo")
require.NoError(t, err)

actualUsersV, err = actualAuth.(*types.Document).Get("authenticatedUsers")
require.NoError(t, err)
require.Empty(t, actualUsersV)
}
2 changes: 1 addition & 1 deletion integration/commands_diagnostic_test.go
Expand Up @@ -403,7 +403,7 @@ func TestCommandsDiagnosticWhatsMyURI(t *testing.T) {
databaseName := s.Collection.Database().Name()
collectionName := s.Collection.Name()

// only check port number on TCP connection, no need to check on Unix socket
// only check port number on TCP connection, no need to check on Unix domain socket
isUnix := s.IsUnixSocket(t)

// setup second client connection to check that `whatsmyuri` returns different ports
Expand Down
7 changes: 5 additions & 2 deletions integration/hello_command_test.go
Expand Up @@ -61,8 +61,11 @@ func TestHello(t *testing.T) {
func TestHelloWithSupportedMechs(t *testing.T) {
t.Parallel()

ctx, collection := setup.Setup(t, shareddata.Scalars, shareddata.Composites)
db := collection.Database()
s := setup.SetupWithOpts(t, &setup.SetupOpts{
SetupUser: true,
Providers: []shareddata.Provider{shareddata.Scalars, shareddata.Composites},
})
ctx, db := s.Ctx, s.Collection.Database()

usersPayload := []bson.D{
{
Expand Down
18 changes: 10 additions & 8 deletions integration/setup/setup.go
Expand Up @@ -45,7 +45,7 @@ var (

targetProxyAddrF = flag.String("target-proxy-addr", "", "in-process FerretDB: use given proxy")
targetTLSF = flag.Bool("target-tls", false, "in-process FerretDB: use TLS")
targetUnixSocketF = flag.Bool("target-unix-socket", false, "in-process FerretDB: use Unix socket")
targetUnixSocketF = flag.Bool("target-unix-socket", false, "in-process FerretDB: use Unix domain socket")

postgreSQLURLF = flag.String("postgresql-url", "", "in-process FerretDB: PostgreSQL URL for 'postgresql' handler.")
sqliteURLF = flag.String("sqlite-url", "", "in-process FerretDB: SQLite URI for 'sqlite' handler.")
Expand Down Expand Up @@ -88,6 +88,9 @@ type SetupOpts struct {

// ExtraOptions sets the options in MongoDB URI, when the option exists it overwrites that option.
ExtraOptions url.Values

// SetupUser true creates a user.
SetupUser bool
}

// SetupResult represents setup results.
Expand All @@ -97,12 +100,12 @@ type SetupResult struct {
MongoDBURI string
}

// IsUnixSocket returns true if MongoDB URI is a Unix socket.
// IsUnixSocket returns true if MongoDB URI is a Unix domain socket.
func (s *SetupResult) IsUnixSocket(tb testtb.TB) bool {
tb.Helper()

// we can't use a regular url.Parse because
// MongoDB really wants Unix socket path in the host part of the URI
// MongoDB really wants Unix domain socket path in the host part of the URI
opts := options.Client().ApplyURI(s.MongoDBURI)
res := slices.ContainsFunc(opts.Hosts, func(host string) bool {
return strings.Contains(host, "/")
Expand Down Expand Up @@ -163,7 +166,9 @@ func SetupWithOpts(tb testtb.TB, opts *SetupOpts) *SetupResult {

collection := setupCollection(tb, setupCtx, client, opts)

setupUser(tb, setupCtx, client)
if opts.SetupUser {
setupUser(tb, ctx, client)
}

level.SetLevel(*logLevelF)

Expand Down Expand Up @@ -326,11 +331,8 @@ func insertBenchmarkProvider(tb testtb.TB, ctx context.Context, collection *mong
}

// setupUser creates a user in admin database with supported mechanisms.
// The user uses username/password credential which is the same as the database
// credentials. This is done to avoid the need to reconnect as different credential.
//
// Without this, once the first user is created, the authentication fails
// as username/password does not exist in admin.system.users collection.
// Without this, once the first user is created, the authentication fails as local exception no longer applies.
func setupUser(tb testtb.TB, ctx context.Context, client *mongo.Client) {
tb.Helper()

Expand Down

0 comments on commit 75f5a74

Please sign in to comment.