Skip to content

Commit

Permalink
get audit logs endpoints fixes and doc updates (#149)
Browse files Browse the repository at this point in the history
* get audit logs endpoints fixes and doc updates

* refactor: rename audit logs db method

* rename GetAuditLogs
  • Loading branch information
irshadaj authored Oct 13, 2023
1 parent fdceaed commit ee475cf
Show file tree
Hide file tree
Showing 10 changed files with 92 additions and 174 deletions.
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 @@ -121,7 +121,7 @@ func NewV2API(cfg config.Configuration, resources v2.Resources, routerInst *rout

// Audit API
// TODO: This might actually need its own permission that's assigned to the Administrator user by default
routerInst.GET("/api/v2/audit", resources.GetAuditLogs).RequirePermissions(permissions.AuthManageUsers),
routerInst.GET("/api/v2/audit", resources.ListAuditLogs).RequirePermissions(permissions.AuthManageUsers),

// App Config API
routerInst.GET("/api/v2/config", resources.GetApplicationConfigurations).RequirePermissions(permissions.AppReadApplicationConfiguration),
Expand Down
31 changes: 5 additions & 26 deletions cmd/api/src/api/v2/apiclient/audit.go
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
// Copyright 2023 Specter Ops, Inc.
//
//
// Licensed under the Apache License, Version 2.0
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//
// http://www.apache.org/licenses/LICENSE-2.0
//
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
//
// SPDX-License-Identifier: Apache-2.0

package apiclient
Expand Down Expand Up @@ -44,28 +44,7 @@ func (s Client) GetLatestAuditLogs() (v2.AuditLogsResponse, error) {
}
}

func (s Client) GetAuditLogs(offset, limit int) (v2.AuditLogsResponse, error) {
var logs v2.AuditLogsResponse

params := url.Values{
"offset": []string{strconv.Itoa(offset)},
"limit": []string{strconv.Itoa(limit)},
}

if response, err := s.Request(http.MethodGet, "api/v2/audit", params, nil); err != nil {
return logs, err
} else {
defer response.Body.Close()

if api.IsErrorResponse(response) {
return logs, ReadAPIError(response)
}

return logs, api.ReadAPIV2ResponsePayload(&logs, response)
}
}

func (s Client) GetAuditLogsBetween(after, before time.Time, offset, limit int) (v2.AuditLogsResponse, error) {
func (s Client) ListAuditLogs(after, before time.Time, offset, limit int) (v2.AuditLogsResponse, error) {
var logs v2.AuditLogsResponse

params := url.Values{
Expand Down
20 changes: 9 additions & 11 deletions cmd/api/src/api/v2/audit.go
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
// Copyright 2023 Specter Ops, Inc.
//
//
// Licensed under the Apache License, Version 2.0
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//
// http://www.apache.org/licenses/LICENSE-2.0
//
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
//
// SPDX-License-Identifier: Apache-2.0

package v2
Expand All @@ -22,18 +22,18 @@ import (
"strings"
"time"

"github.com/specterops/bloodhound/slices"
"github.com/specterops/bloodhound/src/api"
"github.com/specterops/bloodhound/src/model"
"github.com/specterops/bloodhound/slices"
)

// AuditLogsResponse holds the data returned to an Audit logs request
type AuditLogsResponse struct {
Logs model.AuditLogs `json:"logs"`
}

// GetAuditLogs retrieves audit logs
func (s Resources) GetAuditLogs(response http.ResponseWriter, request *http.Request) {
// ListAuditLogs retrieves audit logs
func (s Resources) ListAuditLogs(response http.ResponseWriter, request *http.Request) {
var (
order []string
auditLogs model.AuditLogs
Expand Down Expand Up @@ -104,11 +104,9 @@ func (s Resources) GetAuditLogs(response http.ResponseWriter, request *http.Requ
api.WriteErrorResponse(request.Context(), ErrBadQueryParameter(request, limitQueryParam, err), response)
} else if getLogsBefore, err := ParseTimeQueryParameter(queryParams, logsBeforeQueryParam, time.Now()); err != nil {
api.WriteErrorResponse(request.Context(), ErrBadQueryParameter(request, logsBeforeQueryParam, err), response)
} else if getLogsAfter, err := ParseTimeQueryParameter(queryParams, logsAfterQueryParam, getLogsBefore.Add(-time.Hour)); err != nil {
} else if getLogsAfter, err := ParseTimeQueryParameter(queryParams, logsAfterQueryParam, getLogsBefore.Add(-time.Hour*24*365)); err != nil {
api.WriteErrorResponse(request.Context(), ErrBadQueryParameter(request, logsAfterQueryParam, err), response)
} else if logs, err := s.DB.GetAuditLogsBetween(getLogsBefore, getLogsAfter, offset, limit, strings.Join(order, ", "), sqlFilter); err != nil {
api.HandleDatabaseError(request, response, err)
} else if count, err := s.DB.GetAuditLogsCount(); err != nil {
} else if logs, count, err := s.DB.ListAuditLogs(getLogsBefore, getLogsAfter, offset, limit, strings.Join(order, ", "), sqlFilter); err != nil {
api.HandleDatabaseError(request, response, err)
} else {
api.WriteResponseWrapperWithPagination(request.Context(), AuditLogsResponse{Logs: logs}, limit, offset, count, http.StatusOK, response)
Expand Down
12 changes: 6 additions & 6 deletions cmd/api/src/api/v2/audit_integration_test.go
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
// Copyright 2023 Specter Ops, Inc.
//
//
// Licensed under the Apache License, Version 2.0
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//
// http://www.apache.org/licenses/LICENSE-2.0
//
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
//
// SPDX-License-Identifier: Apache-2.0

//go:build serial_integration
Expand All @@ -27,7 +27,7 @@ import (
"github.com/stretchr/testify/require"
)

func Test_GetAuditLogs(t *testing.T) {
func Test_ListAuditLogs(t *testing.T) {
testCtx := integration.NewContext(t, integration.StartBHServer)

t.Run("Test Getting Latest Audit Logs", func(t *testing.T) {
Expand Down Expand Up @@ -56,7 +56,7 @@ func Test_GetAuditLogs(t *testing.T) {
testCtx.DeleteAssetGroup(newAssetGroup.ID)

// Expect one audit log entry from the deletion
auditLogs := testCtx.GetAuditLogsBetween(deletionTimestamp, time.Now(), 0, 1000)
auditLogs := testCtx.ListAuditLogs(deletionTimestamp, time.Now(), 0, 1000)
require.Equal(t, 1, len(auditLogs), "Expected only 1 audit log entry but saw %d", len(auditLogs))

testCtx.AssetAuditLog(auditLogs[0], "DeleteAssetGroup", map[string]any{
Expand Down
73 changes: 22 additions & 51 deletions cmd/api/src/api/v2/audit_test.go
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
// Copyright 2023 Specter Ops, Inc.
//
//
// Licensed under the Apache License, Version 2.0
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//
// http://www.apache.org/licenses/LICENSE-2.0
//
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
//
// SPDX-License-Identifier: Apache-2.0

package v2_test
Expand All @@ -23,9 +23,9 @@ import (
"net/url"
"testing"

"github.com/specterops/bloodhound/src/api"
"github.com/specterops/bloodhound/headers"
"github.com/specterops/bloodhound/mediatypes"
"github.com/specterops/bloodhound/src/api"

"github.com/gorilla/mux"
"github.com/stretchr/testify/require"
Expand All @@ -37,7 +37,7 @@ import (
"github.com/specterops/bloodhound/src/model"
)

func TestResources_GetAuditLogs_SortingError(t *testing.T) {
func TestResources_ListAuditLogs_SortingError(t *testing.T) {
var (
mockCtrl = gomock.NewController(t)
resources = v2.Resources{}
Expand All @@ -56,7 +56,7 @@ func TestResources_GetAuditLogs_SortingError(t *testing.T) {
req.URL.RawQuery = q.Encode()

router := mux.NewRouter()
router.HandleFunc(endpoint, resources.GetAuditLogs).Methods("GET")
router.HandleFunc(endpoint, resources.ListAuditLogs).Methods("GET")

response := httptest.NewRecorder()
router.ServeHTTP(response, req)
Expand All @@ -66,7 +66,7 @@ func TestResources_GetAuditLogs_SortingError(t *testing.T) {
}
}

func TestResources_GetAuditLogs_InvalidColumn(t *testing.T) {
func TestResources_ListAuditLogs_InvalidColumn(t *testing.T) {
var (
mockCtrl = gomock.NewController(t)
resources = v2.Resources{}
Expand All @@ -85,7 +85,7 @@ func TestResources_GetAuditLogs_InvalidColumn(t *testing.T) {
req.URL.RawQuery = q.Encode()

router := mux.NewRouter()
router.HandleFunc(endpoint, resources.GetAuditLogs).Methods("GET")
router.HandleFunc(endpoint, resources.ListAuditLogs).Methods("GET")

response := httptest.NewRecorder()
router.ServeHTTP(response, req)
Expand All @@ -94,7 +94,7 @@ func TestResources_GetAuditLogs_InvalidColumn(t *testing.T) {
}
}

func TestResources_GetAuditLogs_InvalidPredicate(t *testing.T) {
func TestResources_ListAuditLogs_InvalidPredicate(t *testing.T) {
var (
mockCtrl = gomock.NewController(t)
resources = v2.Resources{}
Expand All @@ -113,7 +113,7 @@ func TestResources_GetAuditLogs_InvalidPredicate(t *testing.T) {
req.URL.RawQuery = q.Encode()

router := mux.NewRouter()
router.HandleFunc(endpoint, resources.GetAuditLogs).Methods("GET")
router.HandleFunc(endpoint, resources.ListAuditLogs).Methods("GET")

response := httptest.NewRecorder()
router.ServeHTTP(response, req)
Expand All @@ -122,7 +122,7 @@ func TestResources_GetAuditLogs_InvalidPredicate(t *testing.T) {
}
}

func TestResources_GetAuditLogs_PredicateMismatchWithColumn(t *testing.T) {
func TestResources_ListAuditLogs_PredicateMismatchWithColumn(t *testing.T) {
var (
mockCtrl = gomock.NewController(t)
resources = v2.Resources{}
Expand All @@ -141,7 +141,7 @@ func TestResources_GetAuditLogs_PredicateMismatchWithColumn(t *testing.T) {
req.URL.RawQuery = q.Encode()

router := mux.NewRouter()
router.HandleFunc(endpoint, resources.GetAuditLogs).Methods("GET")
router.HandleFunc(endpoint, resources.ListAuditLogs).Methods("GET")

response := httptest.NewRecorder()
router.ServeHTTP(response, req)
Expand All @@ -150,15 +150,15 @@ func TestResources_GetAuditLogs_PredicateMismatchWithColumn(t *testing.T) {
}
}

func TestResources_GetAuditLogs_DBError(t *testing.T) {
func TestResources_ListAuditLogs_DBError(t *testing.T) {
var (
mockCtrl = gomock.NewController(t)
mockDB = mocks.NewMockDatabase(mockCtrl)
resources = v2.Resources{DB: mockDB}
)
defer mockCtrl.Finish()

mockDB.EXPECT().GetAuditLogsBetween(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), "id, actor_name desc", model.SQLFilter{}).Return(model.AuditLogs{}, fmt.Errorf("foo"))
mockDB.EXPECT().ListAuditLogs(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), "id, actor_name desc", model.SQLFilter{}).Return(model.AuditLogs{}, 0, fmt.Errorf("foo"))

endpoint := "/api/v2/audit"

Expand All @@ -173,7 +173,7 @@ func TestResources_GetAuditLogs_DBError(t *testing.T) {
req.URL.RawQuery = q.Encode()

router := mux.NewRouter()
router.HandleFunc(endpoint, resources.GetAuditLogs).Methods("GET")
router.HandleFunc(endpoint, resources.ListAuditLogs).Methods("GET")

response := httptest.NewRecorder()
router.ServeHTTP(response, req)
Expand All @@ -182,43 +182,15 @@ func TestResources_GetAuditLogs_DBError(t *testing.T) {
}
}

func TestResources_GetAuditLogs_CountError(t *testing.T) {
var (
mockCtrl = gomock.NewController(t)
mockDB = mocks.NewMockDatabase(mockCtrl)
resources = v2.Resources{DB: mockDB}
)
defer mockCtrl.Finish()

mockDB.EXPECT().GetAuditLogsBetween(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), "", model.SQLFilter{}).Return(model.AuditLogs{}, nil)
mockDB.EXPECT().GetAuditLogsCount().Return(0, fmt.Errorf("foo"))

endpoint := "/api/v2/audit"

if req, err := http.NewRequest("GET", endpoint, nil); err != nil {
t.Fatal(err)
} else {
req.Header.Set(headers.ContentType.String(), mediatypes.ApplicationJson.String())

router := mux.NewRouter()
router.HandleFunc(endpoint, resources.GetAuditLogs).Methods("GET")

response := httptest.NewRecorder()
router.ServeHTTP(response, req)
require.Equal(t, http.StatusInternalServerError, response.Code)
}
}

func TestResources_GetAuditLogs(t *testing.T) {
func TestResources_ListAuditLogs(t *testing.T) {
var (
mockCtrl = gomock.NewController(t)
mockDB = mocks.NewMockDatabase(mockCtrl)
resources = v2.Resources{DB: mockDB}
)
defer mockCtrl.Finish()

mockDB.EXPECT().GetAuditLogsBetween(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), "id, actor_name desc", model.SQLFilter{}).Return(model.AuditLogs{}, nil)
mockDB.EXPECT().GetAuditLogsCount().Return(1000, nil)
mockDB.EXPECT().ListAuditLogs(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), "id, actor_name desc", model.SQLFilter{}).Return(model.AuditLogs{}, 1000, nil)

endpoint := "/api/v2/audit"

Expand All @@ -233,24 +205,23 @@ func TestResources_GetAuditLogs(t *testing.T) {
req.URL.RawQuery = q.Encode()

router := mux.NewRouter()
router.HandleFunc(endpoint, resources.GetAuditLogs).Methods("GET")
router.HandleFunc(endpoint, resources.ListAuditLogs).Methods("GET")

response := httptest.NewRecorder()
router.ServeHTTP(response, req)
require.Equal(t, http.StatusOK, response.Code)
}
}

func TestResources_GetAuditLogs_Filtered(t *testing.T) {
func TestResources_ListAuditLogs_Filtered(t *testing.T) {
var (
mockCtrl = gomock.NewController(t)
mockDB = mocks.NewMockDatabase(mockCtrl)
resources = v2.Resources{DB: mockDB}
)
defer mockCtrl.Finish()

mockDB.EXPECT().GetAuditLogsBetween(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), "", model.SQLFilter{SQLString: "actor_name = ?", Params: []any{"foo"}}).Return(model.AuditLogs{}, nil)
mockDB.EXPECT().GetAuditLogsCount().Return(1000, nil)
mockDB.EXPECT().ListAuditLogs(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), "", model.SQLFilter{SQLString: "actor_name = ?", Params: []any{"foo"}}).Return(model.AuditLogs{}, 1000, nil)
endpoint := "/api/v2/audit"

if req, err := http.NewRequest("GET", endpoint, nil); err != nil {
Expand All @@ -263,7 +234,7 @@ func TestResources_GetAuditLogs_Filtered(t *testing.T) {
req.URL.RawQuery = q.Encode()

router := mux.NewRouter()
router.HandleFunc(endpoint, resources.GetAuditLogs).Methods("GET")
router.HandleFunc(endpoint, resources.ListAuditLogs).Methods("GET")

response := httptest.NewRecorder()
router.ServeHTTP(response, req)
Expand Down
Loading

0 comments on commit ee475cf

Please sign in to comment.