-
Notifications
You must be signed in to change notification settings - Fork 109
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Addressed PR feedback and fixed unit/integration tests
- Loading branch information
1 parent
69e5213
commit aa2308c
Showing
8 changed files
with
1,912 additions
and
1,955 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
} | ||
} | ||
} | ||
} |
Oops, something went wrong.