Skip to content

Commit

Permalink
Addressed PR feedback and fixed unit/integration tests
Browse files Browse the repository at this point in the history
  • Loading branch information
ALCooper12 committed Aug 23, 2024
1 parent 69e5213 commit aa2308c
Show file tree
Hide file tree
Showing 8 changed files with 1,912 additions and 1,955 deletions.
1,806 changes: 9 additions & 1,797 deletions cmd/api/src/api/v2/saved_queries_test.go

Large diffs are not rendered by default.

139 changes: 139 additions & 0 deletions cmd/api/src/api/v2/savedqueriespermissions.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
// Copyright 2024 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

import (
"errors"
"net/http"
"strconv"

"github.com/gofrs/uuid"
"github.com/gorilla/mux"
"github.com/specterops/bloodhound/src/api"
"github.com/specterops/bloodhound/src/auth"
ctx2 "github.com/specterops/bloodhound/src/ctx"
"github.com/specterops/bloodhound/src/database"
"github.com/specterops/bloodhound/src/model"
)

type ShareSavedQueriesResponse []model.SavedQueriesPermissions

type SavedQueryPermissionRequest struct {
UserIDs []uuid.UUID `json:"user_ids"`
Public bool `json:"public"`
}

var (
ErrInvalidSelfShare = errors.New("invalidSelfShare")
ErrForbidden = errors.New("forbidden")
ErrInvalidPublicShare = errors.New("inavlidPublicShare")
)

func CanUpdateSavedQueriesPermission(user model.User, savedQueryBelongsToUser bool, createRequest SavedQueryPermissionRequest, dbSavedQueryScope database.SavedQueryScopeMap) error {
if user.Roles.Has(model.Role{Name: auth.RoleAdministrator}) {
if createRequest.Public && savedQueryBelongsToUser {
return nil
} else if len(createRequest.UserIDs) == 0 && (savedQueryBelongsToUser || dbSavedQueryScope[model.SavedQueryScopePublic]) {
return nil
} else if len(createRequest.UserIDs) > 0 && !createRequest.Public {
if dbSavedQueryScope[model.SavedQueryScopePublic] {
return ErrInvalidPublicShare
}
if savedQueryBelongsToUser {
for _, sharedUserID := range createRequest.UserIDs {
if sharedUserID == user.ID {
return ErrInvalidSelfShare
}
}
return nil
}
}
} else if savedQueryBelongsToUser && !dbSavedQueryScope[model.SavedQueryScopePublic] {
if len(createRequest.UserIDs) > 0 && !createRequest.Public {
for _, sharedUserID := range createRequest.UserIDs {
if sharedUserID == user.ID {
return ErrInvalidSelfShare
}
}
}
return nil
}
return ErrForbidden
}

// ShareSavedQueries allows a user to share queries between users, as well as share them publicly
func (s Resources) ShareSavedQueries(response http.ResponseWriter, request *http.Request) {
var (
rawSavedQueryID = mux.Vars(request)[api.URIPathVariableSavedQueryID]
createRequest SavedQueryPermissionRequest
)

if user, isUser := auth.GetUserFromAuthCtx(ctx2.FromRequest(request).AuthCtx); !isUser {
api.WriteErrorResponse(request.Context(), api.BuildErrorResponse(http.StatusBadRequest, "No associated user found", request), response)
} else if savedQueryID, err := strconv.ParseInt(rawSavedQueryID, 10, 64); err != nil {
api.WriteErrorResponse(request.Context(), api.BuildErrorResponse(http.StatusBadRequest, api.ErrorResponseDetailsIDMalformed, request), response)
} else if err := api.ReadJSONRequestPayloadLimited(&createRequest, request); err != nil {
api.WriteErrorResponse(request.Context(), api.BuildErrorResponse(http.StatusBadRequest, err.Error(), request), response)
} else if createRequest.Public && len(createRequest.UserIDs) > 0 {
api.WriteErrorResponse(request.Context(), api.BuildErrorResponse(http.StatusBadRequest, "Public cannot be true while user_ids is populated", request), response)
} else if savedQueryBelongsToUser, err := s.DB.SavedQueryBelongsToUser(request.Context(), user.ID, savedQueryID); errors.Is(err, database.ErrNotFound) {
api.WriteErrorResponse(request.Context(), api.BuildErrorResponse(http.StatusNotFound, "Query does not exist", request), response)
} else if err != nil {
api.HandleDatabaseError(request, response, err)
} else if dbSavedQueryScope, err := s.DB.GetScopeForSavedQuery(request.Context(), savedQueryID, user.ID); err != nil {
api.HandleDatabaseError(request, response, err)
} else if err := CanUpdateSavedQueriesPermission(user, savedQueryBelongsToUser, createRequest, dbSavedQueryScope); err != nil {
if errors.Is(err, ErrInvalidSelfShare) {
api.WriteErrorResponse(request.Context(), api.BuildErrorResponse(http.StatusBadRequest, "Cannot share query to self", request), response)
} else if errors.Is(err, ErrInvalidPublicShare) {
api.WriteErrorResponse(request.Context(), api.BuildErrorResponse(http.StatusBadRequest, "Public query cannot be shared to users. You must set your query to private first", request), response)
} else {
api.WriteErrorResponse(request.Context(), api.BuildErrorResponse(http.StatusForbidden, api.ErrorResponseDetailsForbidden, request), response)
}
} else {
// Query set to public
if createRequest.Public {
if dbSavedQueryScope[model.SavedQueryScopePublic] {
response.WriteHeader(http.StatusNoContent)
} else {
if savedPermission, err := s.DB.CreateSavedQueryPermissionToPublic(request.Context(), savedQueryID); err != nil {
api.HandleDatabaseError(request, response, err)
} else {
api.WriteBasicResponse(request.Context(), ShareSavedQueriesResponse{savedPermission}, http.StatusCreated, response)
}
}
// Query set to private
} else if len(createRequest.UserIDs) == 0 {
if err := s.DB.DeleteSavedQueryPermissionsForUsers(request.Context(), savedQueryID); err != nil {
api.HandleDatabaseError(request, response, err)
} else {
response.WriteHeader(http.StatusNoContent)
}
// Sharing a query
} else if len(createRequest.UserIDs) > 0 && !createRequest.Public {
if dbSavedQueryScope[model.SavedQueryScopePublic] {
api.WriteErrorResponse(request.Context(), api.BuildErrorResponse(http.StatusBadRequest, "Public query cannot be shared to users. You must set your query to private first", request), response)
} else {
if savedPermissions, err := s.DB.CreateSavedQueryPermissionsToUsers(request.Context(), savedQueryID, createRequest.UserIDs...); err != nil {
api.HandleDatabaseError(request, response, err)
} else {
api.WriteBasicResponse(request.Context(), savedPermissions, http.StatusCreated, response)
}
}
}
}
}
Loading

0 comments on commit aa2308c

Please sign in to comment.