Skip to content

Commit

Permalink
Audit Log Refactor (#345)
Browse files Browse the repository at this point in the history
* feat: Initial migration and model updates for AuditLog improvements

* feat: populate actor_email for existing audit_logs

* chore: Fix audit log docs to show the actual response object instead of a generic response

* feat: Log Requester IP for source and actor email with audit logs

* wip: createUser audit logging via middleware

* chore: refactor audit logging to trigger from LoggingMiddleware based on the presence of specific context

This commit mostly contains the logic to make this method for audit logging work. The only audit log
event that works in this commit is CreateUser

* wip: Successfully creating audit logs from middleware. Capturing failure cases.

* feat: Capture DB errors in audit logs

* chore: remove debug logging

* chore: Refactor setting of status to receiver function

* wip: All "simple" audit context endpoints updated for middeware

* fix: Fix a few bugs found during testing

* fix: Remove doc for non-existent POST /api/v2/saml endpoint

* wip: Refactor to simplify setting of AuditCtx in endpoints

* fix: registration issue after rebase

* tasks 6 and 7 for #345 (#357)

* updated IP address parsing

* added tests for IP parsing, changed IP delimiter to comma

* redo IP parsing for changed requirements (#362)

* feat: audit log steel thread (#364)

* chore: clean up old approach to reduce noise

* chore: cleanup audit logging interface for new usages

* chore: fix existing tests (audit integration test needs new target)

* chore: improve integration config defaults

* feat: add audit logging to UpdateUser

This adds a new `AuditableContext` method to the Database, which wraps the Gorm Transaction method with some auditing context and ensures the two phase commit of the audit log is run

* feat: add audit logging to DeleteSAMLProvider
fix: use actual AuditData instead of the direct model

* feat: CreateAssetGroup and DeleteAssetGroup are now back to auditable
fix: broken tests

* feat: plumb commitID for real this time
fix: incorrect formatting of audit log action names
fix: additional test fixes

* fix: additional test fixes

* review comments

---------

Co-authored-by: Irshad Ahmed <iahmed@specterops.io>

* chore: steel thread cleanup (#365)

* BED-3858 - Fix not ingesting tenant count during azure analysis (#328)

Co-authored-by: Irshad Ahmed <iahmed@specterops.io>

* ESC9a Edge Composition (#354)

* feat: esc9a post

* test: add esc9 test

* chore: add harness files

* fix: regen schema after merge

* chore: fix small nits

* chore: cleanup cert template new function

* chore: add missing props

* wip: 9a composition

* fix: treat failure to grab properties as true

* wip: esc9a composition

* wip: esc9a composition

* feat+chore: add depth controls to dawgs patterns

* wip: esc9a composition

* fix: do not drop the current segment if the next pattern is optional

* wip: esc9a composition

* fix: update other continuations to respect depth correctly

* wip: edge comp

* fix: swap

* chore: remove unnecessary logs

* feat: esc9a post

* test: add esc9 test

* chore: fix small nits

* wip: 9a composition

* wip: esc9a composition

* wip: esc9a composition

* feat+chore: add depth controls to dawgs patterns

* wip: esc9a composition

* fix: do not drop the current segment if the next pattern is optional

* wip: esc9a composition

* fix: update other continuations to respect depth correctly

* wip: edge comp

* fix: swap

* chore: remove unnecessary logs

* test: add test covering esc9a edge comp

* chore: revert random re-ordering

* chore: handle negative min/max depth on continuations

---------

Co-authored-by: John Hopper <jhopper@specterops.io>

* docs: Add to ESC3 abuse info (#350)

* docs: add note in ESC6 abuse info (#356)

* feat: Add ADCS pre-built queries (#342)

Co-authored-by: Rohan Vazarkar <rvazarkar@users.noreply.github.com>

* feat: esc6a edge composition (#359)

* feat: esc6a edge composition

* chore: allow composition accordion to show for 6a

* fix: add trustedby rel to path4 pattern, use outboundwithdepth for optional memberof traversal

* chore: update dcfor pattern to use outboundwithdepth for optional group membership

* ESC10a Post Processing (#360)

* wip: initial ESC10a post

* test: all the tests for esc10a

* chore: add edges to post processed

* chore: add harnessgen script

* test: remove edges from harness

* chore: don't exit loop if we hit an error, continue instead

* chore: log and continue

* feat: filter out ESC3 false positives (#351)

* feat: filter out ESC3 false positives

* fix: handle esc3 filtering without retraversal

* fix: handle esc3 filtering without retraversal

* fix: handle esc3 filtering without retraversal

* chore: rename function for re-use

* chore: log and continue

---------

Co-authored-by: rvazarkar <rvazarkar@specterops.io>
Co-authored-by: Rohan Vazarkar <rvazarkar@users.noreply.github.com>

* chore: patch EULAAcceptance bypass to only run if the current user is set to false

* fix: incorrect usage of RemoteAddr
fix: unnecessary AuditData() calls
fix: use pointers for AuditEntry.Model assignments so successful actions can record updated fields like ID

---------

Co-authored-by: mistahj67 <26472282+mistahj67@users.noreply.github.com>
Co-authored-by: Irshad Ahmed <iahmed@specterops.io>
Co-authored-by: Rohan Vazarkar <rvazarkar@users.noreply.github.com>
Co-authored-by: John Hopper <jhopper@specterops.io>
Co-authored-by: Jonas Bülow Knudsen <12843299+JonasBK@users.noreply.github.com>
Co-authored-by: Ulises Rangel <urangel@specterops.io>
Co-authored-by: rvazarkar <rvazarkar@specterops.io>

* add eula_accepted to user audit data (#371)

* audit logs for auth tokens and secrets (#372)

* audit logs for auth tokens and secrets

* feat: UpdateAssetGroup, UpdateAssetGroupSelector, DeleteAssetGroupSelector, CreateSAMLIdentityProvider, and UpdateSAMLIdentityProvider have audit log support (#374)

chore: remove unused RemoveAssetGroupSelector method
chore: update tests and mocks to account for interface changes

* more auth handlers for audit log (#373)

* chore: update documentation for our new commit_id field and `intent` status

* Generate audit log entries on unauthorized access attempts (#375)

* feat: create an audit log record when unauthorized access is attempted

* chore: Only log unauthorized write access

* chore: add audit logging to remaining auth middleware

* test: Fix middleware/auth_test to support new audit log changes

* chore: change source column to TEXT

It was previously sized to only account for max 1 IPv6 address, but
our design changes mean it could now be any length

* chore: rename audit_log.source as 'source' is a reserved keyword in postgres

* chore: add additional fields to user.AuditData

* Refactored unauthorized access audit logging: (#381)

* Refactored unauthorized access audit logging:
- moved audit logging responsibility to Authorizer interface
- cleaned up injections of *db through several layers of code (no longer necessary)
- minor cleanup and support work


---------

Co-authored-by: Alyx Holms <aholms@specterops.io>
Co-authored-by: Irshad Ajmal Ahmed <iahmed@specterops.io>
Co-authored-by: mistahj67 <26472282+mistahj67@users.noreply.github.com>
Co-authored-by: Rohan Vazarkar <rvazarkar@users.noreply.github.com>
Co-authored-by: John Hopper <jhopper@specterops.io>
Co-authored-by: Jonas Bülow Knudsen <12843299+JonasBK@users.noreply.github.com>
Co-authored-by: Ulises Rangel <urangel@specterops.io>
Co-authored-by: rvazarkar <rvazarkar@specterops.io>
Co-authored-by: Cody Bentley <cbentley@specterops.io>
  • Loading branch information
10 people authored Feb 5, 2024
1 parent cf13087 commit f14442a
Show file tree
Hide file tree
Showing 36 changed files with 852 additions and 616 deletions.
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 {
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

0 comments on commit f14442a

Please sign in to comment.