Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support localhost exception #4156

Merged
merged 25 commits into from Mar 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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")
AlekSi marked this conversation as resolved.
Show resolved Hide resolved
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