Skip to content

Commit

Permalink
Working on permissions for bulk deletion
Browse files Browse the repository at this point in the history
  • Loading branch information
diamondap committed Jan 22, 2024
1 parent 58d4bbd commit 667d82f
Show file tree
Hide file tree
Showing 5 changed files with 105 additions and 52 deletions.
3 changes: 3 additions & 0 deletions constants/permissions.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ const (
InstitutionRead = "InstitutionRead"
InstitutionUpdate = "InstitutionUpdate"
InstitutionUpdatePrefs = "InstitutionUpdatePrefs"
IntellectualObjectBatchDelete = "IntellectualObjectBatchDelete"
IntellectualObjectCreate = "IntellectualObjectCreate"
IntellectualObjectDelete = "IntellectualObjectDelete"
IntellectualObjectFinishBulkDelete = "IntellectualObjectFinishBulkDelete"
Expand Down Expand Up @@ -124,6 +125,7 @@ var Permissions = []Permission{
InstitutionRead,
InstitutionUpdate,
InstitutionUpdatePrefs,
IntellectualObjectBatchDelete,
IntellectualObjectCreate,
IntellectualObjectDelete,
IntellectualObjectFinishBulkDelete,
Expand Down Expand Up @@ -311,6 +313,7 @@ func initPermissions() {
sysAdmin[InstitutionRead] = true
sysAdmin[InstitutionUpdate] = true
sysAdmin[InstitutionUpdatePrefs] = true
sysAdmin[IntellectualObjectBatchDelete] = true
sysAdmin[IntellectualObjectCreate] = true
sysAdmin[IntellectualObjectDelete] = true // preserv workers do this with sys admin account
sysAdmin[IntellectualObjectFinishBulkDelete] = true // not implemented yet
Expand Down
97 changes: 49 additions & 48 deletions middleware/authorization_map.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,54 +31,55 @@ type AuthMetadata struct {
// requests that hit an unguarded route will return an internal server
// error.
var AuthMap = map[string]AuthMetadata{
"AlertCreate": {"Alert", constants.AlertCreate},
"AlertDelete": {"Alert", constants.AlertDelete},
"AlertIndex": {"Alert", constants.AlertRead},
"AlertNew": {"Alert", constants.AlertCreate},
"AlertShow": {"Alert", constants.AlertRead},
"AlertUpdate": {"Alert", constants.AlertUpdate},
"AlertMarkAsReadXHR": {"Alert", constants.AlertUpdate},
"AlertMarkAllAsRead": {"Alert", constants.AlertUpdate},
"AlertMarkAsUnreadXHR": {"Alert", constants.AlertUpdate},
"BillingReportShow": {"DepositStats", constants.BillingReportShow},
"ChecksumCreate": {"Checksum", constants.ChecksumCreate},
"ChecksumDelete": {"Checksum", constants.ChecksumDelete},
"ChecksumIndex": {"Checksum", constants.ChecksumRead},
"ChecksumNew": {"Checksum", constants.ChecksumCreate},
"ChecksumShow": {"Checksum", constants.ChecksumRead},
"ChecksumUpdate": {"Checksum", constants.ChecksumUpdate},
"DashboardShow": {"Dashboard", constants.DashboardShow},
"DeletionRequestApprove": {"DeletionRequest", constants.DeletionRequestApprove},
"DeletionRequestCancel": {"DeletionRequest", constants.DeletionRequestApprove},
"DeletionRequestIndex": {"DeletionRequest", constants.DeletionRequestList},
"DeletionRequestReview": {"DeletionRequest", constants.DeletionRequestApprove},
"DeletionRequestShow": {"DeletionRequest", constants.DeletionRequestShow},
"DepositReportShow": {"DepositStats", constants.DepositReportShow},
"GenericFileCreate": {"GenericFile", constants.FileCreate},
"GenericFileCreateBatch": {"GenericFile", constants.FileCreate},
"GenericFileDelete": {"GenericFile", constants.FileDelete},
"GenericFileFinishBulkDelete": {"GenericFile", constants.FileFinishBulkDelete},
"GenericFileIndex": {"GenericFile", constants.FileRead},
"GenericFileInitDelete": {"GenericFile", constants.FileRequestDelete},
"GenericFileInitRestore": {"GenericFile", constants.FileRestore},
"GenericFileNew": {"GenericFile", constants.FileCreate},
"GenericFileRequestDelete": {"GenericFile", constants.FileRequestDelete},
"GenericFileRequestRestore": {"GenericFile", constants.FileRestore},
"GenericFileShow": {"GenericFile", constants.FileRead},
"GenericFileUpdate": {"GenericFile", constants.FileUpdate},
"InstitutionCreate": {"Institution", constants.InstitutionCreate},
"InstitutionDelete": {"Institution", constants.InstitutionDelete},
"InstitutionEdit": {"Institution", constants.InstitutionUpdate},
"InstitutionEditPrefs": {"Institution", constants.InstitutionUpdatePrefs},
"InstitutionIndex": {"Institution", constants.InstitutionList},
"InstitutionNew": {"Institution", constants.InstitutionCreate},
"InstitutionShow": {"Institution", constants.InstitutionRead},
"InstitutionUndelete": {"Institution", constants.InstitutionUpdate},
"InstitutionUpdate": {"Institution", constants.InstitutionUpdate},
"InstitutionUpdatePrefs": {"Institution", constants.InstitutionUpdatePrefs},
"IntellectualObjectCreate": {"IntellectualObject", constants.IntellectualObjectCreate},
"IntellectualObjectDelete": {"IntellectualObject", constants.IntellectualObjectDelete},
"IntellectualObjectEvents": {"PremisEvent", constants.EventRead},
"AlertCreate": {"Alert", constants.AlertCreate},
"AlertDelete": {"Alert", constants.AlertDelete},
"AlertIndex": {"Alert", constants.AlertRead},
"AlertNew": {"Alert", constants.AlertCreate},
"AlertShow": {"Alert", constants.AlertRead},
"AlertUpdate": {"Alert", constants.AlertUpdate},
"AlertMarkAsReadXHR": {"Alert", constants.AlertUpdate},
"AlertMarkAllAsRead": {"Alert", constants.AlertUpdate},
"AlertMarkAsUnreadXHR": {"Alert", constants.AlertUpdate},
"BillingReportShow": {"DepositStats", constants.BillingReportShow},
"ChecksumCreate": {"Checksum", constants.ChecksumCreate},
"ChecksumDelete": {"Checksum", constants.ChecksumDelete},
"ChecksumIndex": {"Checksum", constants.ChecksumRead},
"ChecksumNew": {"Checksum", constants.ChecksumCreate},
"ChecksumShow": {"Checksum", constants.ChecksumRead},
"ChecksumUpdate": {"Checksum", constants.ChecksumUpdate},
"DashboardShow": {"Dashboard", constants.DashboardShow},
"DeletionRequestApprove": {"DeletionRequest", constants.DeletionRequestApprove},
"DeletionRequestCancel": {"DeletionRequest", constants.DeletionRequestApprove},
"DeletionRequestIndex": {"DeletionRequest", constants.DeletionRequestList},
"DeletionRequestReview": {"DeletionRequest", constants.DeletionRequestApprove},
"DeletionRequestShow": {"DeletionRequest", constants.DeletionRequestShow},
"DepositReportShow": {"DepositStats", constants.DepositReportShow},
"GenericFileCreate": {"GenericFile", constants.FileCreate},
"GenericFileCreateBatch": {"GenericFile", constants.FileCreate},
"GenericFileDelete": {"GenericFile", constants.FileDelete},
"GenericFileFinishBulkDelete": {"GenericFile", constants.FileFinishBulkDelete},
"GenericFileIndex": {"GenericFile", constants.FileRead},
"GenericFileInitDelete": {"GenericFile", constants.FileRequestDelete},
"GenericFileInitRestore": {"GenericFile", constants.FileRestore},
"GenericFileNew": {"GenericFile", constants.FileCreate},
"GenericFileRequestDelete": {"GenericFile", constants.FileRequestDelete},
"GenericFileRequestRestore": {"GenericFile", constants.FileRestore},
"GenericFileShow": {"GenericFile", constants.FileRead},
"GenericFileUpdate": {"GenericFile", constants.FileUpdate},
"InstitutionCreate": {"Institution", constants.InstitutionCreate},
"InstitutionDelete": {"Institution", constants.InstitutionDelete},
"InstitutionEdit": {"Institution", constants.InstitutionUpdate},
"InstitutionEditPrefs": {"Institution", constants.InstitutionUpdatePrefs},
"InstitutionIndex": {"Institution", constants.InstitutionList},
"InstitutionNew": {"Institution", constants.InstitutionCreate},
"InstitutionShow": {"Institution", constants.InstitutionRead},
"InstitutionUndelete": {"Institution", constants.InstitutionUpdate},
"InstitutionUpdate": {"Institution", constants.InstitutionUpdate},
"InstitutionUpdatePrefs": {"Institution", constants.InstitutionUpdatePrefs},
"IntellectualObjectInitBatchDelete": {"IntellectualObject", constants.IntellectualObjectBatchDelete},
"IntellectualObjectCreate": {"IntellectualObject", constants.IntellectualObjectCreate},
"IntellectualObjectDelete": {"IntellectualObject", constants.IntellectualObjectDelete},
"IntellectualObjectEvents": {"PremisEvent", constants.EventRead},
// IntellectualObjectFiles gets an object ID and will look up that object to check
// it's institution. The permission, however, is FileReade, because this endpoint
// returns files. https://trello.com/c/n5asx3bj
Expand Down
10 changes: 9 additions & 1 deletion web/api/admin/intellectual_objects_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,15 @@ func IntellectualObjectInitBatchDelete(c *gin.Context) {
if api.AbortIfError(c, err) {
return
}
del, err := webui.NewDeletionForObjectBatch(institutionID, objectIDs, req.CurrentUser, req.BaseURL())
requestorID, err := strconv.ParseInt(c.Request.PostFormValue("requestorID"), 10, 64)
if api.AbortIfError(c, err) {
return
}

common.Context().Log.Warn().Msgf("Creating batch deletion request on behalf of user %d for %d objects belonging to institution %d. Current user is %s.",
requestorID, len(objectIDs), institutionID, req.CurrentUser.Email)

del, err := webui.NewDeletionForObjectBatch(requestorID, institutionID, objectIDs, req.BaseURL())
if api.AbortIfError(c, err) {
return
}
Expand Down
32 changes: 32 additions & 0 deletions web/api/admin/intellectual_objects_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -291,3 +291,35 @@ func TestObjectInitRestore(t *testing.T) {
WithHeader(constants.APIKeyHeader, "password").
Expect().Status(http.StatusForbidden)
}

func TestObjectBatchDelete(t *testing.T) {
// START HERE

// Test permissions. Only APTrust admin should be allowed to do this.

// Ensure that we get failure if we include an object
// with a pending WorkItem.

// Ensure that we get failure if we include an object
// that belongs to another institution.

// Ensure that we get failure if requestorID belongs
// to an inst user rather than inst admin.

// Ensure we get success with valid params:
// inst id, user id, object ids.

// Check post conditions. There should be a deletion
// request with all expected properties and with the
// right list of object IDs.

// Check the text of the alert. It should include
// all of the object identifiers.

// Confirm the alert, and then test that the correct
// WorkItems were created and that no spurious work
// items were created.

// TODO: Create & test bulk delete ENV token from
// parameter store?
}
15 changes: 12 additions & 3 deletions web/webui/deletion.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,16 @@ func NewDeletionForObject(objID int64, currentUser *pgmodels.User, baseURL strin
// IntellectualObjects and returns the Deletion object. This constructor
// is only for initializing new DeletionRequests, not for reviewing, approving
// or cancelling existing requests.
func NewDeletionForObjectBatch(institutionID int64, objIDs []int64, currentUser *pgmodels.User, baseURL string) (*Deletion, error) {
func NewDeletionForObjectBatch(requestorID, institutionID int64, objIDs []int64, baseURL string) (*Deletion, error) {

requestingUser, err := pgmodels.UserByID(requestorID)
if err != nil {
return nil, err
}
if requestingUser.InstitutionID != institutionID || requestingUser.Role != constants.RoleInstAdmin {
common.Context().Log.Error().Msgf("Requesting user %d is not admin at institution %d. Rejecting bulk deletion request.", requestorID, institutionID)
return nil, fmt.Errorf("invalid requestor id")
}

// Make sure that all objects belong to the specified institution.
validObjectCount, err := pgmodels.CountObjectsThatCanBeDeleted(institutionID, objIDs)
Expand All @@ -97,7 +106,7 @@ func NewDeletionForObjectBatch(institutionID int64, objIDs []int64, currentUser
}
if validObjectCount != len(objIDs) {
common.Context().Log.Error().Msgf("Batch deletion requested for %d objects, of which only %d are valid. InstitutionID = %d. Current user = %s. IDs: %v",
len(objIDs), validObjectCount, institutionID, currentUser.Email, objIDs)
len(objIDs), validObjectCount, institutionID, requestingUser.Email, objIDs)
return nil, fmt.Errorf("one or more object ids is invalid")
}

Expand All @@ -113,7 +122,7 @@ func NewDeletionForObjectBatch(institutionID int64, objIDs []int64, currentUser

del := &Deletion{
baseURL: baseURL,
currentUser: currentUser,
currentUser: requestingUser,
}
err = del.initObjectDeletionRequest(institutionID, objIDs)
if err != nil {
Expand Down

0 comments on commit 667d82f

Please sign in to comment.