diff --git a/internal/errors/code.go b/internal/errors/code.go index bc8e186af..a6a4917cf 100644 --- a/internal/errors/code.go +++ b/internal/errors/code.go @@ -21,20 +21,21 @@ const ( Unknown Code = 0 // Unknown will be equal to a zero value for Codes // General function errors are reserved Codes 100-999 - InvalidParameter Code = 100 // InvalidParameter represents an invalid parameter for an operation. - InvalidAddress Code = 101 // InvalidAddress represents an invalid host address for an operation - InvalidPublicId Code = 102 // InvalidPublicId represents an invalid public Id for an operation - InvalidFieldMask Code = 103 // InvalidFieldMask represents an invalid field mast for an operation - EmptyFieldMask Code = 104 // EmptyFieldMask represents an empty field mask for an operation - KeyNotFound Code = 105 // KeyNotFound represents that a key/version was not found in the KMS - TicketAlreadyRedeemed Code = 106 // TicketAlreadyRedeemed represents that the ticket version has already been redeemed - TicketNotFound Code = 107 // TicketNotFound represents that the ticket was not found - Io Code = 108 // Io represents that an io error occurred in an underlying call (i.e binary.Write) - InvalidTimeStamp Code = 109 // InvalidTimeStamp represents an invalid time stamp for an operation - SessionNotFound Code = 110 // SessionNotFound represents that the session was not found - InvalidSessionState Code = 111 // InvalidSessionState represents that the session was in an invalid state - TokenMismatch Code = 112 // TokenMismatch represents that there was a token mismatch - TooShort Code = 113 // TooShort represents an error that means the provided input is not meeting minimum length requirements + InvalidParameter Code = 100 // InvalidParameter represents an invalid parameter for an operation. + InvalidAddress Code = 101 // InvalidAddress represents an invalid host address for an operation + InvalidPublicId Code = 102 // InvalidPublicId represents an invalid public Id for an operation + InvalidFieldMask Code = 103 // InvalidFieldMask represents an invalid field mast for an operation + EmptyFieldMask Code = 104 // EmptyFieldMask represents an empty field mask for an operation + KeyNotFound Code = 105 // KeyNotFound represents that a key/version was not found in the KMS + TicketAlreadyRedeemed Code = 106 // TicketAlreadyRedeemed represents that the ticket version has already been redeemed + TicketNotFound Code = 107 // TicketNotFound represents that the ticket was not found + Io Code = 108 // Io represents that an io error occurred in an underlying call (i.e binary.Write) + InvalidTimeStamp Code = 109 // InvalidTimeStamp represents an invalid time stamp for an operation + SessionNotFound Code = 110 // SessionNotFound represents that the session was not found + InvalidSessionState Code = 111 // InvalidSessionState represents that the session was in an invalid state + TokenMismatch Code = 112 // TokenMismatch represents that there was a token mismatch + TooShort Code = 113 // TooShort represents an error that means the provided input is not meeting minimum length requirements + AccountAlreadyAssociated Code = 114 // AccountAlreadyAssociated represents an attempt to associate an account failed since it was already associated. // PasswordTooShort results from attempting to set a password which is to short. PasswordTooShort Code = 200 diff --git a/internal/errors/code_test.go b/internal/errors/code_test.go index 2737ab033..67ec7de66 100644 --- a/internal/errors/code_test.go +++ b/internal/errors/code_test.go @@ -97,6 +97,11 @@ func TestCode_Both_String_Info(t *testing.T) { c: TooShort, want: TooShort, }, + { + name: "AccountAlreadyAssociated", + c: AccountAlreadyAssociated, + want: AccountAlreadyAssociated, + }, { name: "InternalError", c: Internal, diff --git a/internal/errors/info.go b/internal/errors/info.go index 860d8cc16..704d673c3 100644 --- a/internal/errors/info.go +++ b/internal/errors/info.go @@ -72,6 +72,10 @@ var errorCodeInfo = map[Code]Info{ Message: "too short", Kind: Integrity, }, + AccountAlreadyAssociated: { + Message: "account already associated with another user", + Kind: Parameter, + }, PasswordTooShort: { Message: "too short", Kind: Password, diff --git a/internal/iam/repository_user.go b/internal/iam/repository_user.go index 1575e7ba6..593501fe4 100644 --- a/internal/iam/repository_user.go +++ b/internal/iam/repository_user.go @@ -651,7 +651,7 @@ func associateUserWithAccounts(ctx context.Context, repoKms *kms.Kms, reader db. return errors.Wrap(err, op, errors.WithMsg(fmt.Sprintf("unable to lookup account %s", accountId))) } if acct.IamUserId != "" && acct.IamUserId != userId { - return errors.New(errors.InvalidParameter, op, fmt.Sprintf("%s account is already associated with another user", accountId)) + return errors.New(errors.AccountAlreadyAssociated, op, fmt.Sprintf("%s account is already associated with another user", accountId)) } authAccounts = append(authAccounts, &acct) } @@ -677,7 +677,7 @@ func associateUserWithAccounts(ctx context.Context, repoKms *kms.Kms, reader db. return errors.Wrap(err, op, errors.WithMsg(fmt.Sprintf("failed to associate %s account", aa.PublicId))) } if updatedRows == 0 { - return errors.New(errors.MultipleRecords, op, fmt.Sprintf("failed to associate %s account: it is already associated with another user", aa.PublicId)) + return errors.New(errors.AccountAlreadyAssociated, op, fmt.Sprintf("failed to associate %s account: it is already associated with another user", aa.PublicId)) } if updatedRows > 1 { return errors.New(errors.MultipleRecords, op, fmt.Sprintf("failed to associate %s account: would have updated too many accounts %d", aa.PublicId, updatedRows)) @@ -715,7 +715,7 @@ func dissociateUserFromAccounts(ctx context.Context, repoKms *kms.Kms, reader db return errors.Wrap(err, op, errors.WithMsg(fmt.Sprintf("unable to lookup account %s", accountId))) } if acct.IamUserId != userId { - return errors.New(errors.InvalidParameter, op, fmt.Sprintf("%s account is not associated with user %s", accountId, userId)) + return errors.New(errors.AccountAlreadyAssociated, op, fmt.Sprintf("%s account is not associated with user %s", accountId, userId)) } authAccounts = append(authAccounts, &acct) } @@ -741,7 +741,7 @@ func dissociateUserFromAccounts(ctx context.Context, repoKms *kms.Kms, reader db return errors.Wrap(err, op, errors.WithMsg(fmt.Sprintf("failed to disassociate %s account", aa.PublicId))) } if updatedRows == 0 { - return errors.New(errors.MultipleRecords, op, fmt.Sprintf("failed to disassociate %s account: it is already associated with another user", aa.PublicId)) + return errors.New(errors.AccountAlreadyAssociated, op, fmt.Sprintf("failed to disassociate %s account: it is already associated with another user", aa.PublicId)) } if updatedRows > 1 { return errors.New(errors.MultipleRecords, op, fmt.Sprintf("failed to disassociate %s account: would have updated too many accounts %d", aa.PublicId, updatedRows)) diff --git a/internal/iam/repository_user_test.go b/internal/iam/repository_user_test.go index e8168fa25..e3172e541 100644 --- a/internal/iam/repository_user_test.go +++ b/internal/iam/repository_user_test.go @@ -718,13 +718,13 @@ func TestRepository_associateUserWithAccounts(t *testing.T) { opt []Option } tests := []struct { - name string - args args - want *User - want1 *authAccount - wantErr bool - wantErrIs errors.Code - wantAssoc bool + name string + args args + want *User + want1 *authAccount + wantErr bool + wantErrCode errors.Code + wantAssoc bool }{ { name: "simple", @@ -746,8 +746,8 @@ func TestRepository_associateUserWithAccounts(t *testing.T) { return Ids{user: id} }(), }, - wantErr: true, - wantErrIs: errors.InvalidParameter, + wantErr: true, + wantErrCode: errors.InvalidParameter, }, { name: "missing-userId", @@ -757,8 +757,8 @@ func TestRepository_associateUserWithAccounts(t *testing.T) { return Ids{accts: []string{id}} }(), }, - wantErr: true, - wantErrIs: errors.InvalidParameter, + wantErr: true, + wantErrCode: errors.InvalidParameter, }, { name: "already-properly-assoc", @@ -781,8 +781,8 @@ func TestRepository_associateUserWithAccounts(t *testing.T) { return Ids{user: u.PublicId, accts: []string{a.PublicId}} }(), }, - wantErr: true, - wantErrIs: errors.InvalidParameter, + wantErr: true, + wantErrCode: errors.AccountAlreadyAssociated, }, { name: "assoc-with-diff-user-withDisassociateOption", @@ -794,8 +794,8 @@ func TestRepository_associateUserWithAccounts(t *testing.T) { return Ids{user: u.PublicId, accts: []string{a.PublicId}} }(), }, - wantErr: true, - wantErrIs: errors.InvalidParameter, + wantErr: true, + wantErrCode: errors.AccountAlreadyAssociated, }, { name: "bad-acct-id", @@ -806,8 +806,8 @@ func TestRepository_associateUserWithAccounts(t *testing.T) { return Ids{user: u.PublicId, accts: []string{id}} }(), }, - wantErr: true, - wantErrIs: errors.RecordNotFound, + wantErr: true, + wantErrCode: errors.RecordNotFound, }, { name: "bad-user-id-not-associated-account", @@ -830,7 +830,8 @@ func TestRepository_associateUserWithAccounts(t *testing.T) { return Ids{user: id, accts: []string{a.PublicId}} }(), }, - wantErr: true, + wantErr: true, + wantErrCode: errors.AccountAlreadyAssociated, }, } for _, tt := range tests { @@ -839,7 +840,7 @@ func TestRepository_associateUserWithAccounts(t *testing.T) { err := associateUserWithAccounts(context.Background(), kms, rw, rw, tt.args.Ids.user, tt.args.Ids.accts, tt.args.opt...) if tt.wantErr { require.Error(err) - assert.Truef(errors.Match(errors.T(tt.wantErrIs), err), "unexpected error %s", err.Error()) + assert.Truef(errors.Match(errors.T(tt.wantErrCode), err), "unexpected error %s", err) return } require.NoError(err) @@ -873,12 +874,12 @@ func TestRepository_dissociateUserWithAccount(t *testing.T) { opt []Option } tests := []struct { - name string - args args - want *User - want1 *authAccount - wantErr bool - wantErrIs errors.Code + name string + args args + want *User + want1 *authAccount + wantErr bool + wantErrCode errors.Code }{ { name: "simple", @@ -900,8 +901,8 @@ func TestRepository_dissociateUserWithAccount(t *testing.T) { return Ids{user: id} }(), }, - wantErr: true, - wantErrIs: errors.InvalidParameter, + wantErr: true, + wantErrCode: errors.InvalidParameter, }, { name: "missing-userId", @@ -911,8 +912,8 @@ func TestRepository_dissociateUserWithAccount(t *testing.T) { return Ids{accts: []string{id}} }(), }, - wantErr: true, - wantErrIs: errors.InvalidParameter, + wantErr: true, + wantErrCode: errors.InvalidParameter, }, { name: "already-properly-disassoc", @@ -923,8 +924,8 @@ func TestRepository_dissociateUserWithAccount(t *testing.T) { return Ids{user: u.PublicId, accts: []string{a.PublicId}} }(), }, - wantErr: true, - wantErrIs: errors.InvalidParameter, + wantErr: true, + wantErrCode: errors.AccountAlreadyAssociated, }, { name: "assoc-with-diff-user", @@ -936,8 +937,8 @@ func TestRepository_dissociateUserWithAccount(t *testing.T) { return Ids{user: u.PublicId, accts: []string{a.PublicId}} }(), }, - wantErr: true, - wantErrIs: errors.InvalidParameter, + wantErr: true, + wantErrCode: errors.AccountAlreadyAssociated, }, { name: "bad-acct-id", @@ -948,8 +949,8 @@ func TestRepository_dissociateUserWithAccount(t *testing.T) { return Ids{user: u.PublicId, accts: []string{id}} }(), }, - wantErr: true, - wantErrIs: errors.RecordNotFound, + wantErr: true, + wantErrCode: errors.RecordNotFound, }, { name: "bad-user-id-not-associated-account", @@ -960,8 +961,8 @@ func TestRepository_dissociateUserWithAccount(t *testing.T) { return Ids{user: id, accts: []string{a.PublicId}} }(), }, - wantErr: true, - wantErrIs: errors.InvalidParameter, + wantErr: true, + wantErrCode: errors.AccountAlreadyAssociated, }, { name: "bad-user-id", @@ -973,7 +974,8 @@ func TestRepository_dissociateUserWithAccount(t *testing.T) { return Ids{user: id, accts: []string{a.PublicId}} }(), }, - wantErr: true, + wantErr: true, + wantErrCode: errors.AccountAlreadyAssociated, }, } for _, tt := range tests { @@ -983,7 +985,7 @@ func TestRepository_dissociateUserWithAccount(t *testing.T) { err := dissociateUserFromAccounts(context.Background(), kms, rw, rw, tt.args.Ids.user, tt.args.Ids.accts, tt.args.opt...) if tt.wantErr { require.Error(err) - assert.Truef(errors.Match(errors.T(tt.wantErrIs), err), "unexpected error %s", err.Error()) + assert.Truef(errors.Match(errors.T(tt.wantErrCode), err), "unexpected error %s", err) return } require.NoError(err) @@ -1023,10 +1025,10 @@ func TestRepository_AssociateAccounts(t *testing.T) { opt []Option } tests := []struct { - name string - args args - wantErr bool - wantErrIs error + name string + args args + wantErr bool + wantErrCode errors.Code }{ { name: "valid", @@ -1061,7 +1063,8 @@ func TestRepository_AssociateAccounts(t *testing.T) { return ids }, }, - wantErr: true, + wantErr: true, + wantErrCode: errors.AccountAlreadyAssociated, }, { name: "bad-version", @@ -1073,7 +1076,8 @@ func TestRepository_AssociateAccounts(t *testing.T) { userId: user.PublicId, accountIdsFn: createAccountsFn, }, - wantErr: true, + wantErr: true, + wantErrCode: errors.MultipleRecords, }, { name: "zero-version", @@ -1085,7 +1089,8 @@ func TestRepository_AssociateAccounts(t *testing.T) { userId: user.PublicId, accountIdsFn: createAccountsFn, }, - wantErr: true, + wantErr: true, + wantErrCode: errors.InvalidParameter, }, { name: "no-accounts", @@ -1093,7 +1098,8 @@ func TestRepository_AssociateAccounts(t *testing.T) { userId: user.PublicId, accountIdsFn: func() []string { return nil }, }, - wantErr: true, + wantErr: true, + wantErrCode: errors.InvalidParameter, }, } for _, tt := range tests { @@ -1113,9 +1119,7 @@ func TestRepository_AssociateAccounts(t *testing.T) { got, err := repo.AddUserAccounts(context.Background(), tt.args.userId, version, accountIds, tt.args.opt...) if tt.wantErr { require.Error(err) - if tt.wantErrIs != nil { - assert.Truef(errors.Is(err, tt.wantErrIs), "unexpected error %s", err.Error()) - } + assert.Truef(errors.Match(errors.T(tt.wantErrCode), err), "unexpected error %s", err) return } require.NoError(err) @@ -1167,10 +1171,10 @@ func TestRepository_DisassociateAccounts(t *testing.T) { opt []Option } tests := []struct { - name string - args args - wantErr bool - wantErrIs error + name string + args args + wantErr bool + wantErrCode errors.Code }{ { name: "valid", @@ -1192,7 +1196,8 @@ func TestRepository_DisassociateAccounts(t *testing.T) { return ids }, }, - wantErr: true, + wantErr: true, + wantErrCode: errors.AccountAlreadyAssociated, }, { name: "bad-version", @@ -1204,7 +1209,8 @@ func TestRepository_DisassociateAccounts(t *testing.T) { userId: user.PublicId, accountIdsFn: createAccountsFn, }, - wantErr: true, + wantErr: true, + wantErrCode: errors.MultipleRecords, }, { name: "zero-version", @@ -1216,7 +1222,8 @@ func TestRepository_DisassociateAccounts(t *testing.T) { userId: user.PublicId, accountIdsFn: createAccountsFn, }, - wantErr: true, + wantErr: true, + wantErrCode: errors.InvalidParameter, }, { name: "no-accounts", @@ -1224,7 +1231,8 @@ func TestRepository_DisassociateAccounts(t *testing.T) { userId: user.PublicId, accountIdsFn: func() []string { return nil }, }, - wantErr: true, + wantErr: true, + wantErrCode: errors.InvalidParameter, }, } for _, tt := range tests { @@ -1243,9 +1251,7 @@ func TestRepository_DisassociateAccounts(t *testing.T) { got, err := repo.DeleteUserAccounts(context.Background(), tt.args.userId, version, accountIds, tt.args.opt...) if tt.wantErr { require.Error(err) - if tt.wantErrIs != nil { - assert.Truef(errors.Is(err, tt.wantErrIs), "unexpected error %s", err.Error()) - } + assert.Truef(errors.Match(errors.T(tt.wantErrCode), err), "unexpected error %s", err) return } require.NoError(err) @@ -1294,10 +1300,10 @@ func TestRepository_SetAssociatedAccounts(t *testing.T) { opt []Option } tests := []struct { - name string - args args - wantErr bool - wantErrIs error + name string + args args + wantErr bool + wantErrCode errors.Code }{ { name: "valid", @@ -1366,7 +1372,8 @@ func TestRepository_SetAssociatedAccounts(t *testing.T) { return ids, ids }, }, - wantErr: true, + wantErr: true, + wantErrCode: errors.AccountAlreadyAssociated, }, { name: "bad-version", @@ -1381,7 +1388,8 @@ func TestRepository_SetAssociatedAccounts(t *testing.T) { return ids, ids }, }, - wantErr: true, + wantErr: true, + wantErrCode: errors.MultipleRecords, }, { name: "zero-version", @@ -1396,7 +1404,8 @@ func TestRepository_SetAssociatedAccounts(t *testing.T) { return ids, ids }, }, - wantErr: true, + wantErr: true, + wantErrCode: errors.InvalidParameter, }, { name: "no-accounts-no-changes", @@ -1424,9 +1433,7 @@ func TestRepository_SetAssociatedAccounts(t *testing.T) { got, err := repo.SetUserAccounts(context.Background(), tt.args.userId, version, accountIds, tt.args.opt...) if tt.wantErr { require.Error(err) - if tt.wantErrIs != nil { - assert.Truef(errors.Is(err, tt.wantErrIs), "unexpected error %s", err.Error()) - } + assert.Truef(errors.Match(errors.T(tt.wantErrCode), err), "unexpected error %s", err) return } require.NoError(err) diff --git a/internal/servers/controller/handlers/errors.go b/internal/servers/controller/handlers/errors.go index 357981e94..de3d0a954 100644 --- a/internal/servers/controller/handlers/errors.go +++ b/internal/servers/controller/handlers/errors.go @@ -159,6 +159,8 @@ func backendErrorToApiError(inErr error) error { } case errors.Match(errors.T(errors.RecordNotFound), inErr): return NotFoundErrorf(genericNotFoundMsg) + case errors.Match(errors.T(errors.AccountAlreadyAssociated), inErr): + return InvalidArgumentErrorf(inErr.Error(), nil) case errors.Match(errors.T(errors.InvalidFieldMask), inErr), errors.Match(errors.T(errors.EmptyFieldMask), inErr): return InvalidArgumentErrorf("Error in provided request", map[string]string{"update_mask": "Invalid update mask provided."}) case errors.IsUniqueError(inErr): diff --git a/internal/servers/controller/handlers/errors_test.go b/internal/servers/controller/handlers/errors_test.go index d9d3c09d3..903e2fc18 100644 --- a/internal/servers/controller/handlers/errors_test.go +++ b/internal/servers/controller/handlers/errors_test.go @@ -184,6 +184,17 @@ func TestApiErrorHandler(t *testing.T) { }, }, }, + { + name: "Domain error account already associated", + err: errors.E(errors.WithCode(errors.AccountAlreadyAssociated)), + expected: apiError{ + status: http.StatusBadRequest, + inner: &pb.Error{ + Kind: "InvalidArgument", + Message: "account already associated with another user, parameter violation: error #114", + }, + }, + }, { name: "Wrapped domain error", err: errors.E(errors.WithCode(errors.InvalidAddress), errors.WithMsg("test msg"), errors.WithWrap(errors.E(errors.WithCode(errors.NotNull), errors.WithMsg("inner msg")))),