Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Audit Log Refactor #345

Merged
merged 39 commits into from
Feb 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
0e1d356
feat: Initial migration and model updates for AuditLog improvements
juggernot325 Jan 5, 2024
d0c6173
feat: populate actor_email for existing audit_logs
juggernot325 Jan 8, 2024
1d08960
chore: Fix audit log docs to show the actual response object instead …
juggernot325 Jan 9, 2024
6019c99
feat: Log Requester IP for source and actor email with audit logs
juggernot325 Jan 11, 2024
0f0d55a
wip: createUser audit logging via middleware
juggernot325 Jan 12, 2024
3d65a85
chore: refactor audit logging to trigger from LoggingMiddleware based…
juggernot325 Jan 19, 2024
86b77a3
wip: Successfully creating audit logs from middleware. Capturing fail…
juggernot325 Jan 23, 2024
22c3e8c
feat: Capture DB errors in audit logs
juggernot325 Jan 23, 2024
cf4b854
chore: remove debug logging
juggernot325 Jan 23, 2024
985d580
chore: Refactor setting of status to receiver function
juggernot325 Jan 23, 2024
29673a9
wip: All "simple" audit context endpoints updated for middeware
juggernot325 Jan 24, 2024
a05ba51
fix: Fix a few bugs found during testing
juggernot325 Jan 24, 2024
cd78c04
fix: Remove doc for non-existent POST /api/v2/saml endpoint
juggernot325 Jan 24, 2024
9cb8a3d
wip: Refactor to simplify setting of AuditCtx in endpoints
juggernot325 Jan 25, 2024
e1d81bd
fix: registration issue after rebase
superlinkx Jan 25, 2024
3ac13c1
tasks 6 and 7 for #345 (#357)
irshadaj Jan 26, 2024
fd32507
Merge remote-tracking branch 'origin/main' into populate-audit-log-fi…
superlinkx Jan 29, 2024
2a14ff5
redo IP parsing for changed requirements (#362)
irshadaj Jan 29, 2024
602d04b
feat: audit log steel thread (#364)
superlinkx Jan 30, 2024
6fe1f94
chore: steel thread cleanup (#365)
superlinkx Jan 30, 2024
6810bd3
add eula_accepted to user audit data (#371)
irshadaj Jan 30, 2024
0482b61
audit logs for auth tokens and secrets (#372)
irshadaj Jan 31, 2024
af01314
Merge branch 'main' into populate-audit-log-fields
superlinkx Jan 31, 2024
9ff9831
Merge remote-tracking branch 'origin/main' into populate-audit-log-fi…
superlinkx Jan 31, 2024
3902523
feat: UpdateAssetGroup, UpdateAssetGroupSelector, DeleteAssetGroupSel…
superlinkx Jan 31, 2024
d744c31
more auth handlers for audit log (#373)
irshadaj Jan 31, 2024
061dc91
chore: update documentation for our new commit_id field and `intent` …
superlinkx Jan 31, 2024
4cd2264
Generate audit log entries on unauthorized access attempts (#375)
juggernot325 Jan 31, 2024
13f4160
chore: change source column to TEXT
juggernot325 Feb 1, 2024
2a2c839
rename audit log source column, add indices
irshadaj Feb 1, 2024
cc16a11
Revert "rename audit log source column, add indices"
juggernot325 Feb 1, 2024
2624969
chore: rename audit_log.source as 'source' is a reserved keyword in p…
juggernot325 Feb 1, 2024
c36c609
chore: add additional fields to user.AuditData
juggernot325 Feb 1, 2024
eb5e741
Refactored unauthorized access audit logging: (#381)
sircodemane Feb 2, 2024
0719913
chore: cleanup before review
juggernot325 Feb 2, 2024
847c92c
Merge branch 'main' into populate-audit-log-fields
juggernot325 Feb 2, 2024
37726e5
review comments
irshadaj Feb 5, 2024
9e3cbe7
Merge branch 'main' into populate-audit-log-fields
irshadaj Feb 5, 2024
ba742f5
undo testing change
irshadaj Feb 5, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/config/integration.config.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
"collectors_base_path": "/tmp/collectors",
"log_level": "ERROR",
"log_path": "bhapi.log",
"enable_startup_wait_period": false,
"datapipe_interval": 1,
"features": {
"enable_auth": true
},
Expand Down
6 changes: 4 additions & 2 deletions cmd/api/src/api/middleware/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,9 @@ import (
"strings"
"time"

"github.com/specterops/bloodhound/src/ctx"

"github.com/gofrs/uuid"
"github.com/gorilla/mux"
"github.com/specterops/bloodhound/src/ctx"

"github.com/specterops/bloodhound/src/api"
"github.com/specterops/bloodhound/src/auth"
Expand Down Expand Up @@ -107,6 +106,7 @@ func PermissionsCheckAll(authorizer auth.Authorizer, permissions ...model.Permis
if bhCtx := ctx.FromRequest(request); !bhCtx.AuthCtx.Authenticated() {
api.WriteErrorResponse(request.Context(), api.BuildErrorResponse(http.StatusUnauthorized, "not authenticated", request), response)
} else if !authorizer.AllowsAllPermissions(bhCtx.AuthCtx, permissions) {
authorizer.AuditLogUnauthorizedAccess(request)
api.WriteErrorResponse(request.Context(), api.BuildErrorResponse(http.StatusForbidden, "not authorized", request), response)
} else {
next.ServeHTTP(response, request)
Expand All @@ -123,6 +123,7 @@ func PermissionsCheckAtLeastOne(authorizer auth.Authorizer, permissions ...model
if bhCtx := ctx.FromRequest(request); !bhCtx.AuthCtx.Authenticated() {
api.WriteErrorResponse(request.Context(), api.BuildErrorResponse(http.StatusUnauthorized, "not authenticated", request), response)
} else if !authorizer.AllowsAtLeastOnePermission(bhCtx.AuthCtx, permissions) {
authorizer.AuditLogUnauthorizedAccess(request)
api.WriteErrorResponse(request.Context(), api.BuildErrorResponse(http.StatusForbidden, "not authorized", request), response)
} else {
next.ServeHTTP(response, request)
Expand Down Expand Up @@ -188,6 +189,7 @@ func AuthorizeAuthManagementAccess(permissions auth.PermissionSet, authorizer au
}

if !authorized {
authorizer.AuditLogUnauthorizedAccess(request)
api.WriteErrorResponse(request.Context(), api.BuildErrorResponse(http.StatusUnauthorized, fmt.Sprintf("not authorized for %s", userID), request), response)
} else {
next.ServeHTTP(response, request)
Expand Down
46 changes: 29 additions & 17 deletions cmd/api/src/api/middleware/auth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,18 +25,20 @@ import (
"github.com/specterops/bloodhound/src/api"
"github.com/specterops/bloodhound/src/auth"
"github.com/specterops/bloodhound/src/ctx"
dbmocks "github.com/specterops/bloodhound/src/database/mocks"
"github.com/specterops/bloodhound/src/model"
"github.com/specterops/bloodhound/src/test/must"
"github.com/specterops/bloodhound/src/utils/test"
"github.com/stretchr/testify/require"
"go.uber.org/mock/gomock"
)

func permissionsCheckAllHandler(internalHandler http.HandlerFunc, permissions ...model.Permission) http.Handler {
return PermissionsCheckAll(auth.NewAuthorizer(), permissions...)(internalHandler)
func permissionsCheckAllHandler(db *dbmocks.MockDatabase, internalHandler http.HandlerFunc, permissions ...model.Permission) http.Handler {
return PermissionsCheckAll(auth.NewAuthorizer(db), permissions...)(internalHandler)
}

func permissionsCheckAtLeastOneHandler(internalHandler http.HandlerFunc, permissions ...model.Permission) http.Handler {
return PermissionsCheckAtLeastOne(auth.NewAuthorizer(), permissions...)(internalHandler)
func permissionsCheckAtLeastOneHandler(db *dbmocks.MockDatabase, internalHandler http.HandlerFunc, permissions ...model.Permission) http.Handler {
return PermissionsCheckAtLeastOne(auth.NewAuthorizer(db), permissions...)(internalHandler)
}

func Test_parseAuthorizationHeader(t *testing.T) {
Expand All @@ -61,13 +63,17 @@ func TestPermissionsCheckAll(t *testing.T) {
handlerReturn200 = func(response http.ResponseWriter, request *http.Request) {
response.WriteHeader(http.StatusOK)
}
mockCtrl = gomock.NewController(t)
mockDB = dbmocks.NewMockDatabase(mockCtrl)
)
defer mockCtrl.Finish()

mockDB.EXPECT().AppendAuditLog(gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
test.Request(t).
WithURL("http//example.com").
WithHeader(headers.RequestID.String(), "requestID").
WithContext(&ctx.Context{}).
OnHandler(permissionsCheckAllHandler(handlerReturn200, auth.Permissions().AuthManageSelf)).
OnHandler(permissionsCheckAllHandler(mockDB, handlerReturn200, auth.Permissions().AuthManageSelf)).
Require().
ResponseStatusCode(http.StatusUnauthorized)

Expand All @@ -87,10 +93,11 @@ func TestPermissionsCheckAll(t *testing.T) {
Session: model.UserSession{},
},
}).
OnHandler(permissionsCheckAllHandler(handlerReturn200, auth.Permissions().AuthManageSelf)).
OnHandler(permissionsCheckAllHandler(mockDB, handlerReturn200, auth.Permissions().AuthManageSelf)).
Require().
ResponseStatusCode(http.StatusForbidden)

mockDB.EXPECT().AppendAuditLog(gomock.Any(), gomock.Any()).Return(nil).Times(0) // No audit logs should be created on successful login
test.Request(t).
WithURL("http//example.com").
WithHeader(headers.RequestID.String(), "requestID").
Expand All @@ -109,7 +116,7 @@ func TestPermissionsCheckAll(t *testing.T) {
Session: model.UserSession{},
},
}).
OnHandler(permissionsCheckAllHandler(handlerReturn200, auth.Permissions().AuthManageSelf)).
OnHandler(permissionsCheckAllHandler(mockDB, handlerReturn200, auth.Permissions().AuthManageSelf)).
Require().
ResponseStatusCode(http.StatusOK)
}
Expand All @@ -119,8 +126,12 @@ func TestPermissionsCheckAtLeastOne(t *testing.T) {
handlerReturn200 = func(response http.ResponseWriter, request *http.Request) {
response.WriteHeader(http.StatusOK)
}
mockCtrl = gomock.NewController(t)
mockDB = dbmocks.NewMockDatabase(mockCtrl)
)
defer mockCtrl.Finish()

mockDB.EXPECT().AppendAuditLog(gomock.Any(), gomock.Any()).Return(nil).Times(0)
test.Request(t).
WithURL("http//example.com").
WithContext(&ctx.Context{
Expand All @@ -138,7 +149,7 @@ func TestPermissionsCheckAtLeastOne(t *testing.T) {
Session: model.UserSession{},
},
}).
OnHandler(permissionsCheckAtLeastOneHandler(handlerReturn200, auth.Permissions().AuthManageSelf)).
OnHandler(permissionsCheckAtLeastOneHandler(mockDB, handlerReturn200, auth.Permissions().AuthManageSelf)).
Require().
ResponseStatusCode(http.StatusOK)

Expand All @@ -159,7 +170,7 @@ func TestPermissionsCheckAtLeastOne(t *testing.T) {
Session: model.UserSession{},
},
}).
OnHandler(permissionsCheckAtLeastOneHandler(handlerReturn200, auth.Permissions().AuthManageSelf)).
OnHandler(permissionsCheckAtLeastOneHandler(mockDB, handlerReturn200, auth.Permissions().AuthManageSelf)).
Require().
ResponseStatusCode(http.StatusOK)

Expand All @@ -180,12 +191,13 @@ func TestPermissionsCheckAtLeastOne(t *testing.T) {
Session: model.UserSession{},
},
}).
OnHandler(permissionsCheckAtLeastOneHandler(handlerReturn200, auth.Permissions().GraphDBRead)).
OnHandler(permissionsCheckAtLeastOneHandler(mockDB, handlerReturn200, auth.Permissions().GraphDBRead)).
Require().
ResponseStatusCode(http.StatusOK)

test.Request(t).
WithURL("http//example.com").
WithHeader(headers.RequestID.String(), "requestID").
WithContext(&ctx.Context{
AuthCtx: auth.Context{
PermissionOverrides: auth.PermissionOverrides{},
Expand All @@ -194,20 +206,20 @@ func TestPermissionsCheckAtLeastOne(t *testing.T) {
{
Name: "Big Boy",
Description: "The big boy.",
Permissions: model.Permissions{auth.Permissions().AuthManageSelf, auth.Permissions().GraphDBRead},
Permissions: auth.Permissions().All(),
},
},
},
Session: model.UserSession{},
},
}).
OnHandler(permissionsCheckAtLeastOneHandler(handlerReturn200, auth.Permissions().GraphDBWrite)).
OnHandler(permissionsCheckAtLeastOneHandler(mockDB, handlerReturn200, auth.Permissions().AuthManageSelf)).
Require().
ResponseStatusCode(http.StatusForbidden)
ResponseStatusCode(http.StatusOK)

mockDB.EXPECT().AppendAuditLog(gomock.Any(), gomock.Any()).Return(nil).Times(1)
test.Request(t).
WithURL("http//example.com").
WithHeader(headers.RequestID.String(), "requestID").
WithContext(&ctx.Context{
AuthCtx: auth.Context{
PermissionOverrides: auth.PermissionOverrides{},
Expand All @@ -216,14 +228,14 @@ func TestPermissionsCheckAtLeastOne(t *testing.T) {
{
Name: "Big Boy",
Description: "The big boy.",
Permissions: auth.Permissions().All(),
Permissions: model.Permissions{auth.Permissions().AuthManageSelf, auth.Permissions().GraphDBRead},
},
},
},
Session: model.UserSession{},
},
}).
OnHandler(permissionsCheckAtLeastOneHandler(handlerReturn200, auth.Permissions().AuthManageSelf)).
OnHandler(permissionsCheckAtLeastOneHandler(mockDB, handlerReturn200, auth.Permissions().GraphDBWrite)).
Require().
ResponseStatusCode(http.StatusOK)
ResponseStatusCode(http.StatusForbidden)
}
3 changes: 2 additions & 1 deletion cmd/api/src/api/middleware/logging.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import (
"github.com/specterops/bloodhound/src/auth"
"github.com/specterops/bloodhound/src/config"
"github.com/specterops/bloodhound/src/ctx"
"github.com/specterops/bloodhound/src/database"
)

// PanicHandler is a middleware func that sets up a defer-recovery trap to capture any unhandled panics that bubble
Expand Down Expand Up @@ -114,7 +115,7 @@ func setSignedRequestFields(request *http.Request, logEvent log.Event) {

// LoggingMiddleware is a middleware func that outputs a log for each request-response lifecycle. It includes timestamped
// information organized into fields suitable for searching or parsing.
func LoggingMiddleware(cfg config.Configuration, idResolver auth.IdentityResolver) func(http.Handler) http.Handler {
func LoggingMiddleware(cfg config.Configuration, idResolver auth.IdentityResolver, db *database.BloodhoundDB) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(response http.ResponseWriter, request *http.Request) {
var (
Expand Down
13 changes: 12 additions & 1 deletion cmd/api/src/api/middleware/middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,8 +129,8 @@ func ContextMiddleware(next http.Handler) http.Handler {
// Create a new context with the timeout
requestCtx, cancel := context.WithTimeout(request.Context(), requestedWaitDuration.Value)
defer cancel()

// Insert the bh context

requestCtx = ctx.Set(requestCtx, &ctx.Context{
StartTime: startTime,
Timeout: requestedWaitDuration,
Expand All @@ -139,6 +139,7 @@ func ContextMiddleware(next http.Handler) http.Handler {
Scheme: getScheme(request),
Host: request.Host,
},
RequestIP: parseUserIP(request),
})

// Route the request with the embedded context
Expand All @@ -147,6 +148,16 @@ func ContextMiddleware(next http.Handler) http.Handler {
})
}

func parseUserIP(r *http.Request) string {
irshadaj marked this conversation as resolved.
Show resolved Hide resolved
if result := r.Header.Get("X-Forwarded-For"); result == "" {
log.Warnf("No data found in X-Forwarded-For header")
return r.RemoteAddr
} else {
result += "," + r.RemoteAddr
return result
}
}

func ParseHeaderValues(values string) map[string]string {
parsed := map[string]string{}

Expand Down
22 changes: 22 additions & 0 deletions cmd/api/src/api/middleware/middleware_internal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"crypto/tls"
"net/http"
"net/url"
"strings"
"testing"
"time"

Expand Down Expand Up @@ -79,6 +80,27 @@ func TestRequestWaitDuration(t *testing.T) {
require.True(t, requestedWaitDuration.UserSet)
}

func TestParseUserIP_XForwardedFor_RemoteAddr(t *testing.T) {
req, err := http.NewRequest("GET", "/teapot", nil)
require.Nil(t, err)

ip1 := "192.168.1.1:8080"
ip2 := "192.168.1.2"
ip3 := "192.168.1.3"

req.Header.Set("X-Forwarded-For", strings.Join([]string{ip1, ip2, ip3}, ","))
req.RemoteAddr = "0.0.0.0:3000"

require.Equal(t, parseUserIP(req), strings.Join([]string{ip1, ip2, ip3, req.RemoteAddr}, ","))
}

func TestParseUserIP_RemoteAddrOnly(t *testing.T) {
req, err := http.NewRequest("GET", "/teapot", nil)
require.Nil(t, err)
req.RemoteAddr = "0.0.0.0:3000"
require.Equal(t, parseUserIP(req), req.RemoteAddr)
}

func TestParsePreferHeaderWait(t *testing.T) {
_, err := parsePreferHeaderWait("wait=1.5", 30*time.Second)
require.NotNil(t, err)
Expand Down
4 changes: 2 additions & 2 deletions cmd/api/src/api/registration/registration.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,14 @@ import (
"github.com/specterops/bloodhound/src/queries"
)

func RegisterFossGlobalMiddleware(routerInst *router.Router, cfg config.Configuration, identityResolver auth.IdentityResolver, authenticator api.Authenticator) {
func RegisterFossGlobalMiddleware(routerInst *router.Router, cfg config.Configuration, db *database.BloodhoundDB, identityResolver auth.IdentityResolver, authenticator api.Authenticator) {
// Set up the middleware stack
routerInst.UsePrerouting(middleware.ContextMiddleware)
routerInst.UsePrerouting(middleware.CORSMiddleware())

// Set up logging. This must be done after ContextMiddleware is initialized so the context can be accessed in the log logic
if cfg.EnableAPILogging {
routerInst.UsePrerouting(middleware.LoggingMiddleware(cfg, identityResolver))
routerInst.UsePrerouting(middleware.LoggingMiddleware(cfg, identityResolver, db))
}

routerInst.UsePostrouting(
Expand Down
2 changes: 1 addition & 1 deletion cmd/api/src/api/registration/v2.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ func samlWriteAPIErrorResponse(request *http.Request, response http.ResponseWrit
func registerV2Auth(cfg config.Configuration, db database.Database, permissions auth.PermissionSet, routerInst *router.Router, authenticator api.Authenticator) {
var (
loginResource = authapi.NewLoginResource(cfg, authenticator, db)
managementResource = authapi.NewManagementResource(cfg, db, auth.NewAuthorizer())
managementResource = authapi.NewManagementResource(cfg, db, auth.NewAuthorizer(db))
samlResource = saml.NewSAMLRootResource(cfg, db, samlWriteAPIErrorResponse)
)

Expand Down
25 changes: 10 additions & 15 deletions cmd/api/src/api/v2/agi.go
Original file line number Diff line number Diff line change
Expand Up @@ -175,20 +175,19 @@ func (s Resources) UpdateAssetGroup(response http.ResponseWriter, request *http.
pathVars = mux.Vars(request)
rawAssetGroupID = pathVars[api.URIPathVariableAssetGroupID]
updateAssetGroupRequest UpdateAssetGroupRequest
assetGroup model.AssetGroup
)

if assetGroupID, err := strconv.Atoi(rawAssetGroupID); err != nil {
api.WriteErrorResponse(request.Context(), api.BuildErrorResponse(http.StatusBadRequest, api.ErrorResponseDetailsIDMalformed, request), response)
} else if err := api.ReadJSONRequestPayloadLimited(&updateAssetGroupRequest, request); err != nil {
api.WriteErrorResponse(request.Context(), api.BuildErrorResponse(http.StatusBadRequest, err.Error(), request), response)
} else if err := s.DB.AppendAuditLog(*ctx.FromRequest(request), "UpdateAssetGroup", updateAssetGroupRequest); err != nil {
api.HandleDatabaseError(request, response, err)
} else if assetGroup, err := s.DB.GetAssetGroup(int32(assetGroupID)); err != nil {
} else if assetGroup, err = s.DB.GetAssetGroup(int32(assetGroupID)); err != nil {
api.HandleDatabaseError(request, response, err)
} else {
assetGroup.Name = updateAssetGroupRequest.Name

if err := s.DB.UpdateAssetGroup(assetGroup); err != nil {
if err := s.DB.UpdateAssetGroup(request.Context(), assetGroup); err != nil {
api.HandleDatabaseError(request, response, err)
} else {
api.WriteBasicResponse(request.Context(), assetGroup, http.StatusOK, response)
Expand All @@ -201,9 +200,7 @@ func (s Resources) CreateAssetGroup(response http.ResponseWriter, request *http.

if err := api.ReadJSONRequestPayloadLimited(&createRequest, request); err != nil {
api.WriteErrorResponse(request.Context(), api.BuildErrorResponse(http.StatusBadRequest, err.Error(), request), response)
} else if err := s.DB.AppendAuditLog(*ctx.FromRequest(request), "CreateAssetGroup", createRequest); err != nil {
api.HandleDatabaseError(request, response, err)
} else if newAssetGroup, err := s.DB.CreateAssetGroup(createRequest.Name, createRequest.Tag, false); err != nil {
} else if newAssetGroup, err := s.DB.CreateAssetGroup(request.Context(), createRequest.Name, createRequest.Tag, false); err != nil {
api.HandleDatabaseError(request, response, err)
} else {
assetGroupURL := *ctx.Get(request.Context()).Host
Expand All @@ -218,17 +215,16 @@ func (s Resources) DeleteAssetGroup(response http.ResponseWriter, request *http.
var (
pathVars = mux.Vars(request)
rawAssetGroupID = pathVars[api.URIPathVariableAssetGroupID]
assetGroup model.AssetGroup
)

if assetGroupID, err := strconv.Atoi(rawAssetGroupID); err != nil {
api.WriteErrorResponse(request.Context(), api.BuildErrorResponse(http.StatusBadRequest, api.ErrorResponseDetailsIDMalformed, request), response)
} else if assetGroup, err := s.DB.GetAssetGroup(int32(assetGroupID)); err != nil {
} else if assetGroup, err = s.DB.GetAssetGroup(int32(assetGroupID)); err != nil {
api.HandleDatabaseError(request, response, err)
} else if assetGroup.SystemGroup {
api.WriteErrorResponse(request.Context(), api.BuildErrorResponse(http.StatusConflict, "Cannot delete a system defined asset group.", request), response)
} else if err := s.DB.AppendAuditLog(*ctx.FromRequest(request), "DeleteAssetGroup", assetGroup); err != nil {
api.HandleDatabaseError(request, response, err)
} else if err := s.DB.DeleteAssetGroup(assetGroup); err != nil {
} else if err := s.DB.DeleteAssetGroup(request.Context(), assetGroup); err != nil {
api.HandleDatabaseError(request, response, err)
} else {
response.WriteHeader(http.StatusOK)
Expand Down Expand Up @@ -274,6 +270,7 @@ func (s Resources) UpdateAssetGroupSelectors(response http.ResponseWriter, reque

func (s Resources) DeleteAssetGroupSelector(response http.ResponseWriter, request *http.Request) {
var (
assetGroupSelector model.AssetGroupSelector
pathVars = mux.Vars(request)
rawAssetGroupID = pathVars[api.URIPathVariableAssetGroupID]
rawAssetGroupSelectorID = pathVars[api.URIPathVariableAssetGroupSelectorID]
Expand All @@ -285,13 +282,11 @@ func (s Resources) DeleteAssetGroupSelector(response http.ResponseWriter, reques
api.HandleDatabaseError(request, response, err)
} else if assetGroupSelectorID, err := strconv.Atoi(rawAssetGroupSelectorID); err != nil {
api.WriteErrorResponse(request.Context(), api.BuildErrorResponse(http.StatusBadRequest, api.ErrorResponseDetailsIDMalformed, request), response)
} else if assetGroupSelector, err := s.DB.GetAssetGroupSelector(int32(assetGroupSelectorID)); err != nil {
} else if assetGroupSelector, err = s.DB.GetAssetGroupSelector(int32(assetGroupSelectorID)); err != nil {
api.HandleDatabaseError(request, response, err)
} else if assetGroupSelector.SystemSelector {
api.WriteErrorResponse(request.Context(), api.BuildErrorResponse(http.StatusConflict, "Cannot delete a system defined asset group selector.", request), response)
} else if err := s.DB.AppendAuditLog(*ctx.FromRequest(request), "DeleteAssetGroupSelector", assetGroupSelector); err != nil {
api.HandleDatabaseError(request, response, err)
} else if err := s.DB.DeleteAssetGroupSelector(assetGroupSelector); err != nil {
} else if err := s.DB.DeleteAssetGroupSelector(request.Context(), assetGroupSelector); err != nil {
api.HandleDatabaseError(request, response, err)
} else {
response.WriteHeader(http.StatusOK)
Expand Down
Loading
Loading