import stripAnsi from 'strip-ansi' ; import inquirer , { ConfirmQuestion , InputQuestion , QuestionCollection , } from 'inquirer' ; import chalk from 'chalk' ; import fs from 'fs-extra' ; import { baseQuestions } from './questions/base' ; import { scorecardQuestions } from './questions/scorecard' ; import { hipaaQuestions } from './questions/hipaa' ; import { Organization , PolicyBuilderConfig , AdoptedPolicyBuilderElements , PolicyBuilderElement , PolicyBuilderPartial , PolicyBuilderPartialType , } from '~/src/types' ; import pickAdopted from '~/src/util/pickAdopted' ; async function promptForValues ( options ) { const { config , noninteractive } = options ; const answers = await safeInquirerPrompt ( missingOrEmptyOrganizationValues ( config . organization ) ) ; Object . assign ( config . organization , answers ) ; await promptForSecurityScorecardValues ( config . organization ) ; await promptForHIPAAValues ( config . organization ) ; console . log ( JSON . stringify ( config . organization , null , 2 ) ) ; if ( noninteractive !== true ) { const save = await inquirer . prompt ( promptForSave ) ; if ( save . selected ) { try { await fs . writeFile ( save . path , JSON . stringify ( config , null , 2 ) ) ; console . log ( chalk . green ( `saved configuration to ${ save . path }` ) ) ; } catch ( err ) { console . log ( chalk . yellow ( `Unable to save configuration to ${ save . path }: ${ err }` ) ) ; } } } mergeAutomaticPSPVars ( config ) ; return config ; } async function promptForSecurityScorecardValues ( config ) { if ( config . haveSecurityScorecard === true ) { const toAsk = missingOrEmptyOrganizationValues ( config , scorecardQuestions ) ; const values = await safeInquirerPrompt ( toAsk ) ; Object . assign ( config , values ) ; } config . securityScorecardPeriod = config . securityScorecardPeriod || 'none' ; config . securityScorecardURL = config . securityScorecardURL || 'N/A' ; } async function promptForHIPAAValues ( config ) { if ( config . needStandardHIPAA === true ) { const toAsk = missingOrEmptyOrganizationValues ( config , hipaaQuestions ) ; const values = await safeInquirerPrompt ( toAsk ) ; Object . assign ( config , values ) ; } config . securityScorecardPeriod = config . securityScorecardPeriod || 'none' ; config . securityScorecardURL = config . securityScorecardURL || 'N/A' ; } async function safeInquirerPrompt ( list ) { if ( process . env . NODE_ENV === 'test' ) { return { } ; } const answers = await inquirer . prompt ( list ) ; return sanitize ( answers ) ; } function sanitize ( obj ) { const saneObj = { ... obj } ; Object . keys ( saneObj ) . forEach ( ( key ) => { saneObj [ key ] = stripAnsi ( saneObj [ key ] ) ; } ) ; return saneObj ; } const promptForSave = [ { type : 'confirm' , name : 'selected' , message : 'Save this configuration to a file' , default : false , } , { type : 'input' , name : 'path' , message : 'path to saved file' , when : ( answers ) => { return answers . selected === true ; } , } , ] ; function missingOrganizationValues ( org = { } , values = baseQuestions ) { const orgKeys = Object . keys ( org ) ; return values . map ( ( x ) => x . name ) . filter ( ( x ) => ! orgKeys . includes ( x ) ) ; } function missingOrEmptyOrganizationValues ( org , values = baseQuestions ) { const missingValues = missingOrganizationValues ( org , values ) ; return values . filter ( ( q ) => { return missingValues . includes ( q . name ) || org [ q . name ] === '' ; } ) ; } function buildPartial ( element , partialType ) { const { type , ... properties } = element ; const partial = { ... properties , type : partialType , } ; return partial ; } function getAdoptedPartials ( config ) { const partials = [ ] ; for ( const policy of config . policies || [ ] ) { if ( ! policy . adopted ) { continue ; } partials . push ( { ... policy , type : 'policies' , } ) ; for ( const id of policy . procedures ?? [ ] ) { const byIdFilter = ( p ) => { return p . id === id ; } ; let policyImplementer = config . procedures ?. find ( byIdFilter ) ; if ( policyImplementer ) { partials . push ( buildPartial ( policyImplementer , 'procedures' ) ) ; } else if ( ( policyImplementer = config . references ?. find ( byIdFilter ) ) ) { partials . push ( buildPartial ( policyImplementer , 'references' ) ) ; } } } for ( const ref of config . references ?? [ ] ) { if ( ! ref . adopted ) { continue ; } partials . push ( buildPartial ( ref , 'references' ) ) ; } return partials ; } function getAdoptedProceduresForPolicy ( policyId , config ) { const procedures = [ ] ; const policy = config . policies ?. find ( ( p ) => p . id === policyId ) ; if ( ! policy ) { throw new Error ( `unknown policy id ${ policyId }` ) ; } let policyImplementor ; for ( const procedure of policy . procedures ?? [ ] ) { policyImplementor = config . procedures ?. find ( ( p ) => p . id === procedure ) ?? config . references ?. find ( ( r ) => r . id === procedure ) ; if ( ! policyImplementor ) { throw new Error ( `reference to unknown procedure '${ procedure }' in policy '${ policy . id }'` ) ; } if ( ! policyImplementor . adopted ) { continue ; } procedures . push ( policyImplementor ) ; } return procedures ; } function mergeAutomaticPSPVars ( config ) { const defaultRevision = `${ new Date ( ) . getFullYear ( ) }.1` ; const mergeValues = { defaultRevision , } ; Object . assign ( config . organization , mergeValues ) ; } function getAdoptedPSPElements ( config ) { const adopted = { standards : pickAdopted ( config . standards ) , policies : pickAdopted ( config . policies ) , procedures : pickAdopted ( config . procedures ) , references : pickAdopted ( config . references ) , } ; return adopted ; } export { getAdoptedPartials , getAdoptedProceduresForPolicy , getAdoptedPSPElements , mergeAutomaticPSPVars , missingOrEmptyOrganizationValues , missingOrganizationValues , promptForValues , safeInquirerPrompt , } ;