diff --git a/src/article-api/templates/audit-logs-page.template.md b/src/article-api/templates/audit-logs-page.template.md new file mode 100644 index 0000000000..c91df5343c --- /dev/null +++ b/src/article-api/templates/audit-logs-page.template.md @@ -0,0 +1,30 @@ +# {{ page.title }} + +{{ page.intro }} + +{{ manualContent }} + +## Audit log events + +{% for categoryEntry in categorizedEvents %} +{% assign categoryName = categoryEntry[0] %} +{% assign events = categoryEntry[1] %} +### {{ categoryName }} + +{% if categoryNotes[categoryName] %} +{{ categoryNotes[categoryName] }} + +{% endif %} +{% for event in events %} +#### `{{ event.action }}` + +{{ event.description }} + +**Fields:** {% if event.fields %}{% for field in event.fields %}`{{ field }}`{% unless forloop.last %}, {% endunless %}{% endfor %}{% else %}No fields available{% endif %} + +{% if event.docs_reference_links and event.docs_reference_links != 'N/A' %} +**Reference:** {{ event.docs_reference_links }} +{% endif %} + +{% endfor %} +{% endfor %} \ No newline at end of file diff --git a/src/article-api/tests/audit-logs-transformer.ts b/src/article-api/tests/audit-logs-transformer.ts new file mode 100644 index 0000000000..1bf493c5fc --- /dev/null +++ b/src/article-api/tests/audit-logs-transformer.ts @@ -0,0 +1,95 @@ +import { beforeAll, describe, expect, test } from 'vitest' + +import { get } from '@/tests/helpers/e2etest' + +const makeURL = (pathname: string): string => { + const params = new URLSearchParams({ pathname }) + return `/api/article/body?${params}` +} + +describe('Audit Logs transformer', () => { + beforeAll(() => { + if (!process.env.ROOT) { + console.warn( + 'WARNING: The Audit Logs transformer tests require the ROOT environment variable to be set to the fixture root', + ) + } + }) + + test('Security log events page renders with markdown structure', async () => { + const res = await get( + makeURL('/en/authentication/keeping-your-account-and-data-secure/security-log-events'), + ) + expect(res.statusCode).toBe(200) + expect(res.headers['content-type']).toContain('text/markdown') + + // Check for the main heading + expect(res.body).toContain('# Security log events') + + // Check for intro + expect(res.body).toContain( + 'Learn about security log events recorded for your personal account.', + ) + + // Check for manual content section heading + expect(res.body).toContain('## About security log events') + + // Check for new main heading + expect(res.body).toContain('## Audit log events') + + // Check for category heading + // The template renders "### Category" + expect(res.body).toMatch(/### \w+/) + }) + + test('Enterprise audit log events page renders with markdown structure', async () => { + const res = await get( + makeURL( + '/en/admin/monitoring-activity-in-your-enterprise/reviewing-audit-logs-for-your-enterprise/audit-log-events-for-your-enterprise', + ), + ) + expect(res.statusCode).toBe(200) + expect(res.headers['content-type']).toContain('text/markdown') + + expect(res.body).toContain('# Audit log events for your enterprise') + }) + + test('Organization audit log events page renders with markdown structure', async () => { + const res = await get( + makeURL( + '/en/organizations/keeping-your-organization-secure/managing-security-settings-for-your-organization/audit-log-events-for-your-organization', + ), + ) + expect(res.statusCode).toBe(200) + expect(res.headers['content-type']).toContain('text/markdown') + + expect(res.body).toContain('# Audit log events for your organization') + }) + + test('Events are formatted correctly', async () => { + const res = await get( + makeURL('/en/authentication/keeping-your-account-and-data-secure/security-log-events'), + ) + expect(res.statusCode).toBe(200) + + // Check for event action header + // #### `action.name` + expect(res.body).toMatch(/#### `[\w.]+`/) + + // Check for fields section + expect(res.body).toContain('**Fields:**') + + // Check for reference section + expect(res.body).toContain('**Reference:**') + }) + + test('Manual content is preserved', async () => { + const res = await get( + makeURL('/en/authentication/keeping-your-account-and-data-secure/security-log-events'), + ) + expect(res.statusCode).toBe(200) + + // The source file has manual content before the marker + expect(res.body).toContain('## About security log events') + }) +}) diff --git a/src/article-api/transformers/audit-logs-transformer.ts b/src/article-api/transformers/audit-logs-transformer.ts new file mode 100644 index 0000000000..962e47c3d8 --- /dev/null +++ b/src/article-api/transformers/audit-logs-transformer.ts @@ -0,0 +1,139 @@ +import type { Context, Page } from '@/types' +import type { PageTransformer } from './types' +import type { CategorizedEvents } from '@/audit-logs/types' +import { renderContent } from '@/content-render/index' +import matter from '@gr2m/gray-matter' +import { readFileSync } from 'fs' +import { join, dirname } from 'path' +import { fileURLToPath } from 'url' + +const __filename = fileURLToPath(import.meta.url) +const __dirname = dirname(__filename) + +/** + * Transformer for Audit Logs pages + * Converts audit log events and their data into markdown format using a Liquid template + */ +export class AuditLogsTransformer implements PageTransformer { + canTransform(page: Page): boolean { + return page.autogenerated === 'audit-logs' + } + + async transform(page: Page, pathname: string, context: Context): Promise { + // Import audit log lib dynamically to avoid circular dependencies + const { getCategorizedAuditLogEvents, getCategoryNotes, resolveReferenceLinksToMarkdown } = + await import('@/audit-logs/lib/index') + + // Extract version from context + const currentVersion = context.currentVersion! + + let pageType = '' + if (pathname.includes('/security-log-events')) { + pageType = 'user' + } else if (pathname.includes('/audit-log-events-for-your-enterprise')) { + pageType = 'enterprise' + } else if (pathname.includes('/audit-log-events-for-your-organization')) { + pageType = 'organization' + } else { + throw new Error(`Unknown audit log page type for path: ${pathname}`) + } + + // Get the audit log events data + const categorizedEvents = getCategorizedAuditLogEvents(pageType, currentVersion) + const categoryNotes = getCategoryNotes() + + // Prepare manual content + let manualContent = '' + if (page.markdown) { + const markerIndex = page.markdown.indexOf( + '', + ) + if (markerIndex > 0) { + const { content } = matter(page.markdown) + const manualContentMarkerIndex = content.indexOf( + '', + ) + if (manualContentMarkerIndex > 0) { + const rawManualContent = content.substring(0, manualContentMarkerIndex).trim() + if (rawManualContent) { + manualContent = await renderContent(rawManualContent, { + ...context, + markdownRequested: true, + }) + } + } + } + } + + // Prepare data for template + const templateData = await this.prepareTemplateData( + page, + categorizedEvents, + categoryNotes, + context, + manualContent, + resolveReferenceLinksToMarkdown, + ) + + // Load and render template + const templatePath = join(__dirname, '../templates/audit-logs-page.template.md') + const templateContent = readFileSync(templatePath, 'utf8') + + // Render the template with Liquid + const rendered = await renderContent(templateContent, { + ...context, + ...templateData, + markdownRequested: true, + }) + + return rendered + } + + /** + * Prepare data for the Liquid template + */ + private async prepareTemplateData( + page: Page, + categorizedEvents: CategorizedEvents, + categoryNotes: Record, + context: Context, + manualContent: string, + resolveReferenceLinksToMarkdown: (docsReferenceLinks: string, context: any) => Promise, + ): Promise> { + // Prepare page intro + const intro = page.intro ? await page.renderProp('intro', context, { textOnly: true }) : '' + + // Sort categories and events + const sortedCategorizedEvents: CategorizedEvents = {} + const sortedCategories = Object.keys(categorizedEvents).sort((a, b) => a.localeCompare(b)) + + for (const category of sortedCategories) { + // Create a copy of the events array to avoid mutating the cache + const events = [...categorizedEvents[category]].sort((a, b) => + a.action.localeCompare(b.action), + ) + sortedCategorizedEvents[category] = await Promise.all( + events.map(async (event) => { + const newEvent = { ...event } + if (newEvent.docs_reference_links && newEvent.docs_reference_links !== 'N/A') { + newEvent.docs_reference_links = await resolveReferenceLinksToMarkdown( + newEvent.docs_reference_links, + context, + ) + } + return newEvent + }), + ) + } + + return { + page: { + title: page.title, + intro, + }, + manualContent, + categorizedEvents: sortedCategorizedEvents, + categoryNotes, + } + } +} diff --git a/src/article-api/transformers/index.ts b/src/article-api/transformers/index.ts index 1ee146a920..07231ff403 100644 --- a/src/article-api/transformers/index.ts +++ b/src/article-api/transformers/index.ts @@ -1,5 +1,6 @@ import { TransformerRegistry } from './types' import { RestTransformer } from './rest-transformer' +import { AuditLogsTransformer } from './audit-logs-transformer' import { GraphQLTransformer } from './graphql-transformer' /** @@ -8,15 +9,9 @@ import { GraphQLTransformer } from './graphql-transformer' */ export const transformerRegistry = new TransformerRegistry() -// Register REST transformer transformerRegistry.register(new RestTransformer()) - -// Register GraphQL transformer +transformerRegistry.register(new AuditLogsTransformer()) transformerRegistry.register(new GraphQLTransformer()) -// Future transformers can be registered here: -// transformerRegistry.register(new WebhooksTransformer()) -// transformerRegistry.register(new GitHubAppsTransformer()) - export { TransformerRegistry } from './types' export type { PageTransformer } from './types' diff --git a/src/audit-logs/lib/index.ts b/src/audit-logs/lib/index.ts index b81158f639..8538890f84 100644 --- a/src/audit-logs/lib/index.ts +++ b/src/audit-logs/lib/index.ts @@ -31,11 +31,61 @@ export function getCategoryNotes(): CategoryNotes { return auditLogConfig.categoryNotes || {} } -type TitleResolutionContext = Context & { +export type TitleResolutionContext = Context & { pages: Record redirects: Record } +// Resolves docs_reference_links URLs to markdown links +export async function resolveReferenceLinksToMarkdown( + docsReferenceLinks: string, + context: TitleResolutionContext, +): Promise { + if (!docsReferenceLinks || docsReferenceLinks === 'N/A') { + return '' + } + + // Handle multiple comma-separated or space-separated links + const links = docsReferenceLinks + .split(/[,\s]+/) + .map((link) => link.trim()) + .filter((link) => link && link !== 'N/A') + + const markdownLinks = [] + for (const link of links) { + try { + const page = findPage(link, context.pages, context.redirects) + if (page) { + // Create a minimal context for rendering the title + const renderContext = { + currentLanguage: 'en', + currentVersion: 'free-pro-team@latest', + pages: context.pages, + redirects: context.redirects, + } as unknown as Context + const title = await page.renderProp('title', renderContext, { textOnly: true }) + markdownLinks.push(`[${title}](${link})`) + } else { + // If we can't resolve the link, use the original URL + markdownLinks.push(link) + } + } catch (error) { + // If resolution fails, use the original URL + console.warn( + `Failed to resolve title for link: ${link}`, + error instanceof Error + ? error instanceof Error + ? error.message + : String(error) + : String(error), + ) + markdownLinks.push(link) + } + } + + return markdownLinks.join(', ') +} + // Resolves docs_reference_links URLs to page titles async function resolveReferenceLinksToTitles( docsReferenceLinks: string, diff --git a/src/fixtures/fixtures/content/admin/index.md b/src/fixtures/fixtures/content/admin/index.md new file mode 100644 index 0000000000..ac0afdc79d --- /dev/null +++ b/src/fixtures/fixtures/content/admin/index.md @@ -0,0 +1,14 @@ +--- +title: Enterprise administrator documentation +shortTitle: Enterprise administrators +intro: 'Documentation and guides for enterprise administrators.' + +changelog: + label: enterprise +layout: product-landing +versions: + ghec: '*' + ghes: '*' +children: + - /monitoring-activity-in-your-enterprise +--- diff --git a/src/fixtures/fixtures/content/admin/monitoring-activity-in-your-enterprise/index.md b/src/fixtures/fixtures/content/admin/monitoring-activity-in-your-enterprise/index.md new file mode 100644 index 0000000000..99c47f7b8c --- /dev/null +++ b/src/fixtures/fixtures/content/admin/monitoring-activity-in-your-enterprise/index.md @@ -0,0 +1,14 @@ +--- +title: Monitoring activity in your enterprise +intro: 'You can view user and system activity by leveraging audit logs.' +redirect_from: + - /enterprise/admin/installation/monitoring-activity-on-your-github-enterprise-server-instance +versions: + ghec: '*' + ghes: '*' +topics: + - Enterprise +children: + - /reviewing-audit-logs-for-your-enterprise +shortTitle: Monitor user activity +--- diff --git a/src/fixtures/fixtures/content/admin/monitoring-activity-in-your-enterprise/reviewing-audit-logs-for-your-enterprise/audit-log-events-for-your-enterprise.md b/src/fixtures/fixtures/content/admin/monitoring-activity-in-your-enterprise/reviewing-audit-logs-for-your-enterprise/audit-log-events-for-your-enterprise.md new file mode 100644 index 0000000000..36c8f6bb82 --- /dev/null +++ b/src/fixtures/fixtures/content/admin/monitoring-activity-in-your-enterprise/reviewing-audit-logs-for-your-enterprise/audit-log-events-for-your-enterprise.md @@ -0,0 +1,31 @@ +--- +title: Audit log events for your enterprise +intro: Review the events recorded in an enterprise's audit log. +shortTitle: Audit log events +permissions: 'Enterprise owners {% ifversion ghes %}and site administrators {% endif %}' +redirect_from: + - /enterprise/admin/articles/audited-actions + - /enterprise/admin/installation/audited-actions + - /enterprise/admin/user-management/audited-actions + - /admin/user-management/audited-actions + - /admin/user-management/monitoring-activity-in-your-enterprise/audited-actions +versions: + ghec: '*' + ghes: '*' +type: reference +topics: + - Auditing + - Enterprise + - Logging + - Security +autogenerated: audit-logs +--- + +> [!NOTE] This article lists events that may appear in the audit log for an **enterprise**. For the events that can appear in a user account's security log or the audit log for an organization, see [AUTOTITLE](/authentication/keeping-your-account-and-data-secure/security-log-events) and [AUTOTITLE](/organizations/keeping-your-organization-secure/managing-security-settings-for-your-organization/audit-log-events-for-your-organization). + +## What types of events are included? + +* **Without Enterprise Managed Users**, the audit log only includes events related to the enterprise account and the organizations within it. +* **With Enterprise Managed Users**, the audit log also includes user events, which are not listed here. For that list, see [AUTOTITLE](/authentication/keeping-your-account-and-data-secure/security-log-events). + + diff --git a/src/fixtures/fixtures/content/admin/monitoring-activity-in-your-enterprise/reviewing-audit-logs-for-your-enterprise/index.md b/src/fixtures/fixtures/content/admin/monitoring-activity-in-your-enterprise/reviewing-audit-logs-for-your-enterprise/index.md new file mode 100644 index 0000000000..5f97a80c80 --- /dev/null +++ b/src/fixtures/fixtures/content/admin/monitoring-activity-in-your-enterprise/reviewing-audit-logs-for-your-enterprise/index.md @@ -0,0 +1,13 @@ +--- +title: Reviewing audit logs for your enterprise +intro: You can view user and system activity in the audit logs for your enterprise. +shortTitle: Review audit logs +versions: + ghec: '*' + ghes: '*' +topics: + - Enterprise +children: + - /audit-log-events-for-your-enterprise +--- + diff --git a/src/fixtures/fixtures/content/authentication/index.md b/src/fixtures/fixtures/content/authentication/index.md new file mode 100644 index 0000000000..d4c8fa05c7 --- /dev/null +++ b/src/fixtures/fixtures/content/authentication/index.md @@ -0,0 +1,33 @@ +--- +title: Authentication documentation +shortTitle: Authentication +intro: 'Keep your account and data secure with features like two-factor authentication, SSH, and commit signature verification.' +redirect_from: + - /categories/56/articles + - /categories/ssh + - /mac-verify-ssh + - /ssh-issues + - /verify-ssh-redirect + - /win-verify-ssh + - /categories/92/articles + - /categories/gpg + - /categories/security + - /categories/authenticating-to-github + - /github/authenticating-to-github +versions: + fpt: '*' + ghes: '*' + ghec: '*' + +changelog: + label: '2FA,authentication,security keys,SSH,token authentication' +layout: product-landing +topics: + - 2FA + - Identity + - Access management + - Usernames + - Device verification +children: + - /keeping-your-account-and-data-secure +--- diff --git a/src/fixtures/fixtures/content/authentication/keeping-your-account-and-data-secure/index.md b/src/fixtures/fixtures/content/authentication/keeping-your-account-and-data-secure/index.md new file mode 100644 index 0000000000..f4d0c2b8a7 --- /dev/null +++ b/src/fixtures/fixtures/content/authentication/keeping-your-account-and-data-secure/index.md @@ -0,0 +1,17 @@ +--- +title: Keeping your account and data secure +intro: 'To protect your personal information, you should keep both your account on GitHub and any associated data secure.' +redirect_from: + - /articles/keeping-your-account-and-data-secure + - /github/authenticating-to-github/keeping-your-account-and-data-secure +versions: + fpt: '*' + ghes: '*' + ghec: '*' +topics: + - Identity + - Access management +children: + - /security-log-events +shortTitle: Account security +--- diff --git a/src/fixtures/fixtures/content/authentication/keeping-your-account-and-data-secure/security-log-events.md b/src/fixtures/fixtures/content/authentication/keeping-your-account-and-data-secure/security-log-events.md new file mode 100644 index 0000000000..2e48773098 --- /dev/null +++ b/src/fixtures/fixtures/content/authentication/keeping-your-account-and-data-secure/security-log-events.md @@ -0,0 +1,21 @@ +--- +title: Security log events +intro: Learn about security log events recorded for your personal account. +versions: + fpt: '*' + ghes: '*' + ghec: '*' +topics: + - Identity + - Access management +autogenerated: audit-logs +--- + +> [!NOTE] +> This article contains the events that may appear in your user account's security log. + +## About security log events + +Reference grouped by category. + + diff --git a/src/fixtures/fixtures/content/index.md b/src/fixtures/fixtures/content/index.md index 7b817882b2..ed523bc00b 100644 --- a/src/fixtures/fixtures/content/index.md +++ b/src/fixtures/fixtures/content/index.md @@ -35,12 +35,12 @@ children: - graphql - video-transcripts # - account-and-profile - # - authentication + - authentication # - repositories - # - admin + - admin # - billing # - site-policy - # - organizations + - organizations # - pull-requests # - issues # - copilot diff --git a/src/fixtures/fixtures/content/organizations/index.md b/src/fixtures/fixtures/content/organizations/index.md new file mode 100644 index 0000000000..da877c1e75 --- /dev/null +++ b/src/fixtures/fixtures/content/organizations/index.md @@ -0,0 +1,21 @@ +--- +title: Organizations and teams documentation +shortTitle: Organizations +intro: 'You can use organizations to collaborate with a large number of people across many projects at once, while managing access to your data and customizing settings.' +redirect_from: + - /articles/about-improved-organization-permissions + - /categories/setting-up-and-managing-organizations-and-teams + - /github/setting-up-and-managing-organizations-and-teams + - /organizations/organizing-members-into-teams/disabling-team-discussions-for-your-organization + +layout: product-landing +versions: + fpt: '*' + ghes: '*' + ghec: '*' +topics: + - Organizations + - Teams +children: + - /keeping-your-organization-secure +--- diff --git a/src/fixtures/fixtures/content/organizations/keeping-your-organization-secure/index.md b/src/fixtures/fixtures/content/organizations/keeping-your-organization-secure/index.md new file mode 100644 index 0000000000..f368912f58 --- /dev/null +++ b/src/fixtures/fixtures/content/organizations/keeping-your-organization-secure/index.md @@ -0,0 +1,18 @@ +--- +title: Keeping your organization secure +intro: 'You can harden security for your organization by managing security settings, requiring two-factor authentication (2FA), and reviewing the activity and integrations within your organization.' +redirect_from: + - /articles/preventing-unauthorized-access-to-organization-information + - /articles/keeping-your-organization-secure + - /github/setting-up-and-managing-organizations-and-teams/keeping-your-organization-secure +versions: + fpt: '*' + ghes: '*' + ghec: '*' +topics: + - Organizations + - Teams +children: + - /managing-security-settings-for-your-organization +shortTitle: Organization security +--- diff --git a/src/fixtures/fixtures/content/organizations/keeping-your-organization-secure/managing-security-settings-for-your-organization/audit-log-events-for-your-organization.md b/src/fixtures/fixtures/content/organizations/keeping-your-organization-secure/managing-security-settings-for-your-organization/audit-log-events-for-your-organization.md new file mode 100644 index 0000000000..ffdc3fe1e2 --- /dev/null +++ b/src/fixtures/fixtures/content/organizations/keeping-your-organization-secure/managing-security-settings-for-your-organization/audit-log-events-for-your-organization.md @@ -0,0 +1,22 @@ +--- +title: Audit log events for your organization +intro: Learn about audit log events recorded for your organization. +versions: + fpt: '*' + ghes: '*' + ghec: '*' +topics: + - Organizations + - Teams +shortTitle: Audit log events +autogenerated: audit-logs +--- + +> [!NOTE] +> This article contains the events that may appear in your organization's audit log. + +## About audit log events for your organization + +Reference grouped by category. + + diff --git a/src/fixtures/fixtures/content/organizations/keeping-your-organization-secure/managing-security-settings-for-your-organization/index.md b/src/fixtures/fixtures/content/organizations/keeping-your-organization-secure/managing-security-settings-for-your-organization/index.md new file mode 100644 index 0000000000..3cb126f6d4 --- /dev/null +++ b/src/fixtures/fixtures/content/organizations/keeping-your-organization-secure/managing-security-settings-for-your-organization/index.md @@ -0,0 +1,14 @@ +--- +title: Managing security settings for your organization +shortTitle: Manage security settings +intro: 'You can manage security settings and review the audit log and integrations for your organization.' +versions: + fpt: '*' + ghes: '*' + ghec: '*' +topics: + - Organizations + - Teams +children: + - /audit-log-events-for-your-organization +---