Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Revision str extraction #1207

Merged
merged 8 commits into from
Feb 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .readthedocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@
# Required
version: 2

# Set the OS, Python version and other tools you might need
build:
os: ubuntu-22.04
tools:
python: "3.12"


# Build documentation in the docs/ directory with Sphinx
sphinx:
configuration: docs/conf.py
Expand Down
47 changes: 47 additions & 0 deletions api/source/controllers/Metrics.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,35 @@ async function getCollectionMetrics (req, res, next, {style, aggregation, firstR
}
}

async function getMetaMetrics (req, res, next, {style, aggregation, firstRowOnly = false}) {
try {
const returnType = req.query.format || 'json'
const inPredicates = {
collectionIds: req.query.collectionId,
benchmarkIds: req.query.benchmarkId,
revisionIds: req.query.revisionId
}
const rows = await MetricsService.queryMetaMetrics({
inPredicates,
userId: req.userObject.userId,
style,
aggregation,
returnType
})
if (returnType === 'csv') {
res.type('text/csv')
res.send(csvStringify(rows, {header: true}))
}
else {
res.json(firstRowOnly ? rows[0] : rows)
}
}
catch (e) {
next(e)
}
}


module.exports.getMetricsDetailByCollection = async function (req, res, next) {
await getCollectionMetrics(req, res, next, {style: 'detail', aggregation: 'unagg'})
}
Expand Down Expand Up @@ -67,3 +96,21 @@ module.exports.getMetricsSummaryByCollectionAggLabel = async function (req, res,
module.exports.getMetricsSummaryByCollectionAggStig = async function (req, res, next) {
await getCollectionMetrics(req, res, next, {style: 'summary', aggregation: 'stig'})
}
module.exports.getMetricsDetailByMeta = async function (req, res, next) {
await getMetaMetrics(req, res, next, {style: 'detail', aggregation: 'meta', firstRowOnly: true})
}
module.exports.getMetricsDetailByMetaAggCollection = async function (req, res, next) {
await getMetaMetrics(req, res, next, {style: 'detail', aggregation: 'collection'})
}
module.exports.getMetricsDetailByMetaAggStig = async function (req, res, next) {
await getMetaMetrics(req, res, next, {style: 'detail', aggregation: 'metaStig'})
}
module.exports.getMetricsSummaryByMeta = async function (req, res, next) {
await getMetaMetrics(req, res, next, {style: 'summary', aggregation: 'meta', firstRowOnly: true})
}
module.exports.getMetricsSummaryByMetaAggCollection = async function (req, res, next) {
await getMetaMetrics(req, res, next, {style: 'summary', aggregation: 'collection'})
}
module.exports.getMetricsSummaryByMetaAggStig = async function (req, res, next) {
await getMetaMetrics(req, res, next, {style: 'summary', aggregation: 'metaStig'})
}
4 changes: 2 additions & 2 deletions api/source/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion api/source/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "stig-management-api",
"version": "1.4.1",
"version": "1.4.2",
"description": "An API for managing evaluations of Security Technical Implementation Guide (STIG) assessments.",
"main": "index.js",
"scripts": {
Expand Down
16 changes: 8 additions & 8 deletions api/source/service/AssetService.js
Original file line number Diff line number Diff line change
Expand Up @@ -520,8 +520,8 @@ exports.queryChecklist = async function (inProjection, inPredicates, elevate, us
}
if (inPredicates.revisionStr !== 'latest') {
joins.splice(0, 1, 'revision rev')
const results = /V(\d+)R(\d+(\.\d+)?)/.exec(inPredicates.revisionStr)
const revId = `${inPredicates.benchmarkId}-${results[1]}-${results[2]}`
const {version, release} = dbUtils.parseRevisionStr(inPredicates.revisionStr)
const revId = `${inPredicates.benchmarkId}-${version}-${release}`
predicates.statements.push('rev.revId = :revId')
predicates.binds.revId = revId
}
Expand Down Expand Up @@ -776,8 +776,8 @@ exports.cklFromAssetStigs = async function cklFromAssetStigs (assetId, stigs, el
revisionStrResolved = `V${resultGetBenchmarkId[0].version}R${resultGetBenchmarkId[0].release}`
}
else {
let revParse = /V(\d+)R(\d+(\.\d+)?)/.exec(revisionStr)
revId = `${benchmarkId}-${revParse[1]}-${revParse[2]}`
const {version, release} = dbUtils.parseRevisionStr(revisionStr)
revId = `${benchmarkId}-${version}-${release}`
;[resultGetBenchmarkId] = await connection.execute(sqlGetBenchmarkId, [revId])
}

Expand Down Expand Up @@ -1021,8 +1021,8 @@ exports.cklbFromAssetStigs = async function cklbFromAssetStigs (assetId, stigs)
revisionStrResolved = `V${resultGetBenchmarkId[0].version}R${resultGetBenchmarkId[0].release}`
}
else {
let revParse = /V(\d+)R(\d+(\.\d+)?)/.exec(revisionStr)
revId = `${benchmarkId}-${revParse[1]}-${revParse[2]}`
const {version, release} = dbUtils.parseRevisionStr(revisionStr)
revId = `${benchmarkId}-${version}-${release}`
;[resultGetBenchmarkId] = await connection.execute(sqlGetBenchmarkId, [revId])
}

Expand Down Expand Up @@ -1181,8 +1181,8 @@ exports.xccdfFromAssetStig = async function (assetId, benchmarkId, revisionStr =
revisionStrResolved = `V${result[0].version}R${result[0].release}`
}
else {
let revParse = /V(\d+)R(\d+(\.\d+)?)/.exec(revisionStr)
revId = `${benchmarkId}-${revParse[1]}-${revParse[2]}`
const {version, release} = dbUtils.parseRevisionStr(revisionStr)
revId = `${benchmarkId}-${version}-${release}`
;[result] = await connection.query(sqlGetRevision, [revId])
revisionStrResolved = revisionStr
}
Expand Down
10 changes: 4 additions & 6 deletions api/source/service/CollectionService.js
Original file line number Diff line number Diff line change
Expand Up @@ -756,11 +756,11 @@ exports.getChecklistByCollectionStig = async function (collectionId, benchmarkId
// Non-current revision
if (revisionStr !== 'latest') {
joins.splice(2, 1, 'left join revision rev on sa.benchmarkId=rev.benchmarkId')
const results = /V(\d+)R(\d+(\.\d+)?)/.exec(revisionStr)
const {version, release} = dbUtils.parseRevisionStr(revisionStr)
predicates.statements.push('rev.version = :version')
predicates.statements.push('rev.release = :release')
predicates.binds.version = results[1]
predicates.binds.release = results[2]
predicates.binds.version = version
predicates.binds.release = release
}

// Access control
Expand Down Expand Up @@ -1670,9 +1670,7 @@ exports.writeStigPropsByCollectionStig = async function ({collectionId, benchmar
let version, release
if (defaultRevisionStr) {
if (defaultRevisionStr !== 'latest') {
const revisionParts = /V(\d+)R(\d+(\.\d+)?)/.exec(defaultRevisionStr)
version = revisionParts[1]
release = revisionParts[2]
;({version, release} = dbUtils.parseRevisionStr(defaultRevisionStr))
}
}
connection = await dbUtils.pool.getConnection()
Expand Down
156 changes: 155 additions & 1 deletion api/source/service/MetricsService.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ const dbUtils = require('./utils')

module.exports.queryMetrics = async function ({
inPredicates = {},
inProjections = [],
userId,
aggregation = 'unagg',
style = 'detail',
Expand All @@ -15,6 +14,7 @@ module.exports.queryMetrics = async function ({
}

// CTE processing
// This CTE retreives the granted Asset/STIG pairs for a single collection
const cteProps = {
columns: [
'distinct c.collectionId',
Expand Down Expand Up @@ -173,6 +173,132 @@ module.exports.queryMetrics = async function ({
return (rows || [])
}

module.exports.queryMetaMetrics = async function ({
inPredicates = {},
userId,
aggregation = 'meta',
style = 'detail',
returnType = 'json'
}) {
const predicates = {
statements: [],
binds: []
}
// CTE processing
// This CTE retreives the granted Asset/STIG pairs across all collections (or the requested ones)
const cteProps = {
columns: [
'distinct c.collectionId',
'sa.benchmarkId',
'a.assetId',
'sa.saId'
],
joins: [
'collection c',
'left join collection_grant cg on c.collectionId = cg.collectionId',
'inner join asset a on c.collectionId = a.collectionId and a.state = "enabled"',
'left join stig_asset_map sa on a.assetId = sa.assetId',
'left join user_stig_asset_map usa on sa.saId = usa.saId'
],
predicates: {
statements: [
'(cg.userId = ? AND CASE WHEN cg.accessLevel = 1 THEN usa.userId = cg.userId ELSE TRUE END)',
'c.state = "enabled"'
],
binds: [
userId
]
}
}
if (inPredicates.benchmarkIds) {
cteProps.predicates.statements.push(
'sa.benchmarkId IN ?'
)
cteProps.predicates.binds.push([inPredicates.benchmarkIds])
}
if (inPredicates.collectionIds) {
cteProps.predicates.statements.push(
'c.collectionId IN ?'
)
cteProps.predicates.binds.push([inPredicates.collectionIds])
}
if (inPredicates.revisionIds) {
cteProps.joins.push(
'left join default_rev dr on c.collectionId = dr.collectionId and sa.benchmarkId = dr.benchmarkId',
'left join revision rev on dr.revId = rev.revId'
)
cteProps.predicates.statements.push(
'rev.revId IN ?'
)
cteProps.predicates.binds.push([inPredicates.revisionIds])
}
const cteQuery = dbUtils.makeQueryString({
columns: cteProps.columns,
joins: cteProps.joins,
predicates: cteProps.predicates
})
const ctes = [
`granted as (${cteQuery})`
]
// Main query
const columns = returnType === 'csv' ? [...baseColsFlat[aggregation]] : [...baseCols[aggregation]]
const joins = [
'granted',
'left join asset a on granted.assetId = a.assetId',
'left join stig_asset_map sa on granted.saId = sa.saId',
'left join default_rev dr on granted.collectionId = dr.collectionId and sa.benchmarkId = dr.benchmarkId',
'left join revision rev on dr.revId = rev.revId',
'left join stig on rev.benchmarkId = stig.benchmarkId'
]
const groupBy = []
const orderBy = []
switch (aggregation) {
case 'meta':
predicates.statements.push('sa.benchmarkId IS NOT NULL')
break
case 'collection':
joins.push('left join collection c on granted.collectionId = c.collectionId')
groupBy.push('c.collectionId')
orderBy.push('c.name')
break
case 'metaStig':
predicates.statements.push('sa.benchmarkId IS NOT NULL')
groupBy.push('rev.revId')
orderBy.push('rev.benchmarkId')
break
}
if (style === 'detail') {
if (returnType === 'csv') {
columns.push(...colsMetricsDetailAgg)
}
else {
columns.push(sqlMetricsDetailAgg)
}
}
else { //style: 'summary'
if (returnType === 'csv') {
columns.push(...colsMetricsSummaryAgg)
}
else {
columns.push(sqlMetricsSummaryAgg)
}
}
const query = dbUtils.makeQueryString({
ctes,
columns,
joins,
predicates,
groupBy,
orderBy
})

let [rows, fields] = await dbUtils.pool.query(
query,
[...cteProps.predicates.binds, ...predicates.binds]
)
return (rows || [])
}

const sqlMetricsDetail = `json_object(
'assessments', rev.ruleCount,
'assessmentsBySeverity', json_object(
Expand Down Expand Up @@ -463,6 +589,20 @@ const baseCols = {
'cl.color',
'cl.description',
'count(distinct a.assetId) as assets'
],
meta: [
'count(distinct granted.collectionId) as collections',
'count(distinct a.assetId) as assets',
'count(distinct sa.benchmarkId) as stigs',
'count(sa.saId) as checklists'
],
metaStig: [
'rev.benchmarkId',
'stig.title',
'rev.revisionStr',
'count(distinct granted.collectionId) as collections',
'count(distinct a.assetId) as assets',
'rev.ruleCount'
]
}
const baseColsFlat = {
Expand Down Expand Up @@ -499,5 +639,19 @@ const baseColsFlat = {
'BIN_TO_UUID(cl.uuid,1) as labelId',
'cl.name',
'count(distinct a.assetId) as assets'
],
meta: [
'count(distinct granted.collectionId) as collections',
'count(distinct a.assetId) as assets',
'count(distinct sa.benchmarkId) as stigs',
'count(sa.saId) as checklists'
],
metaStig: [
'rev.benchmarkId',
'stig.title',
'rev.revisionStr',
'count(distinct granted.collectionId) as collections',
'count(distinct a.assetId) as assets',
'rev.ruleCount'
]
}
2 changes: 1 addition & 1 deletion api/source/service/OperationService.js
Original file line number Diff line number Diff line change
Expand Up @@ -338,7 +338,7 @@ exports.replaceAppData = async function (importOpts, appData, userObject, res )
}
for (const pin of c.stigs ?? []) {
if (pin.revisionPinned){
let [input, version, release] = /V(\d+)R(\d+(\.\d+)?)/.exec(pin.revisionStr)
const {version, release} = dbUtils.parseRevisionStr(pin.revisionStr)
dml.collectionPins.insertBinds.push([
parseInt(c.collectionId),
pin.benchmarkId,
Expand Down
Loading
Loading