package authenticate import ( "context" "errors" "strings" "testing" structpb "github.com/golang/protobuf/ptypes/struct" "github.com/hashicorp/boundary/internal/auth" "github.com/hashicorp/boundary/internal/auth/password" "github.com/hashicorp/boundary/internal/authtoken" "github.com/hashicorp/boundary/internal/db" pbs "github.com/hashicorp/boundary/internal/gen/controller/api/services" "github.com/hashicorp/boundary/internal/iam" "github.com/hashicorp/boundary/internal/kms" "github.com/hashicorp/boundary/internal/servers/controller/handlers" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "google.golang.org/grpc/codes" ) const ( testPassword = "thetestpassword" testLoginName = "default" ) func TestAuthenticate(t *testing.T) { conn, _ := db.TestSetup(t, "postgres") rw := db.New(conn) wrapper := db.TestWrapper(t) kms := kms.TestKms(t, conn, wrapper) o, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) authTokenRepoFn := func() (*authtoken.Repository, error) { return authtoken.NewRepository(rw, rw, kms) } iamRepoFn := func() (*iam.Repository, error) { return iam.NewRepository(rw, rw, kms) } passwordRepoFn := func() (*password.Repository, error) { return password.NewRepository(rw, rw, kms) } am := password.TestAuthMethods(t, conn, o.GetPublicId(), 1)[0] acct, err := password.NewAccount(am.GetPublicId(), password.WithLoginName(testLoginName)) require.NoError(t, err) pwRepo, err := passwordRepoFn() require.NoError(t, err) acct, err = pwRepo.CreateAccount(context.Background(), o.GetPublicId(), acct, password.WithPassword(testPassword)) require.NoError(t, err) require.NotNil(t, acct) cases := []struct { name string request *pbs.AuthenticateRequest wantType string wantErr error }{ { name: "basic", request: &pbs.AuthenticateRequest{ AuthMethodId: am.GetPublicId(), TokenType: "token", Credentials: func() *structpb.Struct { creds := map[string]*structpb.Value{ "login_name": {Kind: &structpb.Value_StringValue{StringValue: testLoginName}}, "password": {Kind: &structpb.Value_StringValue{StringValue: testPassword}}, } return &structpb.Struct{Fields: creds} }(), }, wantType: "token", }, { name: "cookie-type", request: &pbs.AuthenticateRequest{ AuthMethodId: am.GetPublicId(), TokenType: "cookie", Credentials: func() *structpb.Struct { creds := map[string]*structpb.Value{ "login_name": {Kind: &structpb.Value_StringValue{StringValue: testLoginName}}, "password": {Kind: &structpb.Value_StringValue{StringValue: testPassword}}, } return &structpb.Struct{Fields: creds} }(), }, wantType: "cookie", }, { name: "no-token-type", request: &pbs.AuthenticateRequest{ AuthMethodId: am.GetPublicId(), Credentials: func() *structpb.Struct { creds := map[string]*structpb.Value{ "login_name": {Kind: &structpb.Value_StringValue{StringValue: testLoginName}}, "password": {Kind: &structpb.Value_StringValue{StringValue: testPassword}}, } return &structpb.Struct{Fields: creds} }(), }, }, { name: "bad-token-type", request: &pbs.AuthenticateRequest{ AuthMethodId: am.GetPublicId(), TokenType: "email", Credentials: func() *structpb.Struct { creds := map[string]*structpb.Value{ "login_name": {Kind: &structpb.Value_StringValue{StringValue: testLoginName}}, "password": {Kind: &structpb.Value_StringValue{StringValue: testPassword}}, } return &structpb.Struct{Fields: creds} }(), }, wantErr: handlers.ApiErrorWithCode(codes.InvalidArgument), }, { name: "no-authmethod", request: &pbs.AuthenticateRequest{ Credentials: func() *structpb.Struct { creds := map[string]*structpb.Value{ "login_name": {Kind: &structpb.Value_StringValue{StringValue: testLoginName}}, "password": {Kind: &structpb.Value_StringValue{StringValue: testPassword}}, } return &structpb.Struct{Fields: creds} }(), }, wantErr: handlers.ApiErrorWithCode(codes.InvalidArgument), }, { name: "wrong-password", request: &pbs.AuthenticateRequest{ AuthMethodId: am.GetPublicId(), TokenType: "token", Credentials: func() *structpb.Struct { creds := map[string]*structpb.Value{ "login_name": {Kind: &structpb.Value_StringValue{StringValue: testLoginName}}, "password": {Kind: &structpb.Value_StringValue{StringValue: "wrong"}}, } return &structpb.Struct{Fields: creds} }(), }, wantErr: handlers.ApiErrorWithCode(codes.Unauthenticated), }, { name: "wrong-login-name", request: &pbs.AuthenticateRequest{ AuthMethodId: am.GetPublicId(), TokenType: "token", Credentials: func() *structpb.Struct { creds := map[string]*structpb.Value{ "login_name": {Kind: &structpb.Value_StringValue{StringValue: "wrong"}}, "password": {Kind: &structpb.Value_StringValue{StringValue: testPassword}}, } return &structpb.Struct{Fields: creds} }(), }, wantErr: handlers.ApiErrorWithCode(codes.Unauthenticated), }, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { assert, require := assert.New(t), require.New(t) s, err := NewService(kms, passwordRepoFn, iamRepoFn, authTokenRepoFn) require.NoError(err) resp, err := s.Authenticate(auth.DisabledAuthTestContext(auth.WithScopeId(o.GetPublicId())), tc.request) if tc.wantErr != nil { assert.Error(err) assert.Truef(errors.Is(err, tc.wantErr), "Got %#v, wanted %#v", err, tc.wantErr) return } require.NoError(err) aToken := resp.GetItem() assert.NotEmpty(aToken.GetId()) assert.NotEmpty(aToken.GetToken()) assert.True(strings.HasPrefix(aToken.GetToken(), aToken.GetId())) assert.Equal(am.GetPublicId(), aToken.GetAuthMethodId()) assert.Equal(aToken.GetCreatedTime(), aToken.GetUpdatedTime()) assert.Equal(aToken.GetCreatedTime(), aToken.GetApproximateLastUsedTime()) assert.Equal(acct.GetPublicId(), aToken.GetAccountId()) assert.Equal(am.GetPublicId(), aToken.GetAuthMethodId()) assert.Equal(tc.wantType, resp.GetTokenType()) }) } } func TestAuthenticate_AuthAccountConnectedToIamUser(t *testing.T) { assert, require := assert.New(t), require.New(t) conn, _ := db.TestSetup(t, "postgres") rw := db.New(conn) wrapper := db.TestWrapper(t) kms := kms.TestKms(t, conn, wrapper) o, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) passwordRepoFn := func() (*password.Repository, error) { return password.NewRepository(rw, rw, kms) } authTokenRepoFn := func() (*authtoken.Repository, error) { return authtoken.NewRepository(rw, rw, kms) } iamRepoFn := func() (*iam.Repository, error) { return iam.NewRepository(rw, rw, kms) } am := password.TestAuthMethods(t, conn, o.GetPublicId(), 1)[0] acct, err := password.NewAccount(am.GetPublicId(), password.WithLoginName(testLoginName)) require.NoError(err) pwRepo, err := passwordRepoFn() require.NoError(err) acct, err = pwRepo.CreateAccount(context.Background(), o.GetPublicId(), acct, password.WithPassword(testPassword)) require.NoError(err) // connected to an account. iamRepo, err := iamRepoFn() require.NoError(err) iamUser, err := iamRepo.LookupUserWithLogin(context.Background(), acct.GetPublicId(), iam.WithAutoVivify(true)) require.NoError(err) s, err := NewService(kms, passwordRepoFn, iamRepoFn, authTokenRepoFn) require.NoError(err) resp, err := s.Authenticate(auth.DisabledAuthTestContext(auth.WithScopeId(o.GetPublicId())), &pbs.AuthenticateRequest{ AuthMethodId: am.GetPublicId(), Credentials: func() *structpb.Struct { creds := map[string]*structpb.Value{ "login_name": {Kind: &structpb.Value_StringValue{StringValue: testLoginName}}, "password": {Kind: &structpb.Value_StringValue{StringValue: testPassword}}, } return &structpb.Struct{Fields: creds} }(), }) require.NoError(err) aToken := resp.GetItem() assert.Equal(iamUser.GetPublicId(), aToken.GetUserId()) assert.Equal(am.GetPublicId(), aToken.GetAuthMethodId()) assert.Equal(acct.GetPublicId(), aToken.GetAccountId()) assert.NotEmpty(aToken.GetId()) assert.NotEmpty(aToken.GetToken()) assert.True(strings.HasPrefix(aToken.GetToken(), aToken.GetId())) }