Skip to content
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
253 changes: 146 additions & 107 deletions src/audit-logs/scripts/sync.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ const AUDIT_LOG_DATA_DIR = 'src/audit-logs/data'

// the 3 different audit log types which correspond to audit log event pages
const auditLogTypes = ['user', 'organization', 'enterprise']
const versions = ['fpt', 'ghec', 'ghes']

// store an array of audit log event data keyed by version and audit log type,
// array will look like:
Expand All @@ -37,15 +36,6 @@ const versions = ['fpt', 'ghec', 'ghes']
// },
// ...
// ]
const auditLogData = {}
versions.forEach((version) => {
auditLogData[version] = {}

auditLogTypes.forEach((type) => {
auditLogData[version][type] = []
})
})

async function main() {
// get latest audit log data
//
Expand All @@ -65,11 +55,26 @@ async function main() {
const schemaEvents = JSON.parse(await getContents(owner, repo, ref, schemaFilePath))
const mainSha = await getCommitSha(owner, repo, `heads/${ref}`)

// update pipeline config data
const configFilepath = `src/audit-logs/lib/config.json`
const configData = JSON.parse(await readFile(configFilepath, 'utf8'))
configData.sha = mainSha
await writeFile(configFilepath, JSON.stringify(configData, null, 2))
const pipelineConfig = JSON.parse(await readFile(configFilepath, 'utf8'))
pipelineConfig.sha = mainSha
await writeFile(configFilepath, JSON.stringify(pipelineConfig, null, 2))

const auditLogData = {}
// Wrapper around filterByAllowlistValues() because we always need all the
// schema events and pipeline config data.
const filter = (
allowListValues,
filterConfig = { filterFn: filterOr, ghesOnly: false },
currentEvents = [],
) =>
filterByAllowlistValues(
schemaEvents,
currentEvents,
allowListValues,
pipelineConfig,
filterConfig,
)

// translate allowLists values into the versions and audit log page the event
// belongs to -- for versions:
Expand All @@ -84,101 +89,70 @@ async function main() {
// * user => user page
// * business_server => enterprise page
// * organization => organization page
schemaEvents
.filter((event) => event._allowlists !== null)
.forEach((event) => {
const minimalEvent = {
action: event.action,
description: event.description,
docs_reference_links: event.docs_reference_links,
}

// FPT events
if (event._allowlists.includes('user')) {
auditLogData.fpt.user.push(minimalEvent)
}
if (event._allowlists.includes('organization')) {
auditLogData.fpt.organization.push(minimalEvent)
}

// GHES events
if (event._allowlists.includes('business_server')) {
auditLogData.ghes.enterprise.push(minimalEvent)

if (event._allowlists.includes('user')) {
auditLogData.ghes.user.push(minimalEvent)
}

if (event._allowlists.includes('organization')) {
auditLogData.ghes.organization.push(minimalEvent)
}
}

// GHEC events
if (event._allowlists.includes('business')) {
auditLogData.ghec.enterprise.push(minimalEvent)

if (event._allowlists.includes('organization')) {
auditLogData.ghec.organization.push(minimalEvent)
}

if (event._allowlists.includes('user')) {
auditLogData.ghec.user.push(minimalEvent)
}
}

// API only events have either `business_api_only` or `org_api_only`
// allowlist values.
//
// * business_api_only: goes to the enterprise events page and is versioned
// for GHEC. If the event has any `ghes` versions, also verisoned for GHES.
// * org_api_only: goes to the organzation events page and is versioned
// for fpt and GHEC. If the event has any `ghes` versions, also versioned
// for GHES.
//
// There's currently one case (`org.sso_response`) where an event is an
// api only event but also has a non-api-only allowlist value so that's
// why we have the checks to prevent adding the event twice.
if (event._allowlists.includes('business_api_only')) {
if (!auditLogData.ghec.enterprise.find((e) => e.action === event.action)) {
auditLogData.ghec.enterprise.push(minimalEvent)
}

if (
Object.keys(event.ghes).length > 0 &&
!auditLogData.ghes.enterprise.find((e) => e.action === event.action)
) {
auditLogData.ghes.enterprise.push(minimalEvent)
}
}

if (event._allowlists.includes('org_api_only')) {
if (!auditLogData.ghec.organization.find((e) => e.action === event.action)) {
auditLogData.fpt.organization.push(minimalEvent)
auditLogData.ghec.organization.push(minimalEvent)
}
//
// API only events have either `business_api_only` or `org_api_only`
// allowlist values.
//
// * business_api_only: goes to the enterprise events page and is versioned
// for GHEC. If the event has any `ghes` versions, also verisoned for GHES.
// * org_api_only: goes to the organzation events page and is versioned
// for fpt and GHEC. If the event has any `ghes` versions, also versioned
// for GHES.
//
// There's currently one case (`org.sso_response`) where an event is an
// api only event but also has a non-api-only allowlist value so that's
// why we have the checks to prevent adding the event twice.
auditLogData.fpt = {}
auditLogData.fpt.user = filter('user')
auditLogData.fpt.organization = filter(['organization', 'org_api_only'])

if (
Object.keys(event.ghes).length > 0 &&
!auditLogData.ghes.organization.find((e) => e.action === event.action)
) {
auditLogData.ghes.organization.push(minimalEvent)
}
}
auditLogData.ghes = {}
auditLogData.ghes.enterprise = filter('business_server')
auditLogData.ghes.enterprise = filter(
'business_api_only',
{
filterFn: filterOr,
ghesOnly: true,
},
auditLogData.ghes.enterprise,
)
auditLogData.ghes.user = filter(['business_server', 'user'], {
filterFn: filterAnd,
ghesOnly: false,
})
auditLogData.ghes.organization = filter(['business_server', 'organization'], {
filterFn: filterAnd,
ghesOnly: false,
})
auditLogData.ghes.organization = filter(
'org_api_only',
{
filterFn: filterOr,
ghesOnly: true,
},
auditLogData.ghes.organization,
)

// For the API only events we append an extra note to the description
// that explains the event is api only. It's better to do it here instead
// of in the schema where it would have to be updated and maintained manually.
//
// Some events have both API only allowlist values so we make sure to append
// to the description only once.
if (
event._allowlists.includes('org_api_only') ||
event._allowlists.includes('business_api_only')
) {
minimalEvent.description += ` ${configData.apiOnlyEventsAdditionalDescription}`
}
})
auditLogData.ghec = {}
auditLogData.ghec.user = filter(['business', 'user'], {
filterFn: filterAnd,
ghesOnly: false,
})
auditLogData.ghec.organization = filter(['business', 'organization'], {
filterFn: filterAnd,
ghesOnly: false,
})
auditLogData.ghec.organization = filter(
'org_api_only',
{ filterFn: filterOr, ghesOnly: false },
auditLogData.ghec.organization,
)
auditLogData.ghec.enterprise = filter('business')
auditLogData.ghec.enterprise = filter(
'business_api_only',
{ filterFn: filterOr, ghesOnly: false },
auditLogData.ghec.enterprise,
)

// We don't maintain the order of events as we process them so after filtering
// all the events based on their allowlist values, we sort them so they're in
Expand Down Expand Up @@ -227,4 +201,69 @@ async function main() {
}
}

// Filters audit log events based on allowlist values.
//
// * eventsToCheck: events to consider
// * currentEvents: events already collected
// * allowListvalues: allowlist values to filter by
// * pipelineConfig: audit log pipeline config data
// * filterFn: callback to filter an event by allowlist values
// * ghesOnly: whether or not we should check an event is versioned for ghes
// as an additional filter check
function filterByAllowlistValues(
eventsToCheck,
currentEvents,
allowListValues,
pipelineConfig,
filterConfig = {
filterFn: filterOr,
ghesOnly: false,
},
) {
if (!Array.isArray(allowListValues)) allowListValues = [allowListValues]
if (!currentEvents) currentEvents = []

const seen = new Set(currentEvents.map((event) => event.action))
const minimalEvents = []

for (const event of eventsToCheck) {
if (event._allowlists === null) continue

if (filterConfig.filterFn(event._allowlists, allowListValues)) {
if (seen.has(event.action)) continue
seen.add(event.action)

const minimal = {
action: event.action,
description: event.description,
docs_reference_links: event.docs_reference_links,
}

if (
event._allowlists.includes('org_api_only') ||
event._allowlists.includes('business_api_only')
) {
minimal.description += ` ${pipelineConfig.apiOnlyEventsAdditionalDescription}`
}

if (filterConfig.ghesOnly) {
if (Object.keys(event.ghes).length > 0) {
minimalEvents.push(minimal)
}
} else {
minimalEvents.push(minimal)
}
}
}
return [...minimalEvents, ...currentEvents]
}

function filterOr(array, conditions) {
return conditions.some((condition) => array.includes(condition))
}

function filterAnd(array, conditions) {
return conditions.every((condition) => array.includes(condition))
}

main()
35 changes: 35 additions & 0 deletions src/content-render/stylesheets/alerts.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Largely identical styling from the monolith,
// but the color names match the Primer variables
// and we had to directly state a few props instead of using variables
// that are only in the monolith.

$colors: "default", "muted", "subtle", "accent", "success", "attention",
"severe", "danger", "open", "closed", "done", "sponsors";

.ghd-alert {
padding: var(--base-size-8, 0.5rem) var(--base-size-16, 1rem);
border-left: 0.25em solid
var(--borderColor-default, var(--color-border-default));
margin-bottom: 1rem;
}

.ghd-alert > :last-child {
margin-bottom: 0;
}

.ghd-alert-title {
display: flex;
font-weight: var(--base-text-weight-medium, 500);
align-items: center;
line-height: 1;
}

@each $color in $colors {
.ghd-alert-#{$color} {
border-left-color: var(--fgColor-#{$color}, var(--color-#{$color}-fg));

.ghd-alert-title {
color: var(--fgColor-#{$color}, var(--color-#{$color}-fg));
}
}
}
1 change: 1 addition & 0 deletions src/content-render/stylesheets/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@
@import "markdown-overrides.scss";
@import "spotlight.scss";
@import "syntax-highlighting.scss";
@import "alerts.scss";
Loading