Skip to content

Commit

Permalink
Refactored logic, added unit/integration tests, handled merge conflicts
Browse files Browse the repository at this point in the history
  • Loading branch information
ALCooper12 committed Aug 20, 2024
1 parent 240ae87 commit f95108b
Show file tree
Hide file tree
Showing 10 changed files with 2,675 additions and 20 deletions.
1 change: 1 addition & 0 deletions cmd/api/src/api/registration/v2.go
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ func NewV2API(cfg config.Configuration, resources v2.Resources, routerInst *rout
routerInst.POST("/api/v2/saved-queries", resources.CreateSavedQuery).RequirePermissions(permissions.SavedQueriesWrite),
routerInst.PUT(fmt.Sprintf("/api/v2/saved-queries/{%s}", api.URIPathVariableSavedQueryID), resources.UpdateSavedQuery).RequirePermissions(permissions.SavedQueriesWrite),
routerInst.DELETE(fmt.Sprintf("/api/v2/saved-queries/{%s}", api.URIPathVariableSavedQueryID), resources.DeleteSavedQuery).RequirePermissions(permissions.SavedQueriesWrite),
routerInst.PUT(fmt.Sprintf("/api/v2/saved-queries/{%s}/share", api.URIPathVariableSavedQueryID), resources.ShareSavedQueries).RequirePermissions(permissions.SavedQueriesWrite),

// Azure Entity API
routerInst.GET("/api/v2/azure/{entity_type}", resources.GetAZEntity).RequirePermissions(permissions.GraphDBRead),
Expand Down
232 changes: 232 additions & 0 deletions cmd/api/src/api/v2/saved_queries.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"strconv"
"strings"

"github.com/gofrs/uuid"
"github.com/gorilla/mux"
"github.com/specterops/bloodhound/src/api"
"github.com/specterops/bloodhound/src/auth"
Expand Down Expand Up @@ -256,3 +257,234 @@ func (s Resources) DeleteSavedQuery(response http.ResponseWriter, request *http.

}
}

type ShareSavedQueriesResponse []model.SavedQueriesPermissions

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

// 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(), int64(savedQueryID), user.ID); err != nil {
api.HandleDatabaseError(request, response, err)
} else if isSavedQueryShared, err := s.DB.IsSavedQueryShared(request.Context(), int64(savedQueryID)); err != nil {
api.HandleDatabaseError(request, response, err)
} else {
isAdmin := user.Roles.Has(model.Role{Name: auth.RoleAdministrator})

if isAdmin {
// Query set to public
if createRequest.Public {
if savedQueryBelongsToUser {
if dbSavedQueryScope[model.SavedQueryScopePublic] {
response.WriteHeader(http.StatusNoContent)
} else {
if isSavedQueryShared {
if savedPermission, err := s.DB.CreateSavedQueryPermissionToPublic(request.Context(), int64(savedQueryID)); err != nil {
api.HandleDatabaseError(request, response, err)
} else if savedQueryPermissions, err := s.DB.GetPermissionsForSavedQuery(request.Context(), int64(savedQueryID)); err != nil {
api.HandleDatabaseError(request, response, err)
} else {
for _, permission := range savedQueryPermissions {
sharedToUserID := permission.SharedToUserID

if err := s.DB.DeleteSavedQueryPermissionsForUser(request.Context(), int64(savedQueryID), sharedToUserID.UUID); err != nil {
api.HandleDatabaseError(request, response, err)
}
}
api.WriteBasicResponse(request.Context(), ShareSavedQueriesResponse{savedPermission}, http.StatusCreated, response)
}
} else {
if savedPermission, err := s.DB.CreateSavedQueryPermissionToPublic(request.Context(), int64(savedQueryID)); err != nil {
api.HandleDatabaseError(request, response, err)
} else {
api.WriteBasicResponse(request.Context(), ShareSavedQueriesResponse{savedPermission}, http.StatusCreated, response)
}
}
}
} else {
if dbSavedQueryScope[model.SavedQueryScopePublic] {
response.WriteHeader(http.StatusNoContent)
} else {
api.WriteErrorResponse(request.Context(), api.BuildErrorResponse(http.StatusForbidden, api.ErrorResponseDetailsForbidden, request), response)
return
}
}
// Query set to private
} else if len(createRequest.UserIDs) == 0 {
if savedQueryBelongsToUser {
if dbSavedQueryScope[model.SavedQueryScopePublic] {
if err := s.DB.DeleteSavedQueryPermissionPublic(request.Context(), int64(savedQueryID)); err != nil {
api.HandleDatabaseError(request, response, err)
} else {
response.WriteHeader(http.StatusNoContent)
}
} else {
if isSavedQueryShared {
if savedQueryPermissions, err := s.DB.GetPermissionsForSavedQuery(request.Context(), int64(savedQueryID)); err != nil {
api.HandleDatabaseError(request, response, err)
} else {
for _, permission := range savedQueryPermissions {
sharedToUserID := permission.SharedToUserID

if err := s.DB.DeleteSavedQueryPermissionsForUser(request.Context(), int64(savedQueryID), sharedToUserID.UUID); err != nil {
api.HandleDatabaseError(request, response, err)
}
}
response.WriteHeader(http.StatusNoContent)
}
} else {
response.WriteHeader(http.StatusNoContent)
}
}
} else {
if dbSavedQueryScope[model.SavedQueryScopePublic] {
if err := s.DB.DeleteSavedQueryPermissionPublic(request.Context(), int64(savedQueryID)); err != nil {
api.HandleDatabaseError(request, response, err)
} else {
response.WriteHeader(http.StatusNoContent)
}
} else {
api.WriteErrorResponse(request.Context(), api.BuildErrorResponse(http.StatusForbidden, api.ErrorResponseDetailsForbidden, request), response)
return
}
}
// Sharing a query
} else if len(createRequest.UserIDs) > 0 && !createRequest.Public {
if savedQueryBelongsToUser {
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 {
var newPermissions []model.SavedQueriesPermissions
for _, sharedUserID := range createRequest.UserIDs {
if sharedUserID != user.ID {
newPermissions = append(newPermissions, model.SavedQueriesPermissions{
QueryID: int64(savedQueryID),
Public: false,
SharedToUserID: database.NullUUID(sharedUserID),
})
} else {
api.WriteErrorResponse(request.Context(), api.BuildErrorResponse(http.StatusBadRequest, "Cannot share query to self", request), response)
return
}
}
// Save the permissions to the database
if savedPermissions, err := s.DB.CreateSavedQueryPermissionsBatch(request.Context(), newPermissions); err != nil {
api.HandleDatabaseError(request, response, err)
} else {
api.WriteBasicResponse(request.Context(), savedPermissions, http.StatusCreated, response)
}
}
} else {
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 {
api.WriteErrorResponse(request.Context(), api.BuildErrorResponse(http.StatusForbidden, api.ErrorResponseDetailsForbidden, request), response)
return
}
}
}
} else if !isAdmin {
if !savedQueryBelongsToUser {
api.WriteErrorResponse(request.Context(), api.BuildErrorResponse(http.StatusForbidden, api.ErrorResponseDetailsForbidden, request), response)
return
// Query set to public
} else if createRequest.Public {
if dbSavedQueryScope[model.SavedQueryScopePublic] {
response.WriteHeader(http.StatusNoContent)
} else {
if isSavedQueryShared {
if savedPermission, err := s.DB.CreateSavedQueryPermissionToPublic(request.Context(), int64(savedQueryID)); err != nil {
api.HandleDatabaseError(request, response, err)
} else if savedQueryPermissions, err := s.DB.GetPermissionsForSavedQuery(request.Context(), int64(savedQueryID)); err != nil {
api.HandleDatabaseError(request, response, err)
} else {
for _, permission := range savedQueryPermissions {
sharedToUserID := permission.SharedToUserID

if err := s.DB.DeleteSavedQueryPermissionsForUser(request.Context(), int64(savedQueryID), sharedToUserID.UUID); err != nil {
api.HandleDatabaseError(request, response, err)
}
}
api.WriteBasicResponse(request.Context(), ShareSavedQueriesResponse{savedPermission}, http.StatusCreated, response)
}
} else {
if savedPermission, err := s.DB.CreateSavedQueryPermissionToPublic(request.Context(), int64(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 dbSavedQueryScope[model.SavedQueryScopePublic] {
api.WriteErrorResponse(request.Context(), api.BuildErrorResponse(http.StatusForbidden, api.ErrorResponseDetailsForbidden, request), response)
return
} else {
if isSavedQueryShared {
if savedQueryPermissions, err := s.DB.GetPermissionsForSavedQuery(request.Context(), int64(savedQueryID)); err != nil {
api.HandleDatabaseError(request, response, err)
} else {
for _, permission := range savedQueryPermissions {
sharedToUserID := permission.SharedToUserID

if err := s.DB.DeleteSavedQueryPermissionsForUser(request.Context(), int64(savedQueryID), sharedToUserID.UUID); err != nil {
api.HandleDatabaseError(request, response, err)
}
}
response.WriteHeader(http.StatusNoContent)
}
} 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.StatusForbidden, api.ErrorResponseDetailsForbidden, request), response)
return
} else {
var newPermissions []model.SavedQueriesPermissions
for _, sharedUserID := range createRequest.UserIDs {
if sharedUserID != user.ID {
newPermissions = append(newPermissions, model.SavedQueriesPermissions{
QueryID: int64(savedQueryID),
Public: false,
SharedToUserID: database.NullUUID(sharedUserID),
})
} else {
api.WriteErrorResponse(request.Context(), api.BuildErrorResponse(http.StatusBadRequest, "Cannot share query to self", request), response)
return
}
}
// Save the permissions to the database
if savedPermissions, err := s.DB.CreateSavedQueryPermissionsBatch(request.Context(), newPermissions); err != nil {
api.HandleDatabaseError(request, response, err)
} else {
api.WriteBasicResponse(request.Context(), savedPermissions, http.StatusCreated, response)
}
}
}
}
}
}
Loading

0 comments on commit f95108b

Please sign in to comment.