Files
threshold/internal/servers/controller/handlers/authmethods/password_test.go
T
dd0f34bc35 Add new OIDC auth method. (#1090)
Co-authored-by: Todd Knight <T.Alan.Knight@gmail.com>
Co-authored-by: Jeff Mitchell <jeffrey.mitchell@gmail.com>
Co-authored-by: Michael Gaffney <mgaffney@users.noreply.github.com>
2021-04-08 20:54:32 -04:00

847 lines
28 KiB
Go

package authmethods_test
import (
"context"
"strings"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/hashicorp/boundary/internal/auth"
"github.com/hashicorp/boundary/internal/auth/oidc"
"github.com/hashicorp/boundary/internal/auth/password"
"github.com/hashicorp/boundary/internal/authtoken"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
pb "github.com/hashicorp/boundary/internal/gen/controller/api/resources/authmethods"
authtokenpb "github.com/hashicorp/boundary/internal/gen/controller/api/resources/authtokens"
scopepb "github.com/hashicorp/boundary/internal/gen/controller/api/resources/scopes"
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/hashicorp/boundary/internal/servers/controller/handlers/authmethods"
"github.com/hashicorp/boundary/internal/types/scope"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/genproto/protobuf/field_mask"
"google.golang.org/grpc/codes"
"google.golang.org/protobuf/testing/protocmp"
"google.golang.org/protobuf/types/known/structpb"
"google.golang.org/protobuf/types/known/timestamppb"
"google.golang.org/protobuf/types/known/wrapperspb"
)
func TestUpdate_Password(t *testing.T) {
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
kms := kms.TestKms(t, conn, wrapper)
iamRepoFn := func() (*iam.Repository, error) {
return iam.TestRepo(t, conn, wrapper), nil
}
oidcRepoFn := func() (*oidc.Repository, error) {
return oidc.NewRepository(rw, rw, kms)
}
pwRepoFn := func() (*password.Repository, error) {
return password.NewRepository(rw, rw, kms)
}
atRepoFn := func() (*authtoken.Repository, error) {
return authtoken.NewRepository(rw, rw, kms)
}
iamRepo := iam.TestRepo(t, conn, wrapper)
o, _ := iam.TestScopes(t, iamRepo)
tested, err := authmethods.NewService(kms, pwRepoFn, oidcRepoFn, iamRepoFn, atRepoFn)
require.NoError(t, err, "Error when getting new auth_method service.")
defaultScopeInfo := &scopepb.ScopeInfo{Id: o.GetPublicId(), Type: o.GetType(), ParentScopeId: scope.Global.String()}
freshAuthMethod := func() (*pb.AuthMethod, func()) {
am, err := tested.CreateAuthMethod(auth.DisabledAuthTestContext(iamRepoFn, o.GetPublicId()),
&pbs.CreateAuthMethodRequest{Item: &pb.AuthMethod{
ScopeId: o.GetPublicId(),
Name: wrapperspb.String("default"),
Description: wrapperspb.String("default"),
Type: "password",
}})
require.NoError(t, err)
clean := func() {
_, err := tested.DeleteAuthMethod(auth.DisabledAuthTestContext(iamRepoFn, o.GetPublicId()),
&pbs.DeleteAuthMethodRequest{Id: am.GetItem().GetId()})
require.NoError(t, err)
}
return am.GetItem(), clean
}
cases := []struct {
name string
req *pbs.UpdateAuthMethodRequest
res *pbs.UpdateAuthMethodResponse
err error
}{
{
name: "Update an Existing AuthMethod",
req: &pbs.UpdateAuthMethodRequest{
UpdateMask: &field_mask.FieldMask{
Paths: []string{"name", "description"},
},
Item: &pb.AuthMethod{
Name: &wrapperspb.StringValue{Value: "new"},
Description: &wrapperspb.StringValue{Value: "desc"},
Type: "password",
},
},
res: &pbs.UpdateAuthMethodResponse{
Item: &pb.AuthMethod{
ScopeId: o.GetPublicId(),
Name: &wrapperspb.StringValue{Value: "new"},
Description: &wrapperspb.StringValue{Value: "desc"},
Type: "password",
Attributes: &structpb.Struct{Fields: map[string]*structpb.Value{
"min_password_length": structpb.NewNumberValue(8),
"min_login_name_length": structpb.NewNumberValue(3),
}},
Scope: defaultScopeInfo,
AuthorizedActions: pwAuthorizedActions,
AuthorizedCollectionActions: authorizedCollectionActions,
},
},
},
{
name: "Multiple Paths in single string",
req: &pbs.UpdateAuthMethodRequest{
UpdateMask: &field_mask.FieldMask{
Paths: []string{"name,description,type"},
},
Item: &pb.AuthMethod{
Name: &wrapperspb.StringValue{Value: "new"},
Description: &wrapperspb.StringValue{Value: "desc"},
Type: "password",
},
},
res: &pbs.UpdateAuthMethodResponse{
Item: &pb.AuthMethod{
ScopeId: o.GetPublicId(),
Name: &wrapperspb.StringValue{Value: "new"},
Description: &wrapperspb.StringValue{Value: "desc"},
Type: "password",
Attributes: &structpb.Struct{Fields: map[string]*structpb.Value{
"min_password_length": structpb.NewNumberValue(8),
"min_login_name_length": structpb.NewNumberValue(3),
}},
Scope: defaultScopeInfo,
AuthorizedActions: pwAuthorizedActions,
AuthorizedCollectionActions: authorizedCollectionActions,
},
},
},
{
name: "No Update Mask",
req: &pbs.UpdateAuthMethodRequest{
Item: &pb.AuthMethod{
Name: &wrapperspb.StringValue{Value: "updated name"},
Description: &wrapperspb.StringValue{Value: "updated desc"},
},
},
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
},
{
name: "No Paths in Mask",
req: &pbs.UpdateAuthMethodRequest{
UpdateMask: &field_mask.FieldMask{Paths: []string{}},
Item: &pb.AuthMethod{
Name: &wrapperspb.StringValue{Value: "updated name"},
Description: &wrapperspb.StringValue{Value: "updated desc"},
},
},
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
},
{
name: "Only non-existant paths in Mask",
req: &pbs.UpdateAuthMethodRequest{
UpdateMask: &field_mask.FieldMask{Paths: []string{"nonexistant_field"}},
Item: &pb.AuthMethod{
Name: &wrapperspb.StringValue{Value: "updated name"},
Description: &wrapperspb.StringValue{Value: "updated desc"},
},
},
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
},
{
name: "Cant change type",
req: &pbs.UpdateAuthMethodRequest{
UpdateMask: &field_mask.FieldMask{Paths: []string{"name", "type"}},
Item: &pb.AuthMethod{
Name: &wrapperspb.StringValue{Value: "updated name"},
Type: "oidc",
},
},
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
},
{
name: "Cant change is primary",
req: &pbs.UpdateAuthMethodRequest{
UpdateMask: &field_mask.FieldMask{Paths: []string{"is_primary"}},
Item: &pb.AuthMethod{
IsPrimary: true,
},
},
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
},
{
name: "Unset Name",
req: &pbs.UpdateAuthMethodRequest{
UpdateMask: &field_mask.FieldMask{
Paths: []string{"name"},
},
Item: &pb.AuthMethod{
Description: &wrapperspb.StringValue{Value: "ignored"},
},
},
res: &pbs.UpdateAuthMethodResponse{
Item: &pb.AuthMethod{
ScopeId: o.GetPublicId(),
Description: &wrapperspb.StringValue{Value: "default"},
Type: "password",
Attributes: &structpb.Struct{Fields: map[string]*structpb.Value{
"min_password_length": structpb.NewNumberValue(8),
"min_login_name_length": structpb.NewNumberValue(3),
}},
Scope: defaultScopeInfo,
AuthorizedActions: pwAuthorizedActions,
AuthorizedCollectionActions: authorizedCollectionActions,
},
},
},
{
name: "Update Only Name",
req: &pbs.UpdateAuthMethodRequest{
UpdateMask: &field_mask.FieldMask{
Paths: []string{"name"},
},
Item: &pb.AuthMethod{
Name: &wrapperspb.StringValue{Value: "updated"},
Description: &wrapperspb.StringValue{Value: "ignored"},
},
},
res: &pbs.UpdateAuthMethodResponse{
Item: &pb.AuthMethod{
ScopeId: o.GetPublicId(),
Name: &wrapperspb.StringValue{Value: "updated"},
Description: &wrapperspb.StringValue{Value: "default"},
Type: "password",
Attributes: &structpb.Struct{Fields: map[string]*structpb.Value{
"min_password_length": structpb.NewNumberValue(8),
"min_login_name_length": structpb.NewNumberValue(3),
}},
Scope: defaultScopeInfo,
AuthorizedActions: pwAuthorizedActions,
AuthorizedCollectionActions: authorizedCollectionActions,
},
},
},
{
name: "Update Only Description",
req: &pbs.UpdateAuthMethodRequest{
UpdateMask: &field_mask.FieldMask{
Paths: []string{"description"},
},
Item: &pb.AuthMethod{
Name: &wrapperspb.StringValue{Value: "ignored"},
Description: &wrapperspb.StringValue{Value: "notignored"},
},
},
res: &pbs.UpdateAuthMethodResponse{
Item: &pb.AuthMethod{
ScopeId: o.GetPublicId(),
Name: &wrapperspb.StringValue{Value: "default"},
Description: &wrapperspb.StringValue{Value: "notignored"},
Type: "password",
Attributes: &structpb.Struct{Fields: map[string]*structpb.Value{
"min_password_length": structpb.NewNumberValue(8),
"min_login_name_length": structpb.NewNumberValue(3),
}},
Scope: defaultScopeInfo,
AuthorizedActions: pwAuthorizedActions,
AuthorizedCollectionActions: authorizedCollectionActions,
},
},
},
{
name: "Update a Non Existing AuthMethod",
req: &pbs.UpdateAuthMethodRequest{
Id: password.AuthMethodPrefix + "_DoesntExis",
UpdateMask: &field_mask.FieldMask{
Paths: []string{"description"},
},
Item: &pb.AuthMethod{
Name: &wrapperspb.StringValue{Value: "new"},
Description: &wrapperspb.StringValue{Value: "desc"},
},
},
err: handlers.ApiErrorWithCode(codes.NotFound),
},
{
name: "Cant change Id",
req: &pbs.UpdateAuthMethodRequest{
UpdateMask: &field_mask.FieldMask{
Paths: []string{"id"},
},
Item: &pb.AuthMethod{
Id: password.AuthMethodPrefix + "_somethinge",
Name: &wrapperspb.StringValue{Value: "new"},
Description: &wrapperspb.StringValue{Value: "new desc"},
},
},
res: nil,
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
},
{
name: "Cant specify Created Time",
req: &pbs.UpdateAuthMethodRequest{
UpdateMask: &field_mask.FieldMask{
Paths: []string{"created_time"},
},
Item: &pb.AuthMethod{
CreatedTime: timestamppb.Now(),
},
},
res: nil,
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
},
{
name: "Cant specify Updated Time",
req: &pbs.UpdateAuthMethodRequest{
UpdateMask: &field_mask.FieldMask{
Paths: []string{"updated_time"},
},
Item: &pb.AuthMethod{
UpdatedTime: timestamppb.Now(),
},
},
res: nil,
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
},
{
name: "Cant specify Type",
req: &pbs.UpdateAuthMethodRequest{
UpdateMask: &field_mask.FieldMask{
Paths: []string{"type"},
},
Item: &pb.AuthMethod{
Type: "oidc",
},
},
res: nil,
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
},
{
name: "Update login name length",
req: &pbs.UpdateAuthMethodRequest{
UpdateMask: &field_mask.FieldMask{
Paths: []string{"attributes.min_login_name_length"},
},
Item: &pb.AuthMethod{
Name: &wrapperspb.StringValue{Value: "ignored"},
Description: &wrapperspb.StringValue{Value: "ignored"},
Attributes: &structpb.Struct{Fields: map[string]*structpb.Value{
"min_login_name_length": structpb.NewNumberValue(42),
"min_password_length": structpb.NewNumberValue(55555),
}},
},
},
res: &pbs.UpdateAuthMethodResponse{
Item: &pb.AuthMethod{
ScopeId: o.GetPublicId(),
Name: &wrapperspb.StringValue{Value: "default"},
Description: &wrapperspb.StringValue{Value: "default"},
Type: "password",
Attributes: &structpb.Struct{Fields: map[string]*structpb.Value{
"min_password_length": structpb.NewNumberValue(8),
"min_login_name_length": structpb.NewNumberValue(42),
}},
Scope: defaultScopeInfo,
AuthorizedActions: pwAuthorizedActions,
AuthorizedCollectionActions: authorizedCollectionActions,
},
},
},
{
name: "Update password length",
req: &pbs.UpdateAuthMethodRequest{
UpdateMask: &field_mask.FieldMask{
Paths: []string{"attributes.min_password_length"},
},
Item: &pb.AuthMethod{
Name: &wrapperspb.StringValue{Value: "ignored"},
Description: &wrapperspb.StringValue{Value: "ignored"},
Attributes: &structpb.Struct{Fields: map[string]*structpb.Value{
"min_login_name_length": structpb.NewNumberValue(5555),
"min_password_length": structpb.NewNumberValue(42),
}},
},
},
res: &pbs.UpdateAuthMethodResponse{
Item: &pb.AuthMethod{
ScopeId: o.GetPublicId(),
Name: &wrapperspb.StringValue{Value: "default"},
Description: &wrapperspb.StringValue{Value: "default"},
Type: "password",
Attributes: &structpb.Struct{Fields: map[string]*structpb.Value{
"min_password_length": structpb.NewNumberValue(42),
"min_login_name_length": structpb.NewNumberValue(3),
}},
Scope: defaultScopeInfo,
AuthorizedActions: pwAuthorizedActions,
AuthorizedCollectionActions: authorizedCollectionActions,
},
},
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
am, cleanup := freshAuthMethod()
defer cleanup()
tc.req.Item.Version = 1
if tc.req.GetId() == "" {
tc.req.Id = am.GetId()
}
if tc.res != nil && tc.res.Item != nil {
tc.res.Item.Id = am.GetId()
tc.res.Item.CreatedTime = am.GetCreatedTime()
}
got, gErr := tested.UpdateAuthMethod(auth.DisabledAuthTestContext(iamRepoFn, o.GetPublicId()), tc.req)
if tc.err != nil {
require.Error(gErr)
assert.True(errors.Is(gErr, tc.err), "UpdateAuthMethod(%+v) got error %v, wanted %v", tc.req, gErr, tc.err)
}
if tc.res == nil {
require.Nil(got)
}
if got != nil {
assert.NotNilf(tc.res, "Expected UpdateAuthMethod response to be nil, but was %v", got)
created := am.GetCreatedTime().AsTime()
gotUpdateTime := got.GetItem().GetUpdatedTime().AsTime()
// Verify it is a auth_method updated after it was created
assert.True(gotUpdateTime.After(created), "Updated auth_method should have been updated after it's creation. Was updated %v, which is after %v", gotUpdateTime, created)
// Clear all values which are hard to compare against.
got.Item.UpdatedTime, tc.res.Item.UpdatedTime = nil, nil
assert.EqualValues(2, got.Item.Version)
tc.res.Item.Version = 2
}
assert.Empty(cmp.Diff(got, tc.res, protocmp.Transform()), "UpdateAuthMethod(%q) got response %q, wanted %q", tc.req, got, tc.res)
})
}
}
func TestAuthenticate_Password(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))
iamRepoFn := func() (*iam.Repository, error) {
return iam.TestRepo(t, conn, wrapper), nil
}
oidcRepoFn := func() (*oidc.Repository, error) {
return oidc.NewRepository(rw, rw, kms)
}
pwRepoFn := func() (*password.Repository, error) {
return password.NewRepository(rw, rw, kms)
}
atRepoFn := func() (*authtoken.Repository, error) {
return authtoken.NewRepository(rw, rw, kms)
}
am := password.TestAuthMethods(t, conn, o.GetPublicId(), 1)[0]
iam.TestSetPrimaryAuthMethod(t, iam.TestRepo(t, conn, wrapper), o, am.PublicId)
acct, err := password.NewAccount(am.GetPublicId(), password.WithLoginName(testLoginName))
require.NoError(t, err)
pwRepo, err := pwRepoFn()
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 := authmethods.NewService(kms, pwRepoFn, oidcRepoFn, iamRepoFn, atRepoFn)
require.NoError(err)
resp, err := s.Authenticate(auth.DisabledAuthTestContext(iamRepoFn, 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 := &authtokenpb.AuthToken{}
require.NoError(handlers.StructToProto(resp.GetAttributes(), aToken, handlers.WithDiscardUnknownFields(true)))
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.GetAttributes().GetFields()["token_type"].GetStringValue())
})
}
}
func TestAuthenticateLogin_Password(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))
iamRepoFn := func() (*iam.Repository, error) {
return iam.TestRepo(t, conn, wrapper), nil
}
oidcRepoFn := func() (*oidc.Repository, error) {
return oidc.NewRepository(rw, rw, kms)
}
pwRepoFn := func() (*password.Repository, error) {
return password.NewRepository(rw, rw, kms)
}
atRepoFn := func() (*authtoken.Repository, error) {
return authtoken.NewRepository(rw, rw, kms)
}
am := password.TestAuthMethods(t, conn, o.GetPublicId(), 1)[0]
iam.TestSetPrimaryAuthMethod(t, iam.TestRepo(t, conn, wrapper), o, am.PublicId)
acct, err := password.NewAccount(am.GetPublicId(), password.WithLoginName(testLoginName))
require.NoError(t, err)
pwRepo, err := pwRepoFn()
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.AuthenticateLoginRequest
wantType string
wantErr error
}{
{
name: "basic",
request: &pbs.AuthenticateLoginRequest{
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.AuthenticateLoginRequest{
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.AuthenticateLoginRequest{
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.AuthenticateLoginRequest{
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.AuthenticateLoginRequest{
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.AuthenticateLoginRequest{
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.AuthenticateLoginRequest{
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 := authmethods.NewService(kms, pwRepoFn, oidcRepoFn, iamRepoFn, atRepoFn)
require.NoError(err)
resp, err := s.AuthenticateLogin(auth.DisabledAuthTestContext(iamRepoFn, 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_Password(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))
iamRepoFn := func() (*iam.Repository, error) {
return iam.TestRepo(t, conn, wrapper), nil
}
oidcRepoFn := func() (*oidc.Repository, error) {
return oidc.NewRepository(rw, rw, kms)
}
pwRepoFn := func() (*password.Repository, error) {
return password.NewRepository(rw, rw, kms)
}
atRepoFn := func() (*authtoken.Repository, error) {
return authtoken.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 := pwRepoFn()
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)
iam.TestUser(t, iamRepo, am.ScopeId, iam.WithAccountIds(acct.PublicId))
iamUser, err := iamRepo.LookupUserWithLogin(context.Background(), acct.GetPublicId())
require.NoError(err)
s, err := authmethods.NewService(kms, pwRepoFn, oidcRepoFn, iamRepoFn, atRepoFn)
require.NoError(err)
resp, err := s.Authenticate(auth.DisabledAuthTestContext(iamRepoFn, 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 := &authtokenpb.AuthToken{}
require.NoError(handlers.StructToProto(resp.GetAttributes(), aToken, handlers.WithDiscardUnknownFields(true)))
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()))
}