From eec6c12f619f4cc83854764e7e1f49067009a0de Mon Sep 17 00:00:00 2001 From: Wes Date: Mon, 2 Dec 2024 14:51:29 -0500 Subject: [PATCH 1/9] BED-5065: initial change --- cmd/api/src/api/v2/auth/auth.go | 6 +++++- cmd/api/src/database/auth.go | 8 +++++++- cmd/api/src/database/db.go | 6 ++++-- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/cmd/api/src/api/v2/auth/auth.go b/cmd/api/src/api/v2/auth/auth.go index ee0373869e..b165ed3a3e 100644 --- a/cmd/api/src/api/v2/auth/auth.go +++ b/cmd/api/src/api/v2/auth/auth.go @@ -356,7 +356,11 @@ func (s ManagementResource) CreateUser(response http.ResponseWriter, request *ht } if newUser, err := s.db.CreateUser(request.Context(), userTemplate); err != nil { - api.HandleDatabaseError(request, response, err) + if errors.Is(err, database.ErrDuplicateUserPrincipal) { + api.WriteErrorResponse(request.Context(), api.BuildErrorResponse(http.StatusConflict, "Principal name already in use", request), response) + } else { + api.HandleDatabaseError(request, response, err) + } } else { api.WriteBasicResponse(request.Context(), newUser, http.StatusOK, response) } diff --git a/cmd/api/src/database/auth.go b/cmd/api/src/database/auth.go index d65c358bf6..57378e5a66 100644 --- a/cmd/api/src/database/auth.go +++ b/cmd/api/src/database/auth.go @@ -264,7 +264,13 @@ func (s *BloodhoundDB) CreateUser(ctx context.Context, user model.User) (model.U Model: &updatedUser, } return updatedUser, s.AuditableTransaction(ctx, auditEntry, func(tx *gorm.DB) error { - return CheckError(tx.WithContext(ctx).Create(&updatedUser)) + err := tx.WithContext(ctx).Create(&updatedUser) + if err != nil { + if strings.Contains(err.Error.Error(), "duplicate key value violates unique constraint \"users_principal_name_key\"") { + return fmt.Errorf("%w: %v", ErrDuplicateUserPrincipal, err) + } + } + return CheckError(err) }) } diff --git a/cmd/api/src/database/db.go b/cmd/api/src/database/db.go index 19c792f2c5..5c601ade91 100644 --- a/cmd/api/src/database/db.go +++ b/cmd/api/src/database/db.go @@ -44,8 +44,10 @@ const ( ) var ( - ErrDuplicateAGName = errors.New("duplicate asset group name") - ErrDuplicateAGTag = errors.New("duplicate asset group tag") + ErrDuplicateAGName = errors.New("duplicate asset group name") + ErrDuplicateAGTag = errors.New("duplicate asset group tag") + ErrDuplicateUserPrincipal = errors.New("duplicate user principal name") + ErrDuplicateUserEmail = errors.New("duplicate user email") ) func IsUnexpectedDatabaseError(err error) bool { From 8c92fde2db3627c98ad29e0877e7f751b9b04c61 Mon Sep 17 00:00:00 2001 From: Wes Date: Tue, 3 Dec 2024 16:56:09 -0500 Subject: [PATCH 2/9] BED-5065: adjust where error is being checked in db Adjust where error is being checked on api --- cmd/api/src/api/error.go | 3 +++ cmd/api/src/api/v2/auth/auth.go | 6 +----- cmd/api/src/database/auth.go | 8 +------- cmd/api/src/database/helper.go | 10 +++++++++- 4 files changed, 14 insertions(+), 13 deletions(-) diff --git a/cmd/api/src/api/error.go b/cmd/api/src/api/error.go index aa5afac6d7..316993ed92 100644 --- a/cmd/api/src/api/error.go +++ b/cmd/api/src/api/error.go @@ -64,6 +64,7 @@ const ( ErrorResponseAGNameTagEmpty = "asset group name or tag must not be empty" ErrorResponseAGDuplicateName = "asset group name must be unique" ErrorResponseAGDuplicateTag = "asset group tag must be unique" + ErrorResponseUserDuplicatePrincipal = "principal name must be unique" ErrorResponseDetailsUniqueViolation = "unique constraint was violated" ErrorResponseDetailsNotImplemented = "All good things to those who wait. Not implemented." @@ -122,6 +123,8 @@ func BuildErrorResponse(httpStatus int, message string, request *http.Request) * func HandleDatabaseError(request *http.Request, response http.ResponseWriter, err error) { if errors.Is(err, database.ErrNotFound) { WriteErrorResponse(request.Context(), BuildErrorResponse(http.StatusNotFound, ErrorResponseDetailsResourceNotFound, request), response) + } else if errors.Is(err, database.ErrDuplicateUserPrincipal) { + WriteErrorResponse(request.Context(), BuildErrorResponse(http.StatusConflict, ErrorResponseUserDuplicatePrincipal, request), response) } else if errors.Is(err, context.DeadlineExceeded) { WriteErrorResponse(request.Context(), BuildErrorResponse(http.StatusInternalServerError, ErrorResponseRequestTimeout, request), response) } else { diff --git a/cmd/api/src/api/v2/auth/auth.go b/cmd/api/src/api/v2/auth/auth.go index b165ed3a3e..ee0373869e 100644 --- a/cmd/api/src/api/v2/auth/auth.go +++ b/cmd/api/src/api/v2/auth/auth.go @@ -356,11 +356,7 @@ func (s ManagementResource) CreateUser(response http.ResponseWriter, request *ht } if newUser, err := s.db.CreateUser(request.Context(), userTemplate); err != nil { - if errors.Is(err, database.ErrDuplicateUserPrincipal) { - api.WriteErrorResponse(request.Context(), api.BuildErrorResponse(http.StatusConflict, "Principal name already in use", request), response) - } else { - api.HandleDatabaseError(request, response, err) - } + api.HandleDatabaseError(request, response, err) } else { api.WriteBasicResponse(request.Context(), newUser, http.StatusOK, response) } diff --git a/cmd/api/src/database/auth.go b/cmd/api/src/database/auth.go index 57378e5a66..d65c358bf6 100644 --- a/cmd/api/src/database/auth.go +++ b/cmd/api/src/database/auth.go @@ -264,13 +264,7 @@ func (s *BloodhoundDB) CreateUser(ctx context.Context, user model.User) (model.U Model: &updatedUser, } return updatedUser, s.AuditableTransaction(ctx, auditEntry, func(tx *gorm.DB) error { - err := tx.WithContext(ctx).Create(&updatedUser) - if err != nil { - if strings.Contains(err.Error.Error(), "duplicate key value violates unique constraint \"users_principal_name_key\"") { - return fmt.Errorf("%w: %v", ErrDuplicateUserPrincipal, err) - } - } - return CheckError(err) + return CheckError(tx.WithContext(ctx).Create(&updatedUser)) }) } diff --git a/cmd/api/src/database/helper.go b/cmd/api/src/database/helper.go index fd361a075d..21c84e212c 100644 --- a/cmd/api/src/database/helper.go +++ b/cmd/api/src/database/helper.go @@ -17,9 +17,11 @@ package database import ( + "errors" + "fmt" "github.com/gofrs/uuid" - "github.com/specterops/bloodhound/errors" "gorm.io/gorm" + "strings" ) func CheckError(tx *gorm.DB) error { @@ -27,6 +29,12 @@ func CheckError(tx *gorm.DB) error { return ErrNotFound } + if tx.Error != nil { + if strings.Contains(tx.Error.Error(), "duplicate key value violates unique constraint \"users_principal_name_key\"") { + return fmt.Errorf("%w: %v", ErrDuplicateUserPrincipal, tx.Error) + } + } + return tx.Error } From 1b1876e175308de114265aa0fb862acedd45ad72 Mon Sep 17 00:00:00 2001 From: Wes Date: Thu, 5 Dec 2024 14:18:38 -0500 Subject: [PATCH 3/9] BED-5065: Add error checking --- .../components/CreateUserForm/CreateUserForm.tsx | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/packages/javascript/bh-shared-ui/src/components/CreateUserForm/CreateUserForm.tsx b/packages/javascript/bh-shared-ui/src/components/CreateUserForm/CreateUserForm.tsx index 5557c6ce66..6e7eff30c0 100644 --- a/packages/javascript/bh-shared-ui/src/components/CreateUserForm/CreateUserForm.tsx +++ b/packages/javascript/bh-shared-ui/src/components/CreateUserForm/CreateUserForm.tsx @@ -82,6 +82,14 @@ const CreateUserForm: React.FC<{ onCancel(); }; + const checkError = (err): React.ReactElement => { + if (err.response.data.errors[0].message == "Principal name already in use") { + return Principal name is already in use.; + } else { + return An unexpected error occurred. Please try again.; + } + } + return (
{!(getRolesQuery.isLoading || listSSOProvidersQuery.isLoading) && ( @@ -338,11 +346,7 @@ const CreateUserForm: React.FC<{ - {error && ( - - An unexpected error occurred. Please try again. - - )} + {error && (checkError(error))}