Skip to content

Commit 527dba2

Browse files
Evaluate hasExtendedMetadata Liquid in secret-scanning render paths (#61622)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent ce1757f commit 527dba2

5 files changed

Lines changed: 163 additions & 4 deletions

File tree

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import { describe, expect, test, vi, beforeEach } from 'vitest'
2+
3+
import { SecretScanningTransformer } from '@/article-api/transformers/secret-scanning-transformer'
4+
import shortVersionsMiddleware from '@/versions/middleware/short-versions'
5+
import { allVersions } from '@/versions/lib/all-versions'
6+
import enterpriseServerReleases from '@/versions/lib/enterprise-server-releases'
7+
import { getSecretScanningData } from '@/secret-scanning/lib/get-secret-scanning-data'
8+
import type { Context, ExtendedRequest, Page, SecretScanningData } from '@/types'
9+
10+
vi.mock('@/secret-scanning/lib/get-secret-scanning-data')
11+
vi.mock('@/article-api/lib/load-template', () => ({
12+
loadTemplate: () => '{{ content }}',
13+
}))
14+
15+
const ghesConditional = '{% ifversion ghes %}false{% else %}true{% endif %}'
16+
17+
const makeEntry = (): SecretScanningData =>
18+
({
19+
provider: 'Example',
20+
supportedSecret: 'Example Token',
21+
secretType: 'example_token',
22+
versions: {},
23+
isPublic: true,
24+
isPrivateWithGhas: true,
25+
hasPushProtection: true,
26+
hasValidityCheck: ghesConditional,
27+
hasExtendedMetadata: ghesConditional,
28+
base64Supported: false,
29+
isduplicate: false,
30+
}) as SecretScanningData
31+
32+
const stubPage = {
33+
autogenerated: 'secret-scanning',
34+
title: 'Test',
35+
intro: '',
36+
render: vi.fn().mockResolvedValue(''),
37+
renderProp: vi.fn().mockResolvedValue(''),
38+
} as unknown as Page
39+
40+
const buildContext = async (currentVersion: string): Promise<Context> => {
41+
const req = { language: 'en', query: {} } as ExtendedRequest
42+
req.context = { currentVersion, allVersions, enterpriseServerReleases } as Context
43+
req.context.currentVersionObj = allVersions[currentVersion]
44+
await shortVersionsMiddleware(req, null, () => {})
45+
return req.context
46+
}
47+
48+
describe('SecretScanningTransformer Liquid evaluation', () => {
49+
const transformer = new SecretScanningTransformer()
50+
51+
beforeEach(() => {
52+
vi.clearAllMocks()
53+
})
54+
55+
const oldestGhes = enterpriseServerReleases.oldestSupported
56+
57+
test('resolves GHES conditionals to false on enterprise-server', async () => {
58+
vi.mocked(getSecretScanningData).mockResolvedValue([makeEntry()])
59+
const context = await buildContext(`enterprise-server@${oldestGhes}`)
60+
61+
await transformer.transform(stubPage, '/test', context)
62+
63+
expect(context.secretScanningData).toBeDefined()
64+
expect(context.secretScanningData![0].hasValidityCheck).toBe(false)
65+
expect(context.secretScanningData![0].hasExtendedMetadata).toBe(false)
66+
})
67+
68+
test('resolves GHES conditionals to true on free-pro-team', async () => {
69+
vi.mocked(getSecretScanningData).mockResolvedValue([makeEntry()])
70+
const context = await buildContext('free-pro-team@latest')
71+
72+
await transformer.transform(stubPage, '/test', context)
73+
74+
expect(context.secretScanningData).toBeDefined()
75+
expect(context.secretScanningData![0].hasValidityCheck).toBe(true)
76+
expect(context.secretScanningData![0].hasExtendedMetadata).toBe(true)
77+
})
78+
})

src/article-api/transformers/secret-scanning-transformer.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,14 +40,24 @@ export class SecretScanningTransformer implements PageTransformer {
4040

4141
// Process Liquid in values
4242
for (const entry of data) {
43-
// Only process Liquid for the hasValidityCheck field, as in the middleware
43+
// Process Liquid for the hasValidityCheck field, as in the middleware
4444
if (typeof entry.hasValidityCheck === 'string' && entry.hasValidityCheck.includes('{%')) {
4545
// Render Liquid and parse as YAML to get correct boolean type
4646
entry.hasValidityCheck = load(
4747
await liquid.parseAndRender(entry.hasValidityCheck, context),
4848
) as boolean
4949
}
5050

51+
// Process Liquid for the hasExtendedMetadata field, as in the middleware
52+
if (
53+
typeof entry.hasExtendedMetadata === 'string' &&
54+
entry.hasExtendedMetadata.includes('{%')
55+
) {
56+
entry.hasExtendedMetadata = load(
57+
await liquid.parseAndRender(entry.hasExtendedMetadata, context),
58+
) as boolean
59+
}
60+
5161
if (entry.isduplicate) {
5262
entry.secretType += ' <br/><a href="#token-versions">Token versions</a>'
5363
}

src/secret-scanning/middleware/secret-scanning.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,13 @@ export default async function secretScanning(
4848
// to execute that Liquid to get the actual value.
4949
for (const entry of req.context.secretScanningData) {
5050
for (const [key, value] of Object.entries(entry)) {
51-
if (key === 'hasValidityCheck' && typeof value === 'string' && value.includes('{%')) {
51+
if (
52+
(key === 'hasValidityCheck' || key === 'hasExtendedMetadata') &&
53+
typeof value === 'string' &&
54+
value.includes('{%')
55+
) {
5256
const evaluated = yaml.load(await liquid.parseAndRender(value, req.context))
53-
entry[key] = evaluated as string
57+
entry[key] = evaluated as boolean | string
5458
}
5559
}
5660
if (entry.isduplicate) {
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import { describe, expect, test, vi, beforeEach } from 'vitest'
2+
import { readFileSync } from 'fs'
3+
import type { Response } from 'express'
4+
5+
import secretScanning from '@/secret-scanning/middleware/secret-scanning'
6+
import shortVersionsMiddleware from '@/versions/middleware/short-versions'
7+
import { allVersions } from '@/versions/lib/all-versions'
8+
import enterpriseServerReleases from '@/versions/lib/enterprise-server-releases'
9+
import { getSecretScanningData } from '@/secret-scanning/lib/get-secret-scanning-data'
10+
import type { Context, ExtendedRequest, SecretScanningData } from '@/types'
11+
12+
vi.mock('@/secret-scanning/lib/get-secret-scanning-data')
13+
14+
const { targetFilename } = JSON.parse(
15+
readFileSync('src/secret-scanning/lib/config.json', 'utf8'),
16+
) as { targetFilename: string }
17+
18+
// Both hasValidityCheck and hasExtendedMetadata can be emitted by token-scanning-service
19+
// as a Liquid conditional that resolves to false on GHES and true elsewhere.
20+
const ghesConditional = '{% ifversion ghes %}false{% else %}true{% endif %}'
21+
22+
const makeEntry = (): SecretScanningData =>
23+
({
24+
provider: 'Example',
25+
supportedSecret: 'Example Token',
26+
secretType: 'example_token',
27+
versions: {},
28+
isPublic: true,
29+
isPrivateWithGhas: true,
30+
hasPushProtection: true,
31+
hasValidityCheck: ghesConditional,
32+
hasExtendedMetadata: ghesConditional,
33+
base64Supported: false,
34+
isduplicate: false,
35+
}) as SecretScanningData
36+
37+
const runMiddleware = async (currentVersion: string): Promise<SecretScanningData> => {
38+
vi.mocked(getSecretScanningData).mockResolvedValue([makeEntry()])
39+
40+
const req = { language: 'en', query: {}, pagePath: `/en/${targetFilename}` } as ExtendedRequest
41+
req.context = { currentVersion, allVersions, enterpriseServerReleases } as Context
42+
req.context.currentVersionObj = allVersions[currentVersion]
43+
await shortVersionsMiddleware(req, null, () => {})
44+
45+
await secretScanning(req, {} as Response, () => {})
46+
return req.context.secretScanningData![0]
47+
}
48+
49+
describe('secret-scanning middleware Liquid evaluation', () => {
50+
beforeEach(() => {
51+
vi.clearAllMocks()
52+
})
53+
54+
const oldestSupportedGhes = enterpriseServerReleases.oldestSupported
55+
56+
test('resolves GHES conditionals to false on enterprise-server', async () => {
57+
const entry = await runMiddleware(`enterprise-server@${oldestSupportedGhes}`)
58+
expect(entry.hasValidityCheck).toBe(false)
59+
expect(entry.hasExtendedMetadata).toBe(false)
60+
})
61+
62+
test('resolves GHES conditionals to true on free-pro-team', async () => {
63+
const entry = await runMiddleware('free-pro-team@latest')
64+
expect(entry.hasValidityCheck).toBe(true)
65+
expect(entry.hasExtendedMetadata).toBe(true)
66+
})
67+
})

src/types/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -280,7 +280,7 @@ export type SecretScanningData = {
280280
isPrivateWithGhas: boolean
281281
hasPushProtection: boolean
282282
hasValidityCheck: boolean | string
283-
hasExtendedMetadata?: boolean
283+
hasExtendedMetadata?: boolean | string
284284
base64Supported: boolean
285285
isduplicate: boolean
286286
}

0 commit comments

Comments
 (0)