Skip to content

Add check validator for softwareDesignTraining #58

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

Merged
merged 5 commits into from
Dec 11, 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
167 changes: 166 additions & 1 deletion __tests__/checks/validators.test.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const { githubOrgMFA } = require('../../src/checks/validators')
const { githubOrgMFA, softwareDesignTraining } = require('../../src/checks/validators')
// @see: https://github.com/secure-dashboards/openjs-foundation-dashboard/issues/43
describe('githubOrgMFA', () => {
let organizations, check, projects
Expand Down Expand Up @@ -146,3 +146,168 @@ describe('githubOrgMFA', () => {
})
})
})

describe('softwareDesignTraining', () => {
let softwareDesignTrainings, check, projects
beforeEach(() => {
softwareDesignTrainings = [
{
project_id: 1,
date: new Date().toISOString()
},
{
project_id: 2,
date: new Date().toISOString()
}
]

check = {
id: 1,
priority_group: 'P1',
details_url: 'https://example.com',
level_incubating_status: 'expected',
level_active_status: 'expected',
level_retiring_status: 'expected'
}

projects = [
{
id: 1,
category: 'impact'
},
{
id: 2,
category: 'at-large'
}
]
})

it('Should generate a passed result if the project has a software design training and it is up to date', () => {
const analysis = softwareDesignTraining({ softwareDesignTrainings, check, projects })
expect(analysis).toEqual({
alerts: [],
results: [
{
project_id: 1,
compliance_check_id: 1,
severity: 'critical',
status: 'passed',
rationale: 'Software Design Training is up to date'
},
{
project_id: 2,
compliance_check_id: 1,
severity: 'critical',
status: 'passed',
rationale: 'Software Design Training is up to date'
}
],
tasks: []
})
})
it('Should generate a failed result if the project has a software design training but it is out of date', () => {
softwareDesignTrainings[0].date = '2019-01-01'
const analysis = softwareDesignTraining({ softwareDesignTrainings, check, projects })
expect(analysis).toEqual({
alerts: [
{
project_id: 1,
compliance_check_id: 1,
severity: 'critical',
title: 'Software Design Training is out of date',
description: 'Check the details on https://example.com'
}
],
results: [
{
project_id: 1,
compliance_check_id: 1,
severity: 'critical',
status: 'failed',
rationale: 'Software Design Training is out of date'
},
{
project_id: 2,
compliance_check_id: 1,
severity: 'critical',
status: 'passed',
rationale: 'Software Design Training is up to date'
}
],
tasks: [
{
project_id: 1,
compliance_check_id: 1,
severity: 'critical',
title: 'Update Software Design Training',
description: 'Check the details on https://example.com'
}
]
})
})
it('Should generate a failed result if the project does not have a software design training', () => {
softwareDesignTrainings = []
const analysis = softwareDesignTraining({ softwareDesignTrainings, check, projects })
expect(analysis).toEqual({
alerts: [
{
project_id: 1,
compliance_check_id: 1,
severity: 'critical',
title: 'No Software Design Training found',
description: 'Check the details on https://example.com'
},
{
project_id: 2,
compliance_check_id: 1,
severity: 'critical',
title: 'No Software Design Training found',
description: 'Check the details on https://example.com'
}
],
results: [
{
project_id: 1,
compliance_check_id: 1,
severity: 'critical',
status: 'failed',
rationale: 'No Software Design Training found'
},
{
project_id: 2,
compliance_check_id: 1,
severity: 'critical',
status: 'failed',
rationale: 'No Software Design Training found'
}
],
tasks: [
{
project_id: 1,
compliance_check_id: 1,
severity: 'critical',
title: 'Create a Software Design Training',
description: 'Check the details on https://example.com'
},
{
project_id: 2,
compliance_check_id: 1,
severity: 'critical',
title: 'Create a Software Design Training',
description: 'Check the details on https://example.com'
}
]
})
})
it('Should skip the check if it is not in scope for the project category', () => {
check.level_active_status = 'n/a'
check.level_incubating_status = 'n/a'
check.level_retiring_status = 'n/a'
const analysis = softwareDesignTraining({ softwareDesignTrainings, check, projects })
expect(analysis).toEqual({
alerts: [],
results: [],
tasks: []
})
})
})
50 changes: 49 additions & 1 deletion __tests__/utils.test.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const { validateGithubUrl, ensureGithubToken, groupArrayItemsByCriteria, isCheckApplicableToProjectCategory, getSeverityFromPriorityGroup } = require('../src/utils/index')
const { validateGithubUrl, ensureGithubToken, groupArrayItemsByCriteria, isCheckApplicableToProjectCategory, getSeverityFromPriorityGroup, isDateWithinPolicy } = require('../src/utils/index')

describe('ensureGithubToken', () => {
let originalGithubToken
Expand Down Expand Up @@ -108,3 +108,51 @@ describe('getSeverityFromPriorityGroup', () => {
expect(getSeverityFromPriorityGroup('R11')).toBe('info')
})
})

describe('isDateWithinPolicy', () => {
it('should return true for date within 90 days', () => {
const targetDate = new Date().toISOString()
const policy = '90d'
expect(isDateWithinPolicy(targetDate, policy)).toBe(true)
})

it('should return false for date outside 90 days', () => {
const targetDate = new Date(new Date().setDate(new Date().getDate() - 91)).toISOString()
const policy = '90d'
expect(isDateWithinPolicy(targetDate, policy)).toBe(false)
})

it('should return true for date within 3 months', () => {
const targetDate = new Date().toISOString()
const policy = '3m'
expect(isDateWithinPolicy(targetDate, policy)).toBe(true)
})

it('should return false for date outside 3 months', () => {
const targetDate = new Date(new Date().setMonth(new Date().getMonth() - 4)).toISOString()
const policy = '3m'
expect(isDateWithinPolicy(targetDate, policy)).toBe(false)
})

it('should return true for date within 1 quarter', () => {
const targetDate = new Date().toISOString()
const policy = '1q'
expect(isDateWithinPolicy(targetDate, policy)).toBe(true)
})

it('should throw an error for unsupported policy format', () => {
const targetDate = new Date().toISOString()
const policy = '10y' // Unsupported format
expect(() => isDateWithinPolicy(targetDate, policy)).toThrow('Unsupported policy format')
})

it('should throw an error if expiration policy is not provided', () => {
const targetDate = new Date().toISOString()
expect(() => isDateWithinPolicy(targetDate)).toThrow('Policy is required')
})

it('should throw an error if the targetDate is not provided', () => {
const policy = '3m'
expect(() => isDateWithinPolicy(undefined, policy)).toThrow('Target date is required')
})
})
11 changes: 11 additions & 0 deletions package-lock.json

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

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
"ajv": "8.17.1",
"ajv-formats": "3.0.1",
"commander": "12.1.0",
"date-fns": "4.1.0",
"debug": "4.3.7",
"inquirer": "12.1.0",
"knex": "3.1.0",
Expand Down
74 changes: 74 additions & 0 deletions src/checks/validators/githubOrgMFA.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
const debug = require('debug')('checks:validator:githubOrgMFA')
const { getSeverityFromPriorityGroup, isCheckApplicableToProjectCategory, groupArrayItemsByCriteria } = require('../../utils')

const groupByProject = groupArrayItemsByCriteria('project_id')

// @see: https://github.com/secure-dashboards/openjs-foundation-dashboard/issues/43
module.exports = ({ organizations, check, projects }) => {
debug('Validating GitHub organizations MFA...')
debug('Grouping organizations by project...')
const organizationsGroupedByProject = groupByProject(organizations)

const alerts = []
const results = []
const tasks = []

debug('Processing organizations...')
organizationsGroupedByProject.forEach((projectOrgs) => {
debug(`Processing project (${projectOrgs[0].project_id})`)
const project = projects.find(p => p.id === projectOrgs[0].project_id)
const isInScope = isCheckApplicableToProjectCategory(check, project)
// If the check is not in scope, skip it.
if (!isInScope) {
debug(`This check is not in scope for project (${project.id})`)
return
}

const baseData = {
project_id: projectOrgs[0].project_id,
compliance_check_id: check.id,
severity: getSeverityFromPriorityGroup(check.priority_group)
}

const result = { ...baseData }
const task = { ...baseData }
const alert = { ...baseData }

const failedOrgs = projectOrgs.filter(org => org.two_factor_requirement_enabled === false).map(org => org.login)
const unknownOrgs = projectOrgs.filter(org => org.two_factor_requirement_enabled === null).map(org => org.login)

if (projectOrgs.every(org => org.two_factor_requirement_enabled === true)) {
result.status = 'passed'
result.rationale = 'The organization(s) have 2FA enabled'
} else if (failedOrgs.length) {
result.status = 'failed'
result.rationale = `The organization(s) (${failedOrgs.join(',')}) do not have 2FA enabled`
alert.title = `The organization(s) (${failedOrgs.join(',')}) do not have 2FA enabled`
alert.description = `Check the details on ${check.details_url}`
task.title = `Enable 2FA for the organization(s) (${failedOrgs.join(',')})`
task.description = `Check the details on ${check.details_url}`
} else if (unknownOrgs.length) {
result.status = 'unknown'
result.rationale = `The organization(s) (${unknownOrgs.join(',')}) have 2FA status unknown`
}
// Include only the task if was populated
if (Object.keys(task).length > Object.keys(baseData).length) {
debug(`Adding task for project (${project.id})`)
tasks.push(task)
}
// Include only the alert if was populated
if (Object.keys(alert).length > Object.keys(baseData).length) {
debug(`Adding alert for project (${project.id})`)
alerts.push(alert)
}
// Always include the result
results.push(result)
debug(`Processed project (${project.id})`)
})

return {
alerts,
results,
tasks
}
}
Loading
Loading