Skip to content

Commit 1dc10d9

Browse files
committed
fix: only include root security and security schemes relevant for operation when creating single operation specification
1 parent 68b1306 commit 1dc10d9

File tree

9 files changed

+338
-23
lines changed

9 files changed

+338
-23
lines changed

src/apitypes/rest/rest.operation.ts

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ import {
4747
takeIfDefined,
4848
} from '../../utils'
4949
import { API_KIND, INLINE_REFS_FLAG, ORIGINS_SYMBOL, VERSION_STATUS } from '../../consts'
50-
import { getCustomTags, resolveApiAudience } from './rest.utils'
50+
import { extractSecuritySchemesNames, getCustomTags, resolveApiAudience } from './rest.utils'
5151
import { DebugPerformanceContext, syncDebugPerformance } from '../../utils/logs'
5252
import {
5353
calculateDeprecatedItems,
@@ -142,13 +142,15 @@ export const buildRestOperation = (
142142
const models: Record<string, string> = {}
143143
const apiKind = effectiveOperationObject[REST_KIND_KEY] || document.apiKind || API_KIND.BWC
144144
const [specWithSingleOperation] = syncDebugPerformance('[ModelsAndOperationHashing]', () => {
145+
const operationSecurity = effectiveOperationObject.security
145146
const specWithSingleOperation = createSingleOperationSpec(
146147
document.data,
147148
path,
148149
method,
149150
openapi,
150151
servers,
151152
security,
153+
operationSecurity,
152154
components?.securitySchemes,
153155
)
154156
calculateSpecRefs(document.data, refsOnlySingleOperationSpec, specWithSingleOperation, [operationId], models, componentsHashMap)
@@ -320,22 +322,32 @@ const isOperationPaths = (paths: JsonPath[]): boolean => {
320322
// todo output of this method disrupts document normalization.
321323
// origin symbols are not being transferred to the resulting spec.
322324
// DO NOT pass output of this method to apiDiff
323-
const createSingleOperationSpec = (
325+
export const createSingleOperationSpec = (
324326
document: OpenAPIV3.Document,
325327
path: string,
326328
method: OpenAPIV3.HttpMethods,
327329
openapi?: string,
328330
servers?: OpenAPIV3.ServerObject[],
329331
security?: OpenAPIV3.SecurityRequirementObject[],
332+
operationSecurity?: OpenAPIV3.SecurityRequirementObject[],
330333
securitySchemes?: { [p: string]: OpenAPIV3.ReferenceObject | OpenAPIV3.SecuritySchemeObject },
331334
): TYPE.RestOperationData => {
332335
const pathData = document.paths[path] as OpenAPIV3.PathItemObject
333336

337+
// Filter security schemes to only include used ones
338+
const effectiveSecurity = operationSecurity ?? security ?? []
339+
const usedSecuritySchemeNames = extractSecuritySchemesNames(effectiveSecurity)
340+
const effectiveSecuritySchemes = securitySchemes && usedSecuritySchemeNames.size > 0
341+
? Object.fromEntries(
342+
Object.entries(securitySchemes).filter(([name]) => usedSecuritySchemeNames.has(name)),
343+
)
344+
: undefined
345+
334346
const isRefPathData = !!pathData.$ref
335347
return {
336348
openapi: openapi ?? '3.0.0',
337349
...takeIfDefined({ servers }),
338-
...takeIfDefined({ security }), // TODO: remove duplicates in security
350+
...!operationSecurity ? takeIfDefined({ security }) : {},// Only add root security if operation security is not explicitly defined
339351
paths: {
340352
[path]: isRefPathData
341353
? pathData
@@ -346,7 +358,7 @@ const createSingleOperationSpec = (
346358
},
347359
},
348360
components: {
349-
...takeIfDefined({ securitySchemes }),
361+
...takeIfDefined({ securitySchemes: effectiveSecuritySchemes }),
350362
},
351363
}
352364
}

test/helpers/matchers.ts

Lines changed: 51 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,11 @@ import {
3131
} from '../../src'
3232
import { JsonPath } from 'json-crawl'
3333
import { ActionType } from '@netcracker/qubership-apihub-api-diff'
34-
import { ArrayContaining, ExpectedRecursive, ObjectContaining, RecursiveMatcher } from '../../.jest/jasmin'
34+
import { ArrayContaining, AsymmetricMatcher, ExpectedRecursive, ObjectContaining, RecursiveMatcher } from '../../.jest/jasmin'
35+
import { extractSecuritySchemesNames } from '../../src/apitypes/rest/rest.utils'
36+
import type { OpenAPIV3 } from 'openapi-types'
37+
38+
type SecuritySchemesObject = OpenAPIV3.ComponentsObject['securitySchemes']
3539

3640
export type ApihubComparisonMatcher = ObjectContaining<VersionsComparison> & VersionsComparison
3741
export type ApihubOperationChangesMatcher = ObjectContaining<OperationChanges> & OperationChanges
@@ -102,27 +106,27 @@ export function operationTypeMatcher(
102106
expected: RecursiveMatcher<OperationType>,
103107
): ApihubChangesSummaryMatcher {
104108
return expect.objectContaining({
105-
comparisons: expect.arrayContaining([
106-
expect.objectContaining({
107-
operationTypes: expect.arrayContaining([
108-
expect.objectContaining(expected),
109-
]),
110-
}),
111-
]),
112-
},
109+
comparisons: expect.arrayContaining([
110+
expect.objectContaining({
111+
operationTypes: expect.arrayContaining([
112+
expect.objectContaining(expected),
113+
]),
114+
}),
115+
]),
116+
},
113117
)
114118
}
115119

116120
export function operationChangesMatcher(
117121
expected: Array<ExpectedRecursive<OperationChanges>>,
118122
): ApihubOperationChangesMatcher {
119123
return expect.objectContaining({
120-
comparisons: expect.arrayContaining([
121-
expect.objectContaining({
122-
data: expect.toIncludeSameMembers(expected),
123-
}),
124-
]),
125-
},
124+
comparisons: expect.arrayContaining([
125+
expect.objectContaining({
126+
data: expect.toIncludeSameMembers(expected),
127+
}),
128+
]),
129+
},
126130
)
127131
}
128132

@@ -166,8 +170,8 @@ export function notificationsMatcher(
166170
expected: Array<RecursiveMatcher<NotificationMessage>>,
167171
): ApihubNotificationsMatcher {
168172
return expect.objectContaining({
169-
notifications: expect.toIncludeSameMembers(expected),
170-
},
173+
notifications: expect.toIncludeSameMembers(expected),
174+
},
171175
)
172176
}
173177

@@ -184,8 +188,8 @@ export function exportDocumentsMatcher(
184188
expected: Array<RecursiveMatcher<ZippableDocument>>,
185189
): ApihubExportDocumentsMatcher {
186190
return expect.objectContaining({
187-
exportDocuments: expect.toIncludeSameMembers(expected),
188-
},
191+
exportDocuments: expect.toIncludeSameMembers(expected),
192+
},
189193
)
190194
}
191195

@@ -196,3 +200,31 @@ export function exportDocumentMatcher(
196200
filename: filename,
197201
})
198202
}
203+
204+
/**
205+
* Custom matcher to verify that security schemes in result match exactly the schemes used in security requirements
206+
*/
207+
export function securitySchemesFromRequirementsMatcher(
208+
securityRequirements: OpenAPIV3.SecurityRequirementObject[],
209+
): AsymmetricMatcher<SecuritySchemesObject> {
210+
const expectedSchemes = Array.from(extractSecuritySchemesNames(securityRequirements))
211+
212+
return {
213+
asymmetricMatch: (actual: SecuritySchemesObject): boolean => {
214+
if (!actual || typeof actual !== 'object') {
215+
return false
216+
}
217+
218+
const actualSchemes = Object.keys(actual)
219+
220+
// Check that all expected schemes are present
221+
const hasAllExpected = expectedSchemes.every(scheme => actualSchemes.includes(scheme))
222+
223+
// Check that no extra schemes are present
224+
const hasOnlyExpected = actualSchemes.every(scheme => expectedSchemes.includes(scheme))
225+
226+
return hasAllExpected && hasOnlyExpected
227+
},
228+
jasmineToString: () => `securitySchemesFromRequirements(${JSON.stringify(expectedSchemes)})`,
229+
}
230+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
openapi: 3.0.3
2+
info:
3+
title: Conditional Security Test
4+
version: 1.0.0
5+
security:
6+
- bearerAuth: []
7+
paths:
8+
/test:
9+
get:
10+
summary: Test operation with explicit security
11+
security:
12+
- apiKey: []
13+
responses:
14+
'200':
15+
description: OK
16+
components:
17+
securitySchemes:
18+
bearerAuth:
19+
type: http
20+
scheme: bearer
21+
apiKey:
22+
type: apiKey
23+
in: header
24+
name: X-API-Key
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
openapi: 3.0.3
2+
info:
3+
title: Security Filtering Root Test
4+
version: 1.0.0
5+
paths:
6+
/test:
7+
get:
8+
summary: Test operation
9+
security: []
10+
responses:
11+
'200':
12+
description: OK
13+
components:
14+
securitySchemes:
15+
bearerAuth:
16+
type: http
17+
scheme: bearer
18+
apiKeyAuth:
19+
type: apiKey
20+
in: header
21+
name: X-API-Key
22+
unusedAuth:
23+
type: http
24+
scheme: basic
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
openapi: 3.0.3
2+
info:
3+
title: Security Filtering Root Test
4+
version: 1.0.0
5+
security: []
6+
paths:
7+
/test:
8+
get:
9+
summary: Test operation
10+
responses:
11+
'200':
12+
description: OK
13+
components:
14+
securitySchemes:
15+
bearerAuth:
16+
type: http
17+
scheme: bearer
18+
apiKeyAuth:
19+
type: apiKey
20+
in: header
21+
name: X-API-Key
22+
unusedAuth:
23+
type: http
24+
scheme: basic
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
openapi: 3.0.3
2+
info:
3+
title: Security Filtering Test
4+
version: 1.0.0
5+
paths:
6+
/test:
7+
get:
8+
summary: Test operation with explicit security
9+
responses:
10+
'200':
11+
description: OK
12+
components:
13+
securitySchemes:
14+
bearerAuth:
15+
type: http
16+
scheme: bearer
17+
apiKeyAuth:
18+
type: apiKey
19+
in: header
20+
name: X-API-Key
21+
unusedAuth:
22+
type: http
23+
scheme: basic
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
openapi: 3.0.3
2+
info:
3+
title: Security Filtering Test
4+
version: 1.0.0
5+
paths:
6+
/test:
7+
get:
8+
summary: Test operation with explicit security
9+
security:
10+
- bearerAuth: []
11+
responses:
12+
'200':
13+
description: OK
14+
components:
15+
securitySchemes:
16+
bearerAuth:
17+
type: http
18+
scheme: bearer
19+
apiKeyAuth:
20+
type: apiKey
21+
in: header
22+
name: X-API-Key
23+
unusedAuth:
24+
type: http
25+
scheme: basic
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
openapi: 3.0.3
2+
info:
3+
title: Security Filtering Root Test
4+
version: 1.0.0
5+
security:
6+
- bearerAuth: []
7+
paths:
8+
/test:
9+
get:
10+
summary: Test operation
11+
responses:
12+
'200':
13+
description: OK
14+
components:
15+
securitySchemes:
16+
bearerAuth:
17+
type: http
18+
scheme: bearer
19+
apiKeyAuth:
20+
type: apiKey
21+
in: header
22+
name: X-API-Key
23+
unusedAuth:
24+
type: http
25+
scheme: basic

0 commit comments

Comments
 (0)