@sap/eslint-plugin-cds 3.0.5 → 3.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +49 -0
- package/README.md +1 -1
- package/lib/api/index.js +4 -4
- package/lib/conf/all.js +17 -17
- package/lib/conf/experimental.js +12 -0
- package/lib/conf/index.js +12 -3
- package/lib/conf/recommended.js +13 -14
- package/lib/constants.js +2 -0
- package/lib/index.js +2 -1
- package/lib/parser.js +10 -1
- package/lib/rules/assoc2many-ambiguous-key.js +40 -11
- package/lib/rules/auth-no-empty-restrictions.js +38 -10
- package/lib/rules/auth-restrict-grant-service.js +29 -29
- package/lib/rules/auth-use-requires.js +27 -15
- package/lib/rules/auth-valid-restrict-grant.js +138 -82
- package/lib/rules/auth-valid-restrict-keys.js +34 -18
- package/lib/rules/auth-valid-restrict-to.js +57 -106
- package/lib/rules/auth-valid-restrict-where.js +44 -43
- package/lib/rules/extension-restrictions.js +11 -3
- package/lib/rules/index.js +5 -1
- package/lib/rules/latest-cds-version.js +5 -4
- package/lib/rules/no-db-keywords.js +14 -5
- package/lib/rules/no-dollar-prefixed-names.js +9 -2
- package/lib/rules/no-java-keywords.js +181 -0
- package/lib/rules/no-join-on-draft.js +9 -3
- package/lib/rules/sql-cast-suggestion.js +19 -15
- package/lib/rules/sql-null-comparison.js +60 -0
- package/lib/rules/start-elements-lowercase.js +6 -2
- package/lib/rules/start-entities-uppercase.js +12 -5
- package/lib/rules/valid-csv-header.js +33 -13
- package/lib/types.d.ts +4 -4
- package/lib/utils/Cache.js +4 -2
- package/lib/utils/Colors.js +2 -0
- package/lib/utils/LintError.js +17 -0
- package/lib/utils/createRule.js +160 -134
- package/lib/utils/csnTraversal.js +163 -0
- package/lib/utils/findFuzzy.js +21 -12
- package/lib/utils/getConfigPath.js +4 -2
- package/lib/utils/getConfiguredFileTypes.js +2 -0
- package/lib/utils/getFileExtensions.js +2 -0
- package/lib/utils/getProjectRootPath.js +53 -15
- package/lib/utils/isConfiguredFileType.js +8 -3
- package/lib/utils/rules.js +15 -9
- package/lib/utils/runRuleTester.js +69 -36
- package/package.json +1 -1
- package/lib/utils/genDocs.js +0 -346
|
@@ -1,7 +1,17 @@
|
|
|
1
|
-
|
|
1
|
+
'use strict'
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
|
|
3
|
+
const { findFuzzy } = require('../utils/rules')
|
|
4
|
+
|
|
5
|
+
// See https://cap.cloud.sap/docs/guides/security/authorization#restrict-annotation
|
|
6
|
+
// The combination of these events is equivalent to the virtual 'WRITE' event.
|
|
7
|
+
const SAME_AS_WRITE_EVENT = [ 'CREATE', 'DELETE', 'UPDATE', 'UPSERT' ]
|
|
8
|
+
// Note that 'INSERT' is not meant to be used by users. They should use 'CREATE' instead.
|
|
9
|
+
const VALID_EVENTS = [ ...SAME_AS_WRITE_EVENT, 'READ', 'INSERT', '*', 'WRITE']
|
|
10
|
+
|
|
11
|
+
const TYPICAL_ISSUES = {
|
|
12
|
+
__proto__: null,
|
|
13
|
+
any: '*'
|
|
14
|
+
}
|
|
5
15
|
|
|
6
16
|
module.exports = {
|
|
7
17
|
meta: {
|
|
@@ -9,99 +19,145 @@ module.exports = {
|
|
|
9
19
|
docs: {
|
|
10
20
|
description: '`@restrict.grant` must have valid values.',
|
|
11
21
|
category: 'Model Validation',
|
|
12
|
-
recommended: true
|
|
22
|
+
recommended: true,
|
|
23
|
+
url: 'https://cap.cloud.sap/docs/tools/cds-lint/meta/auth-valid-restrict-grant',
|
|
13
24
|
},
|
|
14
25
|
messages: {
|
|
15
|
-
|
|
16
|
-
|
|
26
|
+
invalidType: 'Invalid type for grant value. Must either be string or array of strings.',
|
|
27
|
+
invalidSingleType: 'Grant value must be a string.',
|
|
28
|
+
misspelledItem: "Invalid item '{{invalid}}'. Did you mean '{{candidates}}'?",
|
|
29
|
+
unknownItem: `Invalid item '{{invalid}}'. Valid: [ {{candidates}} ].`,
|
|
30
|
+
replaceBy: `Replace grant values '{{values}}' with '{{name}}'.`,
|
|
31
|
+
missingEventOrAction: "Missing event/action on '{{name}}' for `@restrict.grant`.",
|
|
32
|
+
star: 'Grant value \'*\' overrides all other grants. Replace by \'*\' only.'
|
|
17
33
|
},
|
|
18
34
|
type: 'problem',
|
|
19
35
|
model: 'inferred'
|
|
20
36
|
},
|
|
21
37
|
create (context) {
|
|
22
38
|
return {
|
|
23
|
-
|
|
39
|
+
any: checkRestrictGrant
|
|
24
40
|
}
|
|
25
41
|
|
|
26
|
-
function checkRestrictGrant
|
|
42
|
+
function checkRestrictGrant(e) {
|
|
43
|
+
if (!Array.isArray(e?.['@restrict']))
|
|
44
|
+
return
|
|
45
|
+
|
|
27
46
|
const node = context.getNode(e)
|
|
28
47
|
const file = e.$location.file
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
48
|
+
const actionNames = e.actions ? Object.keys(e.actions) : []
|
|
49
|
+
const validEventsAndActions = [ ...VALID_EVENTS, ...actionNames ]
|
|
50
|
+
|
|
51
|
+
for (const entry of e['@restrict']) {
|
|
52
|
+
if (entry?.grant !== undefined) {
|
|
53
|
+
if (!checkRestrictEntry(entry)) {
|
|
54
|
+
// max. one message per annotation, to avoid spamming the user
|
|
55
|
+
break
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Check an entry of the `@restrict` array.
|
|
62
|
+
* Returns `true` if the value is valid, `false` otherwise.
|
|
63
|
+
*
|
|
64
|
+
* @param {object} entry
|
|
65
|
+
* @returns {boolean}
|
|
66
|
+
*/
|
|
67
|
+
function checkRestrictEntry( entry ) {
|
|
68
|
+
if (typeof entry.grant === 'string') {
|
|
69
|
+
return checkSingleGrantValue(entry.grant)
|
|
70
|
+
|
|
71
|
+
} else if (!Array.isArray(entry.grant)) {
|
|
72
|
+
// neither string nor array: report invalid type
|
|
73
|
+
context.report({ messageId: 'invalidType', node, file })
|
|
74
|
+
return false
|
|
75
|
+
|
|
76
|
+
} else {
|
|
77
|
+
if (entry.grant.length === 0) {
|
|
78
|
+
context.report({
|
|
79
|
+
messageId: 'missingEventOrAction',
|
|
80
|
+
data: { name: e.name }, node, file
|
|
81
|
+
})
|
|
82
|
+
return false
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
for (const value of entry.grant) {
|
|
86
|
+
if (!checkSingleGrantValue(value))
|
|
87
|
+
return false
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// If grant values contain '*', '*' only is enough. It overrides everything.
|
|
91
|
+
if (entry.grant.length > 1 && entry.grant.includes('*')) {
|
|
92
|
+
context.report({ messageId: 'star', node, file })
|
|
93
|
+
return false
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// If the given values include all of SAME_AS_WRITE_EVENT, the user can
|
|
97
|
+
// replace them with 'WRITE'.
|
|
98
|
+
const includesWrite = SAME_AS_WRITE_EVENT.every(value => entry.grant.includes(value))
|
|
99
|
+
if (includesWrite) {
|
|
100
|
+
context.report({
|
|
101
|
+
messageId: 'replaceBy',
|
|
102
|
+
data: { values: SAME_AS_WRITE_EVENT.join(', '), name: 'WRITE' },
|
|
103
|
+
node, file
|
|
104
|
+
})
|
|
105
|
+
return false
|
|
106
|
+
}
|
|
107
|
+
return true
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Returns `true` if the value is valid, `false` otherwise.
|
|
113
|
+
*
|
|
114
|
+
* @param grant
|
|
115
|
+
* @returns {boolean}
|
|
116
|
+
*/
|
|
117
|
+
function checkSingleGrantValue(grant) {
|
|
118
|
+
if (typeof grant !== 'string') {
|
|
119
|
+
// single grant values must be strings
|
|
120
|
+
context.report({ messageId: 'invalidSingleType', node, file })
|
|
121
|
+
return false
|
|
122
|
+
}
|
|
123
|
+
if (grant.trim() === '') {
|
|
124
|
+
context.report({
|
|
125
|
+
messageId: 'missingEventOrAction',
|
|
126
|
+
data: { name: e.name }, node, file,
|
|
127
|
+
})
|
|
128
|
+
return false
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (!validEventsAndActions.includes(grant)) {
|
|
132
|
+
const candidates = TYPICAL_ISSUES[grant]
|
|
133
|
+
? [ TYPICAL_ISSUES[grant] ]
|
|
134
|
+
: findFuzzy(grant, validEventsAndActions.sort(), null, false, 2)
|
|
135
|
+
|
|
136
|
+
if (candidates.length === 0) {
|
|
137
|
+
context.report({
|
|
138
|
+
messageId: 'unknownItem',
|
|
139
|
+
data: { invalid: grant, candidates: validEventsAndActions.join(', ') }, node, file,
|
|
140
|
+
})
|
|
141
|
+
} else {
|
|
142
|
+
context.report({
|
|
143
|
+
messageId: 'misspelledItem',
|
|
144
|
+
data: { invalid: grant, candidates: candidates.join(', ') }, node, file,
|
|
145
|
+
})
|
|
103
146
|
}
|
|
147
|
+
return false
|
|
104
148
|
}
|
|
149
|
+
|
|
150
|
+
if (grant === 'INSERT') {
|
|
151
|
+
// special case: 'INSERT' is an internal event name. Users should use 'CREATE' instead.
|
|
152
|
+
context.report({
|
|
153
|
+
messageId: 'replaceBy',
|
|
154
|
+
data: { values: 'INSERT', name: 'CREATE' },
|
|
155
|
+
node, file
|
|
156
|
+
})
|
|
157
|
+
return false
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return true
|
|
105
161
|
}
|
|
106
162
|
}
|
|
107
163
|
}
|
|
@@ -1,40 +1,56 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
1
3
|
const { isEmptyObject, findFuzzy } = require('../utils/rules')
|
|
2
4
|
|
|
3
|
-
|
|
5
|
+
// These are the only valid properties inside `@restrict` annotations.
|
|
6
|
+
// They are described in <https://cap.cloud.sap/docs/guides/security/authorization#restrict-annotation>
|
|
7
|
+
const VALID_RESTRICT_PROPERTIES = ['grant', 'to', 'where']
|
|
4
8
|
|
|
5
9
|
module.exports = {
|
|
6
10
|
meta: {
|
|
7
11
|
schema: [{/* to avoid deprecation warning for ESLint 9 */}],
|
|
8
12
|
docs: {
|
|
9
|
-
description: '`@restrict` must have
|
|
13
|
+
description: '`@restrict` must not have properties besides `to`, `grant`, and `where`.',
|
|
10
14
|
category: 'Model Validation',
|
|
11
|
-
recommended: true
|
|
15
|
+
recommended: true,
|
|
16
|
+
url: 'https://cap.cloud.sap/docs/tools/cds-lint/meta/auth-valid-restrict-keys',
|
|
12
17
|
},
|
|
13
18
|
messages: {
|
|
14
|
-
|
|
19
|
+
misspelledProperty: "Misspelled or unknown property '{{invalid}}'. Did you mean '{{candidates}}'?",
|
|
20
|
+
unknownProperty: "Unknown property '{{invalid}}'. Possible: ['to', 'grant', 'where']",
|
|
15
21
|
},
|
|
16
22
|
type: 'problem',
|
|
17
23
|
model: 'inferred'
|
|
18
24
|
},
|
|
19
25
|
create (context) {
|
|
20
26
|
return {
|
|
21
|
-
|
|
27
|
+
any: checkRestrictKeys,
|
|
22
28
|
}
|
|
23
29
|
|
|
24
30
|
function checkRestrictKeys (e) {
|
|
25
|
-
if (e['@restrict'])
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
31
|
+
if (!Array.isArray(e?.['@restrict']))
|
|
32
|
+
return
|
|
33
|
+
|
|
34
|
+
for (const entry of e['@restrict']) {
|
|
35
|
+
if (entry && typeof entry === 'object' && !isEmptyObject(entry)) {
|
|
36
|
+
for (const key of Object.keys(entry)) {
|
|
37
|
+
if (VALID_RESTRICT_PROPERTIES.includes(key))
|
|
38
|
+
continue
|
|
39
|
+
const candidates = findFuzzy(key, VALID_RESTRICT_PROPERTIES.sort(), null, false, 2)
|
|
40
|
+
if (candidates.length === 0) {
|
|
41
|
+
context.report({
|
|
42
|
+
messageId: 'unknownProperty',
|
|
43
|
+
data: { invalid: key },
|
|
44
|
+
node: context.getNode(e),
|
|
45
|
+
file: e.$location.file
|
|
46
|
+
})
|
|
47
|
+
} else {
|
|
48
|
+
context.report({
|
|
49
|
+
messageId: 'misspelledProperty',
|
|
50
|
+
data: { invalid: key, candidates },
|
|
51
|
+
node: context.getNode(e),
|
|
52
|
+
file: e.$location.file
|
|
53
|
+
})
|
|
38
54
|
}
|
|
39
55
|
}
|
|
40
56
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
'use strict'
|
|
2
2
|
|
|
3
|
-
const VALID_PSEUDO_ROLES = ['authenticated-user', 'system-user', 'any']
|
|
3
|
+
const VALID_PSEUDO_ROLES = new Set(['authenticated-user', 'system-user', 'internal-user', 'any'])
|
|
4
4
|
|
|
5
5
|
module.exports = {
|
|
6
6
|
meta: {
|
|
@@ -8,125 +8,76 @@ module.exports = {
|
|
|
8
8
|
docs: {
|
|
9
9
|
description: '`@restrict.to` must have valid values.',
|
|
10
10
|
category: 'Model Validation',
|
|
11
|
-
recommended: true
|
|
11
|
+
recommended: true,
|
|
12
|
+
url: 'https://cap.cloud.sap/docs/tools/cds-lint/meta/auth-valid-restrict-to',
|
|
12
13
|
},
|
|
13
|
-
hasSuggestions: true,
|
|
14
14
|
messages: {
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
invalidType: 'Invalid type for value of `@restrict.to`. Must either be string or array of strings.',
|
|
16
|
+
pseudoRoleTypo: "Did you mean pseudo-role '{{valid}}' instead of '{{role}}'?",
|
|
17
|
+
any: 'Role \'any\' overrides all other roles. Replace by \'any\' only.'
|
|
17
18
|
},
|
|
18
19
|
type: 'problem',
|
|
19
20
|
model: 'inferred'
|
|
20
21
|
},
|
|
21
22
|
create (context) {
|
|
22
23
|
return {
|
|
23
|
-
entity: checkRestrictTo
|
|
24
|
+
entity: checkRestrictTo,
|
|
25
|
+
service: checkRestrictTo,
|
|
26
|
+
action: checkRestrictTo,
|
|
27
|
+
function: checkRestrictTo,
|
|
24
28
|
}
|
|
25
29
|
|
|
26
|
-
function checkRestrictTo
|
|
27
|
-
|
|
28
|
-
|
|
30
|
+
function checkRestrictTo(def) {
|
|
31
|
+
// TODO: This check also applies to `@requires`. Test that.
|
|
32
|
+
if (!Array.isArray(def?.['@restrict']))
|
|
33
|
+
return
|
|
29
34
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
if (p.to) {
|
|
34
|
-
switch (typeof p.to) {
|
|
35
|
-
case 'string':
|
|
36
|
-
if (p.to !== p.to.toLowerCase() && !USER_ROLES.includes(p.to)) {
|
|
37
|
-
USER_ROLES.push(p.to)
|
|
38
|
-
}
|
|
39
|
-
break
|
|
40
|
-
case 'object':
|
|
41
|
-
for (const r in p.to) {
|
|
42
|
-
if (r !== r.toLowerCase() && !USER_ROLES.includes(r)) {
|
|
43
|
-
USER_ROLES.push(r)
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
})
|
|
49
|
-
}
|
|
50
|
-
})
|
|
51
|
-
const ROLES = USER_ROLES.concat(VALID_PSEUDO_ROLES)
|
|
52
|
-
|
|
53
|
-
if (e['@restrict']) {
|
|
54
|
-
const node = context.getNode(e)
|
|
55
|
-
const file = e.$location.file
|
|
56
|
-
|
|
57
|
-
// TODO: For hierachies, check whether service restriction exists
|
|
58
|
-
// const { prefix } = splitDefName(e)
|
|
59
|
-
// const prefixSplit = prefix.split('.')
|
|
60
|
-
// const serviceName = prefixSplit[prefixSplit.length - 1]
|
|
61
|
-
// const services = model.services
|
|
62
|
-
// let grantAllTo;
|
|
63
|
-
// Object.values(services).map((s) => {
|
|
64
|
-
// if (s.name === serviceName && s['@requires']) {
|
|
65
|
-
// grantAllTo = s['@requires'];
|
|
66
|
-
// }
|
|
67
|
-
// })
|
|
35
|
+
const node = context.getNode(def)
|
|
36
|
+
const file = def.$location.file
|
|
37
|
+
def['@restrict'].forEach(checkRestrictEntry)
|
|
68
38
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
39
|
+
function checkRestrictEntry(entry) {
|
|
40
|
+
if (entry?.to !== undefined) {
|
|
41
|
+
const roles = getUserRoles(entry)
|
|
42
|
+
if (roles.every(checkRole)) {
|
|
43
|
+
// all roles are valid
|
|
44
|
+
if (roles.length > 1 && roles.includes('any'))
|
|
45
|
+
context.report({ messageId: 'any', node, file })
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
72
49
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
messageId: 'InvalidItem',
|
|
87
|
-
data: { invalid: toValue, candidates },
|
|
88
|
-
node,
|
|
89
|
-
file
|
|
90
|
-
})
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
break
|
|
94
|
-
}
|
|
50
|
+
function getUserRoles(entry) {
|
|
51
|
+
if (typeof entry.to === 'string') {
|
|
52
|
+
return [ entry.to ]
|
|
53
|
+
}
|
|
54
|
+
else if (Array.isArray(entry.to)) {
|
|
55
|
+
return entry.to
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
// neither string nor array: report invalid type
|
|
59
|
+
context.report({ messageId: 'invalidType', node, file })
|
|
60
|
+
return []
|
|
61
|
+
}
|
|
62
|
+
}
|
|
95
63
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
})
|
|
112
|
-
}
|
|
113
|
-
toValue.forEach((value) => {
|
|
114
|
-
if (!ROLES.includes(value)) {
|
|
115
|
-
const candidates = findFuzzy(value, ROLES.sort(), undefined, false, 2)
|
|
116
|
-
if (candidates.length > 0) {
|
|
117
|
-
context.report({
|
|
118
|
-
messageId: 'InvalidItem',
|
|
119
|
-
data: { invalid: value, candidates },
|
|
120
|
-
node,
|
|
121
|
-
file
|
|
122
|
-
})
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
})
|
|
126
|
-
}
|
|
127
|
-
break
|
|
128
|
-
}
|
|
64
|
+
function checkRole(role) {
|
|
65
|
+
if (typeof role !== 'string') {
|
|
66
|
+
context.report({ messageId: 'invalidType', node, file })
|
|
67
|
+
return false
|
|
68
|
+
}
|
|
69
|
+
else if (role !== '') {
|
|
70
|
+
// empty roles handled by auth-no-empty-restrictions
|
|
71
|
+
const roleLowercase = role.toLowerCase()
|
|
72
|
+
if (roleLowercase !== role && VALID_PSEUDO_ROLES.has(roleLowercase)) {
|
|
73
|
+
context.report({
|
|
74
|
+
messageId: 'pseudoRoleTypo',
|
|
75
|
+
data: { role, valid: roleLowercase },
|
|
76
|
+
node, file,
|
|
77
|
+
})
|
|
78
|
+
return false
|
|
129
79
|
}
|
|
80
|
+
return true
|
|
130
81
|
}
|
|
131
82
|
}
|
|
132
83
|
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
1
3
|
const cds = require('@sap/cds')
|
|
2
4
|
|
|
3
5
|
module.exports = {
|
|
@@ -6,65 +8,64 @@ module.exports = {
|
|
|
6
8
|
docs: {
|
|
7
9
|
description: '`@restrict.where` must have valid values.',
|
|
8
10
|
category: 'Model Validation',
|
|
9
|
-
recommended: true
|
|
11
|
+
recommended: true,
|
|
12
|
+
url: 'https://cap.cloud.sap/docs/tools/cds-lint/meta/auth-valid-restrict-where',
|
|
10
13
|
},
|
|
11
14
|
severity: 'error',
|
|
12
|
-
hasSuggestions: true,
|
|
13
15
|
messages: {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
+
compilationFailed: 'Invalid `where` expression, CDS compilation failed.',
|
|
17
|
+
invalidType: 'Invalid `where` type. Must be a string or expression.',
|
|
16
18
|
},
|
|
17
19
|
type: 'problem',
|
|
18
20
|
model: 'inferred'
|
|
19
21
|
},
|
|
20
22
|
create (context) {
|
|
21
23
|
const model = context.getModel()
|
|
24
|
+
if (!model)
|
|
25
|
+
return
|
|
22
26
|
|
|
23
|
-
return {
|
|
24
|
-
entity
|
|
27
|
+
return function checkRestrictWhere() {
|
|
28
|
+
model.foreach('entity', checkEntityRestrict)
|
|
25
29
|
}
|
|
26
30
|
|
|
27
|
-
function
|
|
28
|
-
|
|
31
|
+
function checkEntityRestrict (def) {
|
|
32
|
+
if (!Array.isArray(def['@restrict']))
|
|
33
|
+
return
|
|
34
|
+
|
|
35
|
+
for (const entry of def['@restrict']) {
|
|
36
|
+
if (entry?.where !== undefined) {
|
|
37
|
+
// TODO: Check return value (where xpr)
|
|
38
|
+
checkAndCompileWhereExpression(entry.where)
|
|
39
|
+
}
|
|
40
|
+
}
|
|
29
41
|
|
|
30
|
-
|
|
31
|
-
if (
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
USER_ROLES.push(p.to)
|
|
38
|
-
}
|
|
39
|
-
break
|
|
40
|
-
case 'object':
|
|
41
|
-
for (const r in p.to) {
|
|
42
|
-
if (r !== r.toLowerCase() && !USER_ROLES.includes(r)) {
|
|
43
|
-
USER_ROLES.push(r)
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
}
|
|
42
|
+
function checkAndCompileWhereExpression(where) {
|
|
43
|
+
if (where === null || Array.isArray(where) ||
|
|
44
|
+
(typeof where !== 'object' && typeof where !== 'string')) {
|
|
45
|
+
context.report({
|
|
46
|
+
messageId: 'invalidType',
|
|
47
|
+
node: context.getNode(def),
|
|
48
|
+
file: def.$location.file,
|
|
48
49
|
})
|
|
50
|
+
return null
|
|
49
51
|
}
|
|
50
|
-
})
|
|
51
52
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
53
|
+
try {
|
|
54
|
+
const compileOptions = {}
|
|
55
|
+
return typeof where === 'string'
|
|
56
|
+
? cds.parse.expr(where, compileOptions)
|
|
57
|
+
: where
|
|
58
|
+
|
|
59
|
+
} catch(e) {
|
|
60
|
+
if (e.code !== 'ERR_CDS_COMPILATION_FAILURE')
|
|
61
|
+
throw e
|
|
62
|
+
|
|
63
|
+
context.report({
|
|
64
|
+
messageId: 'compilationFailed',
|
|
65
|
+
node: context.getNode(def),
|
|
66
|
+
file: def.$location.file,
|
|
67
|
+
})
|
|
68
|
+
return null
|
|
68
69
|
}
|
|
69
70
|
}
|
|
70
71
|
}
|