Skip to content

Commit cba90d5

Browse files
Feat/swg 15853 port validation security clean (#4995)
* feat(ls): port validation security
1 parent 573e52f commit cba90d5

File tree

14 files changed

+636
-1
lines changed

14 files changed

+636
-1
lines changed

packages/apidom-ls/src/config/codes.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,8 @@ enum ApilintCodes {
8080
SCHEMA_PATTERN_REG_EXP_ANCHORS,
8181
SCHEMA_ITEMS_REQUIRED,
8282

83+
SECURITY_REQUIREMENT_ARRAY = 14997,
84+
SECURITY_SCHEME_USED = 14998,
8385
DUPLICATE_KEYS = 14999,
8486
NOT_ALLOWED_FIELDS = 15000,
8587

@@ -835,6 +837,7 @@ enum ApilintCodes {
835837
OPENAPI2_SECURITY_SCHEME_FIELD_TOKEN_URL_REQUIRED,
836838
OPENAPI2_SECURITY_SCHEME_FIELD_SCOPES_TYPE = 3220800,
837839
OPENAPI2_SECURITY_SCHEME_FIELD_SCOPES_REQUIRED,
840+
OPENAPI2_SECURITY_SCHEME_FIELD_SCOPES_RESOLVED,
838841

839842
OPENAPI2_HEADER = 3230000,
840843
OPENAPI2_HEADER_FIELD_DESCRIPTION_TYPE = 3230100,
Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
import keysDefined2_0Lint from './keys--defined-2-0.ts';
22
import keysDefined3_0__3_1Lint from './keys--defined-3-0--3-1.ts';
3+
import valuesResolved20Lint from './values--resolved-2-0.ts';
4+
import valueTypeArrayLint from './value--type-array.ts';
35

4-
const lints = [keysDefined2_0Lint, keysDefined3_0__3_1Lint];
6+
const lints = [
7+
keysDefined2_0Lint,
8+
keysDefined3_0__3_1Lint,
9+
valuesResolved20Lint,
10+
valueTypeArrayLint,
11+
];
512

613
export default lints;
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { DiagnosticSeverity } from 'vscode-languageserver-types';
2+
3+
import ApilintCodes from '../../../codes.ts';
4+
import { LinterMeta } from '../../../../apidom-language-types.ts';
5+
import { OpenAPI2, OpenAPI30 } from '../../target-specs.ts';
6+
7+
const valueTypeArrayLint: LinterMeta = {
8+
code: ApilintCodes.SECURITY_REQUIREMENT_ARRAY,
9+
source: 'apilint',
10+
message: 'must be an array',
11+
severity: DiagnosticSeverity.Error,
12+
linterFunction: 'apilintChildrenOfType',
13+
linterParams: ['array'],
14+
targetSpecs: [...OpenAPI2, ...OpenAPI30],
15+
};
16+
17+
export default valueTypeArrayLint;
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { DiagnosticSeverity } from 'vscode-languageserver-types';
2+
3+
import ApilintCodes from '../../../codes.ts';
4+
import { LinterMeta } from '../../../../apidom-language-types.ts';
5+
import { OpenAPI2 } from '../../target-specs.ts';
6+
7+
const valuesResolved20Lint: LinterMeta = {
8+
code: ApilintCodes.OPENAPI2_SECURITY_SCHEME_FIELD_SCOPES_RESOLVED,
9+
source: 'apilint',
10+
message: 'Security scope definition could not be resolved',
11+
severity: DiagnosticSeverity.Error,
12+
linterFunction: 'apilintSecurityScopeResolved',
13+
marker: 'value',
14+
targetSpecs: OpenAPI2,
15+
};
16+
17+
export default valuesResolved20Lint;

packages/apidom-ls/src/config/openapi/security-scheme/lint/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import flowsTypeLint from './flows--type.ts';
2626
import flowsRequiredLint from './flows--required.ts';
2727
import openIdConnectUrlFormatURILint from './open-id-connect-url--format-uri.ts';
2828
import openIdConnectUrlRequiredLint from './open-id-connect-url--required.ts';
29+
import keysUsedLint from './keys--used.ts';
2930

3031
const lints = [
3132
typeEquals2_0Lint,
@@ -57,6 +58,7 @@ const lints = [
5758
allowedFields2_0Lint,
5859
allowedFields3_0Lint,
5960
allowedFields3_1Lint,
61+
keysUsedLint,
6062
];
6163

6264
export default lints;
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { DiagnosticSeverity } from 'vscode-languageserver-types';
2+
3+
import ApilintCodes from '../../../codes.ts';
4+
import { LinterMeta } from '../../../../apidom-language-types.ts';
5+
import { OpenAPI2, OpenAPI30 } from '../../target-specs.ts';
6+
7+
const keysUsedLint: LinterMeta = {
8+
code: ApilintCodes.SECURITY_SCHEME_USED,
9+
source: 'apilint',
10+
message:
11+
'Security Scheme was defined but never used. To apply security, use the `security` section in operations or on the root level of your API definition',
12+
severity: DiagnosticSeverity.Warning,
13+
linterFunction: 'apilintSecuritySchemeUsed',
14+
marker: 'key',
15+
targetSpecs: [...OpenAPI2, ...OpenAPI30],
16+
};
17+
18+
export default keysUsedLint;

packages/apidom-ls/src/services/validation/linter-functions.ts

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1334,4 +1334,73 @@ export const standardLinterfunctions: FunctionItem[] = [
13341334
return true;
13351335
},
13361336
},
1337+
{
1338+
functionName: 'apilintSecurityScopeResolved',
1339+
function: (element: Element): boolean => {
1340+
const api = root(element);
1341+
const securityDefinitions = isObject(api) && api.get('securityDefinitions');
1342+
if (!securityDefinitions || !isObject(securityDefinitions)) return true;
1343+
1344+
const hasScope = (schemeName: string, scopesFromSecurity: string[]) => {
1345+
const oneSecurityDefinition = securityDefinitions.get(schemeName);
1346+
if (!oneSecurityDefinition) return true; // returning true, because when key is not found, then keys--defined rule will come into play.
1347+
1348+
const oneSecurityDefinitionScopes = oneSecurityDefinition.get('scopes');
1349+
if (!oneSecurityDefinitionScopes) return true; // returning true, because when scopes is not found, then scope--required rule from security scheme will come into play.
1350+
1351+
return scopesFromSecurity.every(
1352+
(scopeFromSecurity: string) => !!oneSecurityDefinitionScopes.get(scopeFromSecurity),
1353+
);
1354+
};
1355+
1356+
if (isObject(element)) {
1357+
const securityRequirement = element.toValue();
1358+
for (const [schemeName, scopesFromSecurity] of Object.entries(securityRequirement)) {
1359+
if (Array.isArray(scopesFromSecurity)) {
1360+
return hasScope(schemeName, scopesFromSecurity);
1361+
}
1362+
}
1363+
}
1364+
1365+
return true;
1366+
},
1367+
},
1368+
{
1369+
functionName: 'apilintSecuritySchemeUsed',
1370+
function: (element: Element): boolean => {
1371+
if (element && element.parent && isMember(element.parent)) {
1372+
const schemeName: string =
1373+
isStringElement(element.parent.key) && element.parent.key.toValue();
1374+
const api = root(element);
1375+
const globalSecurity: ObjectElement[] = isObject(api) && api.get('security');
1376+
if (globalSecurity) {
1377+
for (const secObj of globalSecurity) {
1378+
if (secObj.hasKey(schemeName)) {
1379+
return true;
1380+
}
1381+
}
1382+
}
1383+
const paths: ObjectElement = isObject(api) && api.get('paths');
1384+
return !!paths.findElements(
1385+
(e) => {
1386+
if (isObject(e) && e.hasKey('security')) {
1387+
const opSecurity = e.get('security');
1388+
if (isArray(opSecurity)) {
1389+
return !!opSecurity.findElements(
1390+
(securityRequirementObject) =>
1391+
isObject(securityRequirementObject) &&
1392+
securityRequirementObject.hasKey(schemeName),
1393+
{},
1394+
).length;
1395+
}
1396+
}
1397+
return false;
1398+
},
1399+
{ recursive: true },
1400+
).length;
1401+
}
1402+
1403+
return true;
1404+
},
1405+
},
13371406
];
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
openapi: "3.0.3"
2+
info:
3+
title: Example
4+
version: "1.0"
5+
6+
components:
7+
securitySchemes:
8+
oauth2:
9+
type: oauth2
10+
flows:
11+
implicit:
12+
authorizationUrl: "https://auth.example.com/authorize"
13+
scopes:
14+
read:pets: "Read pets information"
15+
write:pets: "Write pets information"
16+
17+
secondScheme:
18+
type: oauth2
19+
flows:
20+
implicit:
21+
authorizationUrl: "https://auth.example.com/authorize"
22+
scopes:
23+
read:pets: "Read pets information"
24+
write:pets: "Write pets information"
25+
26+
security:
27+
- oauth2: ["read:pets", "write:pets"]
28+
- secondScheme: 'notarray'
29+
30+
paths:
31+
/pets:
32+
get:
33+
responses:
34+
"200":
35+
description: OK
36+
37+
/lic:
38+
get:
39+
security:
40+
- secondScheme: ["read:pets"]
41+
- oauth2: 'notarray'
42+
responses:
43+
"200":
44+
description: OK
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
swagger: "2.0"
2+
info:
3+
title: Example
4+
version: "1.0"
5+
6+
securityDefinitions:
7+
oauth2:
8+
type: oauth2
9+
flow: implicit
10+
authorizationUrl: "https://auth.example.com/authorize"
11+
scopes:
12+
read:pets: "Read pets information"
13+
write:pets: "Write pets information"
14+
secondScheme:
15+
type: oauth2
16+
flow: implicit
17+
authorizationUrl: "https://auth.example.com/authorize"
18+
scopes:
19+
read:pets: "Read pets information"
20+
write:pets: "Write pets information"
21+
notUsedScheme:
22+
type: oauth2
23+
flow: implicit
24+
authorizationUrl: "https://auth.example.com/authorize"
25+
scopes:
26+
read:people: "Read people information"
27+
write:people: "Write people information"
28+
security:
29+
- oauth2: ["read:pets", "write:pets"]
30+
- secondScheme: ["read:pets", "write:pets"]
31+
32+
paths:
33+
/pets:
34+
get:
35+
responses:
36+
"200":
37+
description: OK
38+
39+
/lic:
40+
get:
41+
security:
42+
- secondScheme: ['read:pets']
43+
- oauth2: ['read:pets']
44+
responses:
45+
"200":
46+
description: OK
47+
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
openapi: "3.0.3"
2+
info:
3+
title: Example
4+
version: "1.0"
5+
6+
components:
7+
securitySchemes:
8+
oauth2:
9+
type: oauth2
10+
flows:
11+
implicit:
12+
authorizationUrl: "https://auth.example.com/authorize"
13+
scopes:
14+
read:pets: "Read pets information"
15+
write:pets: "Write pets information"
16+
17+
secondScheme:
18+
type: oauth2
19+
flows:
20+
implicit:
21+
authorizationUrl: "https://auth.example.com/authorize"
22+
scopes:
23+
read:pets: "Read pets information"
24+
write:pets: "Write pets information"
25+
26+
notUsedScheme:
27+
type: oauth2
28+
flows:
29+
implicit:
30+
authorizationUrl: "https://auth.example.com/authorize"
31+
scopes:
32+
read:people: "Read people information"
33+
write:people: "Write people information"
34+
35+
security:
36+
- oauth2: ["read:pets", "write:pets"]
37+
- secondScheme: ["read:pets", "write:pets"]
38+
39+
paths:
40+
/pets:
41+
get:
42+
responses:
43+
"200":
44+
description: OK
45+
46+
/lic:
47+
get:
48+
security:
49+
- secondScheme: ["read:pets"]
50+
- oauth2: ["read:pets"]
51+
responses:
52+
"200":
53+
description: OK

0 commit comments

Comments
 (0)