// Copyright 2016 The etcd Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package auth import ( "context" "fmt" "os" "reflect" "strings" "sync" "testing" "time" "go.etcd.io/etcd/auth/authpb" "go.etcd.io/etcd/etcdserver/api/v3rpc/rpctypes" pb "go.etcd.io/etcd/etcdserver/etcdserverpb" "go.etcd.io/etcd/mvcc/backend" "go.uber.org/zap" "golang.org/x/crypto/bcrypt" "google.golang.org/grpc/metadata" ) func dummyIndexWaiter(index uint64) <-chan struct{} { ch := make(chan struct{}) go func() { ch <- struct{}{} }() return ch } // TestNewAuthStoreRevision ensures newly auth store // keeps the old revision when there are no changes. func TestNewAuthStoreRevision(t *testing.T) { b, tPath := backend.NewDefaultTmpBackend() defer os.Remove(tPath) tp, err := NewTokenProvider(zap.NewExample(), tokenTypeSimple, dummyIndexWaiter) if err != nil { t.Fatal(err) } as := NewAuthStore(zap.NewExample(), b, tp, bcrypt.MinCost) err = enableAuthAndCreateRoot(as) if err != nil { t.Fatal(err) } old := as.Revision() as.Close() b.Close() // no changes to commit b2 := backend.NewDefaultBackend(tPath) as = NewAuthStore(zap.NewExample(), b2, tp, bcrypt.MinCost) new := as.Revision() as.Close() b2.Close() if old != new { t.Fatalf("expected revision %d, got %d", old, new) } } // TestNewAuthStoreBryptCost ensures that NewAuthStore uses default when given bcrypt-cost is invalid func TestNewAuthStoreBcryptCost(t *testing.T) { b, tPath := backend.NewDefaultTmpBackend() defer os.Remove(tPath) tp, err := NewTokenProvider(zap.NewExample(), tokenTypeSimple, dummyIndexWaiter) if err != nil { t.Fatal(err) } invalidCosts := [2]int{bcrypt.MinCost - 1, bcrypt.MaxCost + 1} for _, invalidCost := range invalidCosts { as := NewAuthStore(zap.NewExample(), b, tp, invalidCost) if as.BcryptCost() != bcrypt.DefaultCost { t.Fatalf("expected DefaultCost when bcryptcost is invalid") } as.Close() } b.Close() } func setupAuthStore(t *testing.T) (store *authStore, teardownfunc func(t *testing.T)) { b, tPath := backend.NewDefaultTmpBackend() tp, err := NewTokenProvider(zap.NewExample(), tokenTypeSimple, dummyIndexWaiter) if err != nil { t.Fatal(err) } as := NewAuthStore(zap.NewExample(), b, tp, bcrypt.MinCost) err = enableAuthAndCreateRoot(as) if err != nil { t.Fatal(err) } // adds a new role _, err = as.RoleAdd(&pb.AuthRoleAddRequest{Name: "role-test"}) if err != nil { t.Fatal(err) } ua := &pb.AuthUserAddRequest{Name: "foo", Password: "bar", Options: &authpb.UserAddOptions{NoPassword: false}} _, err = as.UserAdd(ua) // add a non-existing user if err != nil { t.Fatal(err) } tearDown := func(_ *testing.T) { b.Close() os.Remove(tPath) as.Close() } return as, tearDown } func enableAuthAndCreateRoot(as *authStore) error { _, err := as.UserAdd(&pb.AuthUserAddRequest{Name: "root", Password: "root", Options: &authpb.UserAddOptions{NoPassword: false}}) if err != nil { return err } _, err = as.RoleAdd(&pb.AuthRoleAddRequest{Name: "root"}) if err != nil { return err } _, err = as.UserGrantRole(&pb.AuthUserGrantRoleRequest{User: "root", Role: "root"}) if err != nil { return err } return as.AuthEnable() } func TestUserAdd(t *testing.T) { as, tearDown := setupAuthStore(t) defer tearDown(t) ua := &pb.AuthUserAddRequest{Name: "foo", Options: &authpb.UserAddOptions{NoPassword: false}} _, err := as.UserAdd(ua) // add an existing user if err == nil { t.Fatalf("expected %v, got %v", ErrUserAlreadyExist, err) } if err != ErrUserAlreadyExist { t.Fatalf("expected %v, got %v", ErrUserAlreadyExist, err) } ua = &pb.AuthUserAddRequest{Name: "", Options: &authpb.UserAddOptions{NoPassword: false}} _, err = as.UserAdd(ua) // add a user with empty name if err != ErrUserEmpty { t.Fatal(err) } } func TestRecover(t *testing.T) { as, tearDown := setupAuthStore(t) defer tearDown(t) as.enabled = false as.Recover(as.be) if !as.IsAuthEnabled() { t.Fatalf("expected auth enabled got disabled") } } func TestCheckPassword(t *testing.T) { as, tearDown := setupAuthStore(t) defer tearDown(t) // auth a non-existing user _, err := as.CheckPassword("foo-test", "bar") if err == nil { t.Fatalf("expected %v, got %v", ErrAuthFailed, err) } if err != ErrAuthFailed { t.Fatalf("expected %v, got %v", ErrAuthFailed, err) } // auth an existing user with correct password _, err = as.CheckPassword("foo", "bar") if err != nil { t.Fatal(err) } // auth an existing user but with wrong password _, err = as.CheckPassword("foo", "") if err == nil { t.Fatalf("expected %v, got %v", ErrAuthFailed, err) } if err != ErrAuthFailed { t.Fatalf("expected %v, got %v", ErrAuthFailed, err) } } func TestUserDelete(t *testing.T) { as, tearDown := setupAuthStore(t) defer tearDown(t) // delete an existing user ud := &pb.AuthUserDeleteRequest{Name: "foo"} _, err := as.UserDelete(ud) if err != nil { t.Fatal(err) } // delete a non-existing user _, err = as.UserDelete(ud) if err == nil { t.Fatalf("expected %v, got %v", ErrUserNotFound, err) } if err != ErrUserNotFound { t.Fatalf("expected %v, got %v", ErrUserNotFound, err) } } func TestUserChangePassword(t *testing.T) { as, tearDown := setupAuthStore(t) defer tearDown(t) ctx1 := context.WithValue(context.WithValue(context.TODO(), AuthenticateParamIndex{}, uint64(1)), AuthenticateParamSimpleTokenPrefix{}, "dummy") _, err := as.Authenticate(ctx1, "foo", "bar") if err != nil { t.Fatal(err) } _, err = as.UserChangePassword(&pb.AuthUserChangePasswordRequest{Name: "foo", Password: "baz"}) if err != nil { t.Fatal(err) } ctx2 := context.WithValue(context.WithValue(context.TODO(), AuthenticateParamIndex{}, uint64(2)), AuthenticateParamSimpleTokenPrefix{}, "dummy") _, err = as.Authenticate(ctx2, "foo", "baz") if err != nil { t.Fatal(err) } // change a non-existing user _, err = as.UserChangePassword(&pb.AuthUserChangePasswordRequest{Name: "foo-test", Password: "bar"}) if err == nil { t.Fatalf("expected %v, got %v", ErrUserNotFound, err) } if err != ErrUserNotFound { t.Fatalf("expected %v, got %v", ErrUserNotFound, err) } } func TestRoleAdd(t *testing.T) { as, tearDown := setupAuthStore(t) defer tearDown(t) // adds a new role _, err := as.RoleAdd(&pb.AuthRoleAddRequest{Name: "role-test-1"}) if err != nil { t.Fatal(err) } // add a role with empty name _, err = as.RoleAdd(&pb.AuthRoleAddRequest{Name: ""}) if err != ErrRoleEmpty { t.Fatal(err) } } func TestUserGrant(t *testing.T) { as, tearDown := setupAuthStore(t) defer tearDown(t) // grants a role to the user _, err := as.UserGrantRole(&pb.AuthUserGrantRoleRequest{User: "foo", Role: "role-test"}) if err != nil { t.Fatal(err) } // grants a role to a non-existing user _, err = as.UserGrantRole(&pb.AuthUserGrantRoleRequest{User: "foo-test", Role: "role-test"}) if err == nil { t.Errorf("expected %v, got %v", ErrUserNotFound, err) } if err != ErrUserNotFound { t.Errorf("expected %v, got %v", ErrUserNotFound, err) } } func TestHasRole(t *testing.T) { as, tearDown := setupAuthStore(t) defer tearDown(t) // grants a role to the user _, err := as.UserGrantRole(&pb.AuthUserGrantRoleRequest{User: "foo", Role: "role-test"}) if err != nil { t.Fatal(err) } // checks role reflects correctly hr := as.HasRole("foo", "role-test") if !hr { t.Fatal("expected role granted, got false") } // checks non existent role hr = as.HasRole("foo", "non-existent-role") if hr { t.Fatal("expected role not found, got true") } // checks non existent user hr = as.HasRole("nouser", "role-test") if hr { t.Fatal("expected user not found got true") } } func TestIsOpPermitted(t *testing.T) { as, tearDown := setupAuthStore(t) defer tearDown(t) // add new role _, err := as.RoleAdd(&pb.AuthRoleAddRequest{Name: "role-test-1"}) if err != nil { t.Fatal(err) } perm := &authpb.Permission{ PermType: authpb.WRITE, Key: []byte("Keys"), RangeEnd: []byte("RangeEnd"), } _, err = as.RoleGrantPermission(&pb.AuthRoleGrantPermissionRequest{ Name: "role-test-1", Perm: perm, }) if err != nil { t.Fatal(err) } // grants a role to the user _, err = as.UserGrantRole(&pb.AuthUserGrantRoleRequest{User: "foo", Role: "role-test-1"}) if err != nil { t.Fatal(err) } // check permission reflected to user err = as.isOpPermitted("foo", as.Revision(), perm.Key, perm.RangeEnd, perm.PermType) if err != nil { t.Fatal(err) } } func TestGetUser(t *testing.T) { as, tearDown := setupAuthStore(t) defer tearDown(t) _, err := as.UserGrantRole(&pb.AuthUserGrantRoleRequest{User: "foo", Role: "role-test"}) if err != nil { t.Fatal(err) } u, err := as.UserGet(&pb.AuthUserGetRequest{Name: "foo"}) if err != nil { t.Fatal(err) } if u == nil { t.Fatal("expect user not nil, got nil") } expected := []string{"role-test"} if !reflect.DeepEqual(expected, u.Roles) { t.Errorf("expected %v, got %v", expected, u.Roles) } // check non existent user _, err = as.UserGet(&pb.AuthUserGetRequest{Name: "nouser"}) if err == nil { t.Errorf("expected %v, got %v", ErrUserNotFound, err) } } func TestListUsers(t *testing.T) { as, tearDown := setupAuthStore(t) defer tearDown(t) ua := &pb.AuthUserAddRequest{Name: "user1", Password: "pwd1", Options: &authpb.UserAddOptions{NoPassword: false}} _, err := as.UserAdd(ua) // add a non-existing user if err != nil { t.Fatal(err) } ul, err := as.UserList(&pb.AuthUserListRequest{}) if err != nil { t.Fatal(err) } if !contains(ul.Users, "root") { t.Errorf("expected %v in %v", "root", ul.Users) } if !contains(ul.Users, "user1") { t.Errorf("expected %v in %v", "user1", ul.Users) } } func TestRoleGrantPermission(t *testing.T) { as, tearDown := setupAuthStore(t) defer tearDown(t) _, err := as.RoleAdd(&pb.AuthRoleAddRequest{Name: "role-test-1"}) if err != nil { t.Fatal(err) } perm := &authpb.Permission{ PermType: authpb.WRITE, Key: []byte("Keys"), RangeEnd: []byte("RangeEnd"), } _, err = as.RoleGrantPermission(&pb.AuthRoleGrantPermissionRequest{ Name: "role-test-1", Perm: perm, }) if err != nil { t.Error(err) } r, err := as.RoleGet(&pb.AuthRoleGetRequest{Role: "role-test-1"}) if err != nil { t.Fatal(err) } if !reflect.DeepEqual(perm, r.Perm[0]) { t.Errorf("expected %v, got %v", perm, r.Perm[0]) } } func TestRoleRevokePermission(t *testing.T) { as, tearDown := setupAuthStore(t) defer tearDown(t) _, err := as.RoleAdd(&pb.AuthRoleAddRequest{Name: "role-test-1"}) if err != nil { t.Fatal(err) } perm := &authpb.Permission{ PermType: authpb.WRITE, Key: []byte("Keys"), RangeEnd: []byte("RangeEnd"), } _, err = as.RoleGrantPermission(&pb.AuthRoleGrantPermissionRequest{ Name: "role-test-1", Perm: perm, }) if err != nil { t.Fatal(err) } _, err = as.RoleGet(&pb.AuthRoleGetRequest{Role: "role-test-1"}) if err != nil { t.Fatal(err) } _, err = as.RoleRevokePermission(&pb.AuthRoleRevokePermissionRequest{ Role: "role-test-1", Key: []byte("Keys"), RangeEnd: []byte("RangeEnd"), }) if err != nil { t.Fatal(err) } var r *pb.AuthRoleGetResponse r, err = as.RoleGet(&pb.AuthRoleGetRequest{Role: "role-test-1"}) if err != nil { t.Fatal(err) } if len(r.Perm) != 0 { t.Errorf("expected %v, got %v", 0, len(r.Perm)) } } func TestUserRevokePermission(t *testing.T) { as, tearDown := setupAuthStore(t) defer tearDown(t) _, err := as.RoleAdd(&pb.AuthRoleAddRequest{Name: "role-test-1"}) if err != nil { t.Fatal(err) } _, err = as.UserGrantRole(&pb.AuthUserGrantRoleRequest{User: "foo", Role: "role-test"}) if err != nil { t.Fatal(err) } _, err = as.UserGrantRole(&pb.AuthUserGrantRoleRequest{User: "foo", Role: "role-test-1"}) if err != nil { t.Fatal(err) } u, err := as.UserGet(&pb.AuthUserGetRequest{Name: "foo"}) if err != nil { t.Fatal(err) } expected := []string{"role-test", "role-test-1"} if !reflect.DeepEqual(expected, u.Roles) { t.Fatalf("expected %v, got %v", expected, u.Roles) } _, err = as.UserRevokeRole(&pb.AuthUserRevokeRoleRequest{Name: "foo", Role: "role-test-1"}) if err != nil { t.Fatal(err) } u, err = as.UserGet(&pb.AuthUserGetRequest{Name: "foo"}) if err != nil { t.Fatal(err) } expected = []string{"role-test"} if !reflect.DeepEqual(expected, u.Roles) { t.Errorf("expected %v, got %v", expected, u.Roles) } } func TestRoleDelete(t *testing.T) { as, tearDown := setupAuthStore(t) defer tearDown(t) _, err := as.RoleDelete(&pb.AuthRoleDeleteRequest{Role: "role-test"}) if err != nil { t.Fatal(err) } rl, err := as.RoleList(&pb.AuthRoleListRequest{}) if err != nil { t.Fatal(err) } expected := []string{"root"} if !reflect.DeepEqual(expected, rl.Roles) { t.Errorf("expected %v, got %v", expected, rl.Roles) } } func TestAuthInfoFromCtx(t *testing.T) { as, tearDown := setupAuthStore(t) defer tearDown(t) ctx := context.Background() ai, err := as.AuthInfoFromCtx(ctx) if err != nil && ai != nil { t.Errorf("expected (nil, nil), got (%v, %v)", ai, err) } // as if it came from RPC ctx = metadata.NewIncomingContext(context.Background(), metadata.New(map[string]string{"tokens": "dummy"})) ai, err = as.AuthInfoFromCtx(ctx) if err != nil && ai != nil { t.Errorf("expected (nil, nil), got (%v, %v)", ai, err) } ctx = context.WithValue(context.WithValue(context.TODO(), AuthenticateParamIndex{}, uint64(1)), AuthenticateParamSimpleTokenPrefix{}, "dummy") resp, err := as.Authenticate(ctx, "foo", "bar") if err != nil { t.Error(err) } ctx = metadata.NewIncomingContext(context.Background(), metadata.New(map[string]string{rpctypes.TokenFieldNameGRPC: "Invalid Token"})) _, err = as.AuthInfoFromCtx(ctx) if err != ErrInvalidAuthToken { t.Errorf("expected %v, got %v", ErrInvalidAuthToken, err) } ctx = metadata.NewIncomingContext(context.Background(), metadata.New(map[string]string{rpctypes.TokenFieldNameGRPC: "Invalid.Token"})) _, err = as.AuthInfoFromCtx(ctx) if err != ErrInvalidAuthToken { t.Errorf("expected %v, got %v", ErrInvalidAuthToken, err) } ctx = metadata.NewIncomingContext(context.Background(), metadata.New(map[string]string{rpctypes.TokenFieldNameGRPC: resp.Token})) ai, err = as.AuthInfoFromCtx(ctx) if err != nil { t.Error(err) } if ai.Username != "foo" { t.Errorf("expected %v, got %v", "foo", ai.Username) } } func TestAuthDisable(t *testing.T) { as, tearDown := setupAuthStore(t) defer tearDown(t) as.AuthDisable() ctx := context.WithValue(context.WithValue(context.TODO(), AuthenticateParamIndex{}, uint64(2)), AuthenticateParamSimpleTokenPrefix{}, "dummy") _, err := as.Authenticate(ctx, "foo", "bar") if err != ErrAuthNotEnabled { t.Errorf("expected %v, got %v", ErrAuthNotEnabled, err) } // Disabling disabled auth to make sure it can return safely if store is already disabled. as.AuthDisable() _, err = as.Authenticate(ctx, "foo", "bar") if err != ErrAuthNotEnabled { t.Errorf("expected %v, got %v", ErrAuthNotEnabled, err) } } // TestAuthRevisionRace ensures that access to authStore.revision is thread-safe. func TestAuthInfoFromCtxRace(t *testing.T) { b, tPath := backend.NewDefaultTmpBackend() defer os.Remove(tPath) tp, err := NewTokenProvider(zap.NewExample(), tokenTypeSimple, dummyIndexWaiter) if err != nil { t.Fatal(err) } as := NewAuthStore(zap.NewExample(), b, tp, bcrypt.MinCost) defer as.Close() donec := make(chan struct{}) go func() { defer close(donec) ctx := metadata.NewIncomingContext(context.Background(), metadata.New(map[string]string{rpctypes.TokenFieldNameGRPC: "test"})) as.AuthInfoFromCtx(ctx) }() as.UserAdd(&pb.AuthUserAddRequest{Name: "test", Options: &authpb.UserAddOptions{NoPassword: false}}) <-donec } func TestIsAdminPermitted(t *testing.T) { as, tearDown := setupAuthStore(t) defer tearDown(t) err := as.IsAdminPermitted(&AuthInfo{Username: "root", Revision: 1}) if err != nil { t.Errorf("expected nil, got %v", err) } // invalid user err = as.IsAdminPermitted(&AuthInfo{Username: "rooti", Revision: 1}) if err != ErrUserNotFound { t.Errorf("expected %v, got %v", ErrUserNotFound, err) } // non-admin user err = as.IsAdminPermitted(&AuthInfo{Username: "foo", Revision: 1}) if err != ErrPermissionDenied { t.Errorf("expected %v, got %v", ErrPermissionDenied, err) } // disabled auth should return nil as.AuthDisable() err = as.IsAdminPermitted(&AuthInfo{Username: "root", Revision: 1}) if err != nil { t.Errorf("expected nil, got %v", err) } } func TestRecoverFromSnapshot(t *testing.T) { as, _ := setupAuthStore(t) ua := &pb.AuthUserAddRequest{Name: "foo", Options: &authpb.UserAddOptions{NoPassword: false}} _, err := as.UserAdd(ua) // add an existing user if err == nil { t.Fatalf("expected %v, got %v", ErrUserAlreadyExist, err) } if err != ErrUserAlreadyExist { t.Fatalf("expected %v, got %v", ErrUserAlreadyExist, err) } ua = &pb.AuthUserAddRequest{Name: "", Options: &authpb.UserAddOptions{NoPassword: false}} _, err = as.UserAdd(ua) // add a user with empty name if err != ErrUserEmpty { t.Fatal(err) } as.Close() tp, err := NewTokenProvider(zap.NewExample(), tokenTypeSimple, dummyIndexWaiter) if err != nil { t.Fatal(err) } as2 := NewAuthStore(zap.NewExample(), as.be, tp, bcrypt.MinCost) defer func(a *authStore) { a.Close() }(as2) if !as2.IsAuthEnabled() { t.Fatal("recovering authStore from existing backend failed") } ul, err := as.UserList(&pb.AuthUserListRequest{}) if err != nil { t.Fatal(err) } if !contains(ul.Users, "root") { t.Errorf("expected %v in %v", "root", ul.Users) } } func contains(array []string, str string) bool { for _, s := range array { if s == str { return true } } return false } func TestHammerSimpleAuthenticate(t *testing.T) { // set TTL values low to try to trigger races oldTTL, oldTTLRes := simpleTokenTTL, simpleTokenTTLResolution defer func() { simpleTokenTTL = oldTTL simpleTokenTTLResolution = oldTTLRes }() simpleTokenTTL = 10 * time.Millisecond simpleTokenTTLResolution = simpleTokenTTL users := make(map[string]struct{}) as, tearDown := setupAuthStore(t) defer tearDown(t) // create lots of users for i := 0; i < 50; i++ { u := fmt.Sprintf("user-%d", i) ua := &pb.AuthUserAddRequest{Name: u, Password: "123", Options: &authpb.UserAddOptions{NoPassword: false}} if _, err := as.UserAdd(ua); err != nil { t.Fatal(err) } users[u] = struct{}{} } // hammer on authenticate with lots of users for i := 0; i < 10; i++ { var wg sync.WaitGroup wg.Add(len(users)) for u := range users { go func(user string) { defer wg.Done() token := fmt.Sprintf("%s(%d)", user, i) ctx := context.WithValue(context.WithValue(context.TODO(), AuthenticateParamIndex{}, uint64(1)), AuthenticateParamSimpleTokenPrefix{}, token) if _, err := as.Authenticate(ctx, user, "123"); err != nil { t.Error(err) } if _, err := as.AuthInfoFromCtx(ctx); err != nil { t.Error(err) } }(u) } time.Sleep(time.Millisecond) wg.Wait() } } // TestRolesOrder tests authpb.User.Roles is sorted func TestRolesOrder(t *testing.T) { b, tPath := backend.NewDefaultTmpBackend() defer os.Remove(tPath) tp, err := NewTokenProvider(zap.NewExample(), tokenTypeSimple, dummyIndexWaiter) if err != nil { t.Fatal(err) } as := NewAuthStore(zap.NewExample(), b, tp, bcrypt.MinCost) err = enableAuthAndCreateRoot(as) if err != nil { t.Fatal(err) } username := "user" _, err = as.UserAdd(&pb.AuthUserAddRequest{Name: username, Password: "pass", Options: &authpb.UserAddOptions{NoPassword: false}}) if err != nil { t.Fatal(err) } roles := []string{"role1", "role2", "abc", "xyz", "role3"} for _, role := range roles { _, err = as.RoleAdd(&pb.AuthRoleAddRequest{Name: role}) if err != nil { t.Fatal(err) } _, err = as.UserGrantRole(&pb.AuthUserGrantRoleRequest{User: username, Role: role}) if err != nil { t.Fatal(err) } } user, err := as.UserGet(&pb.AuthUserGetRequest{Name: username}) if err != nil { t.Fatal(err) } for i := 1; i < len(user.Roles); i++ { if strings.Compare(user.Roles[i-1], user.Roles[i]) != -1 { t.Errorf("User.Roles isn't sorted (%s vs %s)", user.Roles[i-1], user.Roles[i]) } } } func TestAuthInfoFromCtxWithRootSimple(t *testing.T) { testAuthInfoFromCtxWithRoot(t, tokenTypeSimple) } func TestAuthInfoFromCtxWithRootJWT(t *testing.T) { opts := testJWTOpts() testAuthInfoFromCtxWithRoot(t, opts) } // testAuthInfoFromCtxWithRoot ensures "WithRoot" properly embeds token in the context. func testAuthInfoFromCtxWithRoot(t *testing.T, opts string) { b, tPath := backend.NewDefaultTmpBackend() defer os.Remove(tPath) tp, err := NewTokenProvider(zap.NewExample(), opts, dummyIndexWaiter) if err != nil { t.Fatal(err) } as := NewAuthStore(zap.NewExample(), b, tp, bcrypt.MinCost) defer as.Close() if err = enableAuthAndCreateRoot(as); err != nil { t.Fatal(err) } ctx := context.Background() ctx = as.WithRoot(ctx) ai, aerr := as.AuthInfoFromCtx(ctx) if aerr != nil { t.Error(err) } if ai == nil { t.Error("expected non-nil *AuthInfo") } if ai.Username != "root" { t.Errorf("expected user name 'root', got %+v", ai) } } func TestUserNoPasswordAdd(t *testing.T) { as, tearDown := setupAuthStore(t) defer tearDown(t) username := "usernopass" ua := &pb.AuthUserAddRequest{Name: username, Options: &authpb.UserAddOptions{NoPassword: true}} _, err := as.UserAdd(ua) if err != nil { t.Fatal(err) } ctx := context.WithValue(context.WithValue(context.TODO(), AuthenticateParamIndex{}, uint64(1)), AuthenticateParamSimpleTokenPrefix{}, "dummy") _, err = as.Authenticate(ctx, username, "") if err != ErrAuthFailed { t.Fatalf("expected %v, got %v", ErrAuthFailed, err) } }