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

Implement dropUser command #3866

Merged
merged 19 commits into from
Dec 20, 2023
Prev Previous commit
Next Next commit
wip
  • Loading branch information
henvic committed Dec 14, 2023
commit 0b7235e3c01b387a5f371a285130c19e542f8327
167 changes: 145 additions & 22 deletions integration/users/create_user_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,50 +15,173 @@
package users

import (
"context"
"testing"

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

"github.com/FerretDB/FerretDB/integration"
"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"
"github.com/stretchr/testify/assert"
)

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

ctx, collection := setup.Setup(t)
// https://www.mongodb.com/docs/manual/reference/command/createUser/

_ = collection.Database().RunCommand(ctx, bson.D{
{"dropAllUsersFromDatabase", 1},
})

var res bson.D
err := collection.Database().RunCommand(ctx, bson.D{
{"createUser", "testuser"},
{"roles", bson.A{}},
{"pwd", "password"},
}).Decode(&res)

require.NoError(t, err)

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

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

testutil.AssertEqual(t, expected, actual)
db := collection.Database()

err := db.RunCommand(ctx, bson.D{
{Key: "createUser", Value: "should_already_exist"},
{Key: "roles", Value: bson.A{}},
{Key: "pwd", Value: "password"},
}).Err()
assert.NoError(t, err)

testCases := map[string]struct {
payload bson.D
err *mongo.CommandError
altMessage string
expected bson.D
skip string
}{
"MissingRoles": {
payload: bson.D{
{Key: "createUser", Value: "missing_roles"},
},
err: &mongo.CommandError{
Code: 40414,
Name: "Location40414",
Message: "BSON field 'createUser.roles' is missing but a required field",
},
skip: "not implemented yet", // TODO: implement for FerretDB
},
"AlreadyExists": {
payload: bson.D{
{Key: "createUser", Value: "should_already_exist"},
{Key: "roles", Value: bson.A{}},
{Key: "pwd", Value: "password"},
},
err: &mongo.CommandError{
Code: 51003,
Name: "Location51003",
Message: "User \"should_already_exist@TestCreateUser\" already exists",
},
},
"MissingPwdOrExternal": {
payload: bson.D{
{Key: "createUser", Value: "mising_pwd_or_external"},
{Key: "roles", Value: bson.A{}},
},
err: &mongo.CommandError{
Code: 2,
Name: "BadValue",
Message: "Must provide a 'pwd' field for all user documents, except those with '$external' as the user's source db",
},
},
"Success": {
payload: bson.D{
{Key: "createUser", Value: "success_user"},
{Key: "roles", Value: bson.A{}},
{Key: "pwd", Value: "password"},
},
expected: bson.D{
{
Key: "ok", Value: float64(1),
},
},
},
"WithComment": {
payload: bson.D{
{Key: "createUser", Value: "with_comment_user"},
{Key: "roles", Value: bson.A{}},
{Key: "pwd", Value: "password"},
{Key: "comment", Value: "test string comment"},
},
expected: bson.D{
{
Key: "ok", Value: float64(1),
},
},
},
"WithCommentComposite": {
payload: bson.D{
{Key: "createUser", Value: "with_comment_composite"},
{Key: "roles", Value: bson.A{}},
{Key: "pwd", Value: "password"},
{
Key: "comment",
Value: bson.D{
{Key: "example", Value: "blah"},
{
Key: "complex",
Value: bson.A{
bson.D{{Key: "x", Value: "y"}},
},
},
},
},
},
expected: bson.D{
{
Key: "ok", Value: float64(1),
},
},
},
}
for name, tc := range testCases {
tc := tc
t.Run(name, func(t *testing.T) {
if tc.skip != "" {
t.Skip(tc.skip)
}
t.Parallel()
var res bson.D
err := db.RunCommand(ctx, tc.payload).Decode(&res)
if tc.err != nil {
integration.AssertEqualAltCommandError(t, *tc.err, tc.altMessage, err)
return
}

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

expected := integration.ConvertDocument(t, tc.expected)
testutil.AssertEqual(t, expected, actual)
assert.NoError(t, err)
payload := integration.ConvertDocument(t, tc.payload)
assertUserExists(ctx, t, db, payload)
})
}
}

func assertUserExists(ctx context.Context, t testing.TB, db *mongo.Database, payload *types.Document) {
t.Helper()
var rec bson.D
err = collection.Database().Collection("system.users").FindOne(ctx, bson.D{{"user", "testuser"}}).Decode(&rec)
require.NoError(t, err)
err := db.Collection("system.users").FindOne(ctx, bson.D{
{Key: "user", Value: must.NotFail(payload.Get("createUser"))},
}).Decode(&rec)
assert.NoError(t, err)

var x any
t.Error(db.ListCollections(ctx, x))
t.Error(rec)
err = db.Collection("system.users").FindOne(ctx, bson.D{}).Decode(&rec)
assert.NoError(t, err)

actualRecorded := integration.ConvertDocument(t, rec)
expectedRec := integration.ConvertDocument(t, bson.D{{"ok", float64(1)}})
expectedRec := integration.ConvertDocument(t, bson.D{{Key: "ok", Value: float64(1)}})

testutil.AssertEqual(t, expectedRec, actualRecorded)

// TODO compare other data
}
43 changes: 35 additions & 8 deletions internal/handler/msg_createuser.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

"github.com/FerretDB/FerretDB/internal/backends"
"github.com/FerretDB/FerretDB/internal/handler/common"
"github.com/FerretDB/FerretDB/internal/handler/handlererrors"
"github.com/FerretDB/FerretDB/internal/types"
"github.com/FerretDB/FerretDB/internal/util/lazyerrors"
"github.com/FerretDB/FerretDB/internal/util/must"
Expand All @@ -34,57 +35,83 @@

// https://www.mongodb.com/docs/manual/reference/command/createUser/

username, err := common.GetRequiredParam[string](document, document.Command())
if err != nil {
return nil, err
}

Check warning on line 41 in internal/handler/msg_createuser.go

View check run for this annotation

Codecov / codecov/patch

internal/handler/msg_createuser.go#L38-L41

Added lines #L38 - L41 were not covered by tests

_, err = common.GetRequiredParam[string](document, "pwd")
_, err = common.GetOptionalParam[string](document, "pwd", "")
if err != nil {
return nil, err
}

Check warning on line 46 in internal/handler/msg_createuser.go

View check run for this annotation

Codecov / codecov/patch

internal/handler/msg_createuser.go#L43-L46

Added lines #L43 - L46 were not covered by tests

if err := common.UnimplementedNonDefault(document, "roles", func(v any) bool {
roles, ok := v.(*types.Array)
return ok && roles.Len() == 0
}); err != nil {
return nil, err
}

Check warning on line 53 in internal/handler/msg_createuser.go

View check run for this annotation

Codecov / codecov/patch

internal/handler/msg_createuser.go#L48-L53

Added lines #L48 - L53 were not covered by tests

common.Ignored(document, h.L, "roles", "writeConcern", "authenticationRestrictions", "mechanisms")

saved := must.NotFail(types.NewDocument(
"user", username,
"roles", must.NotFail(types.NewArray()), // Non-default value is currently ignored.
"pwd", "password", // TODO: hash the password.
))

if document.Has("customData") {
customData, err := common.GetOptionalParam[*types.Document](document, "customData", nil)
if err != nil {
return nil, err
}
saved.Set("customData", customData)

Check warning on line 68 in internal/handler/msg_createuser.go

View check run for this annotation

Codecov / codecov/patch

internal/handler/msg_createuser.go#L55-L68

Added lines #L55 - L68 were not covered by tests
}

if document.Has("digestPassword") {
digestPassword, err := common.GetOptionalParam[bool](document, "digestPassword", true)
if err != nil {
return nil, err
}
saved.Set("digestPassword", digestPassword)

Check warning on line 76 in internal/handler/msg_createuser.go

View check run for this annotation

Codecov / codecov/patch

internal/handler/msg_createuser.go#L71-L76

Added lines #L71 - L76 were not covered by tests
}

if document.Has("comment") {
saved.Set("comment", must.NotFail(document.Get("comment")))
}

Check warning on line 81 in internal/handler/msg_createuser.go

View check run for this annotation

Codecov / codecov/patch

internal/handler/msg_createuser.go#L79-L81

Added lines #L79 - L81 were not covered by tests

dbName, err := common.GetRequiredParam[string](document, "$db")
if err != nil {
return nil, err
}

Check warning on line 86 in internal/handler/msg_createuser.go

View check run for this annotation

Codecov / codecov/patch

internal/handler/msg_createuser.go#L83-L86

Added lines #L83 - L86 were not covered by tests

if dbName != "$external" && !document.Has("pwd") {
return nil, handlererrors.NewCommandErrorMsg(handlererrors.ErrBadValue, "Must provide a 'pwd' field for all user documents, except those with '$external' as the user's source db")
}

Check warning on line 90 in internal/handler/msg_createuser.go

View check run for this annotation

Codecov / codecov/patch

internal/handler/msg_createuser.go#L88-L90

Added lines #L88 - L90 were not covered by tests

db, err := h.b.Database(dbName)
if err != nil {
return nil, lazyerrors.Error(err)
}

Check warning on line 95 in internal/handler/msg_createuser.go

View check run for this annotation

Codecov / codecov/patch

internal/handler/msg_createuser.go#L92-L95

Added lines #L92 - L95 were not covered by tests

collection, err := db.Collection("system.users")
if err != nil {
return nil, lazyerrors.Error(err)
}

Check warning on line 100 in internal/handler/msg_createuser.go

View check run for this annotation

Codecov / codecov/patch

internal/handler/msg_createuser.go#L97-L100

Added lines #L97 - L100 were not covered by tests

_, err = collection.InsertAll(ctx, &backends.InsertAllParams{
Docs: []*types.Document{must.NotFail(types.NewDocument(
"createdUser", username,
"roles", []string{},
"pwd", "password", // TODO: hash the password.
)),
},
Docs: []*types.Document{saved},
})
if err != nil {
return nil, lazyerrors.Error(err)
}

Check warning on line 107 in internal/handler/msg_createuser.go

View check run for this annotation

Codecov / codecov/patch

internal/handler/msg_createuser.go#L102-L107

Added lines #L102 - L107 were not covered by tests

// TODO https://github.com/FerretDB/FerretDB/issues/1491
var reply wire.OpMsg
must.NoError(reply.SetSections(wire.OpMsgSection{
Documents: []*types.Document{must.NotFail(types.NewDocument(
"ok", float64(1),
))},
}))

return &reply, nil

Check warning on line 116 in internal/handler/msg_createuser.go

View check run for this annotation

Codecov / codecov/patch

internal/handler/msg_createuser.go#L109-L116

Added lines #L109 - L116 were not covered by tests
}
Loading