diff --git a/api/source/controllers/Asset.js b/api/source/controllers/Asset.js
index 0cec12dac..d20dc859b 100644
--- a/api/source/controllers/Asset.js
+++ b/api/source/controllers/Asset.js
@@ -5,6 +5,7 @@ const config = require('../utils/config')
const escape = require('../utils/escape')
const AssetService = require(`../service/AssetService`);
const CollectionService = require(`../service/CollectionService`);
+const Security = require('../utils/accessLevels')
const dbUtils = require(`../service/utils`)
const {XMLBuilder} = require("fast-xml-parser")
const SmError = require('../utils/error')
@@ -785,47 +786,33 @@ module.exports.deleteAssetMetadataKey = async function (req, res, next) {
}
}
-module.exports.getAssetLabels = async function (req, res, next) {
+module.exports.patchAssets = async function (req, res, next) {
try {
- res.json({})
+ const collectionId = getCollectionIdAndCheckPermission(req, Security.ACCESS_LEVEL.Manage)
+ const patchRequest = req.body
+ const collection = await CollectionService.getCollection( collectionId, ['assets'], false, req.userObject)
+ const collectionAssets = collection.assets.map( a => a.assetId)
+ if (!patchRequest.assetIds.every( a => collectionAssets.includes(a))) {
+ throw new SmError.PrivilegeError('One or more assetId is not a Collection member.')
+ }
+ await AssetService.deleteAssets(patchRequest.assetIds, req.userObject)
+ res.json({
+ operation: 'deleted',
+ assetIds: patchRequest.assetIds
+ })
}
catch (err) {
next(err)
}
}
-module.exports.addAssetLabels = async function (req, res, next) {
- try {
- res.json({})
- }
- catch (err) {
- next(err)
+function getCollectionIdAndCheckPermission(request, minimumAccessLevel = Security.ACCESS_LEVEL.Manage) {
+ let collectionId = request.query.collectionId
+ const collectionGrant = request.userObject.collectionGrants.find( g => g.collection.collectionId === collectionId )
+ if (collectionGrant?.accessLevel < minimumAccessLevel) {
+ throw new SmError.PrivilegeError()
}
+ return collectionId
}
-module.exports.replaceAssetLabels = async function (req, res, next) {
- try {
- res.json({})
- }
- catch (err) {
- next(err)
- }
-}
-module.exports.deleteAssetLabels = async function (req, res, next) {
- try {
- res.json({})
- }
- catch (err) {
- next(err)
- }
-}
-
-module.exports.deleteAssetLabelById = async function (req, res, next) {
- try {
- res.json({})
- }
- catch (err) {
- next(err)
- }
-}
diff --git a/api/source/service/AssetService.js b/api/source/service/AssetService.js
index 89e163efc..c00897aaa 100644
--- a/api/source/service/AssetService.js
+++ b/api/source/service/AssetService.js
@@ -1333,6 +1333,13 @@ exports.deleteAsset = async function(assetId, projection, elevate, userObject) {
return (rows[0])
}
+exports.deleteAssets = async function(assetIds, userObject) {
+ const sqlDelete = `UPDATE asset SET state = "disabled", stateDate = NOW(), stateUserId = ? where assetId IN ?`
+ await dbUtils.pool.query(sqlDelete, [userObject.userId, [assetIds]])
+ // changes above might have affected need for records in collection_rev_map
+ await dbUtils.pruneCollectionRevMap()
+ await dbUtils.updateDefaultRev(null, {})
+}
exports.attachStigToAsset = async function( {assetId, benchmarkId, collectionId, elevate, userObject, svcStatus = {}} ) {
diff --git a/api/source/specification/stig-manager.yaml b/api/source/specification/stig-manager.yaml
index 50c9d8cc2..1e083128b 100644
--- a/api/source/specification/stig-manager.yaml
+++ b/api/source/specification/stig-manager.yaml
@@ -10,8 +10,6 @@ servers:
- url: 'http://localhost:64001/api'
paths:
/assets:
- parameters:
- - $ref: '#/components/parameters/AssetProjectionQuery'
get:
tags:
- Asset
@@ -26,6 +24,7 @@ paths:
- $ref: '#/components/parameters/NameMatchQuery'
- $ref: '#/components/parameters/MetadataQuery'
- $ref: '#/components/parameters/BenchmarkIdQuery'
+ - $ref: '#/components/parameters/AssetProjectionQuery'
responses:
'200':
description: AssetProjected array response
@@ -49,6 +48,8 @@ paths:
- Asset
summary: Create an Asset
operationId: createAsset
+ parameters:
+ - $ref: '#/components/parameters/AssetProjectionQuery'
requestBody:
required: true
content:
@@ -77,6 +78,42 @@ paths:
security:
- oauth:
- 'stig-manager:collection'
+ patch:
+ tags:
+ - Asset
+ summary: Delete one or more Assets
+ operationId: patchAssets
+ parameters:
+ - $ref: '#/components/parameters/CollectionIdQuery'
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/AssetsPatchRequest'
+ responses:
+ '200':
+ description: AssetsPatch response
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/AssetsPatchResponse'
+ '400':
+ description: Client Error
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ClientErrorDuplicateAsset'
+ default:
+ description: unexpected error
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ security:
+ - oauth:
+ - 'stig-manager:collection'
+
'/assets/{assetId}':
parameters:
- $ref: '#/components/parameters/AssetIdPath'
@@ -4031,6 +4068,30 @@ components:
type: array
items:
$ref: '#/components/schemas/StigUserBasic'
+ AssetsPatchRequest:
+ type: object
+ additionalProperties: false
+ properties:
+ operation:
+ type: string
+ enum:
+ - delete
+ assetIds:
+ type: array
+ items:
+ $ref: '#/components/schemas/AssetId'
+ AssetsPatchResponse:
+ type: object
+ additionalProperties: false
+ properties:
+ operation:
+ type: string
+ enum:
+ - deleted
+ assetIds:
+ type: array
+ items:
+ $ref: '#/components/schemas/AssetId'
AssetStigResponse:
type: object
additionalProperties: false
diff --git a/client/src/js/SM/CollectionAsset.js b/client/src/js/SM/CollectionAsset.js
index 2ffc3f3fe..a03ca0817 100644
--- a/client/src/js/SM/CollectionAsset.js
+++ b/client/src/js/SM/CollectionAsset.js
@@ -265,24 +265,26 @@ SM.CollectionAssetGrid = Ext.extend(Ext.grid.GridPanel, {
try {
let assetRecords = me.getSelectionModel().getSelections()
const multiDelete = assetRecords.length > 1
- var confirmStr=`Deleting ${multiDelete ? 'multiple assets' : 'this asset'} will permanently remove all data associated with the asset${multiDelete ? 's' : ''}. This includes all the corresponding STIG assessments. The deleted data cannot be recovered.
Do you wish to continue?`;
+ const confirmStr=`Deleting ${multiDelete ? 'multiple assets' : 'this asset'} will permanently remove all data associated with the asset${multiDelete ? 's' : ''}. This includes all the corresponding STIG assessments. The deleted data cannot be recovered.
Do you wish to continue?`;
let btn = await SM.confirmPromise("Confirm Delete", confirmStr)
if (btn == 'yes') {
- const l = assetRecords.length
- let apiAsset
- for (let i=0; i < l; i++) {
- Ext.getBody().mask(`Deleting ${i+1}/${l} Assets`)
- // Edge case to handle when the selected record was changed (e.g., stats updated)
- // while still selected, then is deleted
- const thisRecord = me.store.getById(assetRecords[i].id)
- apiAsset = await Ext.Ajax.requestPromise({
- responseType: 'json',
- url: `${STIGMAN.Env.apiBase}/assets/${thisRecord.data.assetId}`,
- method: 'DELETE'
- })
- me.store.remove(thisRecord)
- }
- SM.Dispatcher.fireEvent('assetdeleted', apiAsset) //only need the last deleted asset
+ const assetIds = assetRecords.map( r => r.data.assetId)
+ Ext.getBody().mask(`Deleting ${assetRecords.length} Assets`)
+ await Ext.Ajax.requestPromise({
+ responseType: 'json',
+ url: `${STIGMAN.Env.apiBase}/assets?collectionId=${me.collectionId}`,
+ method: 'PATCH',
+ jsonData: {
+ operation: 'delete',
+ assetIds
+ }
+ })
+ me.store.suspendEvents(false)
+ // Might need to handle edge case when the selected record was changed (e.g., stats updated) while still selected, then is deleted
+ me.store.remove(assetRecords)
+ me.store.resumeEvents()
+
+ SM.Dispatcher.fireEvent('assetdeleted', {collection: {collectionId: me.collectionId}}) // mock an Asset for collectionManager.onAssetEvent
}
}
catch (e) {