Skip to content

Commit 3214290

Browse files
Merge pull request #201 from advanced-security/knewbury01/fix-service-handler
Enhance remote flow sources for CAP
2 parents 64efaff + 674fed2 commit 3214290

File tree

10 files changed

+120
-74
lines changed

10 files changed

+120
-74
lines changed

javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/CDS.qll

Lines changed: 33 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -217,27 +217,28 @@ class CdsServeWithCall extends MethodCallNode {
217217
*/
218218
class ServiceInstanceFromServeWithParameter extends ServiceInstance {
219219
CdsServeCall cdsServe;
220+
CdsServeWithCall cdsServeWith;
220221

221222
ServiceInstanceFromServeWithParameter() {
222-
exists(CdsServeWithCall cdsServeWith |
223-
/*
224-
* cds.serve('./srv/some-service1').with ((srv) => { // Parameter `srv` is captured.
225-
* srv.on ('READ','SomeEntity1', (req) => req.reply([...]))
226-
* })
227-
*/
223+
/*
224+
* cds.serve('./srv/some-service1').with ((srv) => { // Parameter `srv` is captured.
225+
* srv.on ('READ','SomeEntity1', (req) => req.reply([...]))
226+
* })
227+
*/
228228

229-
this.(ThisNode).getBinder().asExpr() = cdsServeWith.getDecorator().(FunctionExpr)
230-
or
231-
/*
232-
* cds.serve('./srv/some-service2').with (function() { // Parameter `this` is captured.
233-
* this.on ('READ','SomeEntity2', (req) => req.reply([...]))
234-
* })
235-
*/
229+
this.(ThisNode).getBinder().asExpr() = cdsServeWith.getDecorator().(FunctionExpr)
230+
or
231+
/*
232+
* cds.serve('./srv/some-service2').with (function() { // Parameter `this` is captured.
233+
* this.on ('READ','SomeEntity2', (req) => req.reply([...]))
234+
* })
235+
*/
236236

237-
this = cdsServeWith.getDecorator().(ArrowFunctionExpr).getParameter(0).flow()
238-
)
237+
this = cdsServeWith.getDecorator().(ArrowFunctionExpr).getParameter(0).flow()
239238
}
240239

240+
HandlerRegistration getAHandlerRegistration() { result = cdsServeWith.getAHandlerRegistration() }
241+
241242
override UserDefinedApplicationService getDefinition() {
242243
/* 1. The argument to cds.serve is "all" */
243244
cdsServe.getServiceRepresentation().getStringValue() = "all" and
@@ -344,23 +345,30 @@ class HandlerRegistration extends MethodCallNode {
344345
predicate isAfter() { methodName = "after" }
345346
}
346347

348+
/**
349+
* A parameter of a handler
350+
*/
351+
class HandlerParameter instanceof ParameterNode {
352+
Handler handler;
353+
354+
HandlerParameter() { this = handler.getParameter(0) }
355+
356+
Handler getHandler() { result = handler }
357+
358+
string toString() { result = super.toString() }
359+
}
360+
347361
/**
348362
* The handler that implements a service's logic to deal with the incoming request or message when a certain event is fired.
349363
* It is the last argument to the method calls that registers the handler: either `srv.before`, `srv.on`, or `srv.after`.
350364
* Its first parameter is of type `cds.Event` and handles the event in an asynchronous manner,
351365
* or is of type `cds.Request` and handles the event synchronously.
352366
*/
353367
class Handler extends FunctionNode {
354-
UserDefinedApplicationService srv;
355368
HandlerRegistration handlerRegistration;
356369

357370
Handler() { this = handlerRegistration.getAnArgument() }
358371

359-
/**
360-
* Gets the service registering this handler.
361-
*/
362-
UserDefinedApplicationService getDefiningService() { result = srv }
363-
364372
/**
365373
* Gets a name of one of the event this handler is registered for.
366374
*/
@@ -375,6 +383,8 @@ class Handler extends FunctionNode {
375383
* Gets the request that this handler is registered for, as represented as its first parameter.
376384
*/
377385
CdsRequest getRequest() { result = this.getParameter(0) }
386+
387+
HandlerRegistration getHandlerRegistration() { result = handlerRegistration }
378388
}
379389

380390
class CdsRequest extends ParameterNode {
@@ -822,7 +832,7 @@ class HandlerParameterData instanceof PropRead {
822832
string dataName;
823833

824834
HandlerParameterData() {
825-
this = handlerParameter.getAPropertyRead("data").getAPropertyRead(dataName)
835+
this = handlerParameter.(SourceNode).getAPropertyRead("data").getAPropertyRead(dataName)
826836
}
827837

828838
/**
@@ -841,7 +851,7 @@ class HandlerParameterData instanceof PropRead {
841851
CdlActionOrFunction cdlActionOrFunction, HandlerRegistration handlerRegistration
842852
|
843853
handlerRegistration = userDefinedApplicationService.getAHandlerRegistration() and
844-
handlerRegistration = handlerParameter.getHandlerRegistration() and
854+
handlerRegistration = handlerParameter.getHandler().getHandlerRegistration() and
845855
handlerRegistration.getAnEventName() = cdlActionOrFunction.getUnqualifiedName() and
846856
cdlActionOrFunction =
847857
userDefinedApplicationService.getCdsDeclaration().getAnActionOrFunction() and

javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/RemoteFlowSources.qll

Lines changed: 19 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -2,65 +2,35 @@ import javascript
22
import advanced_security.javascript.frameworks.cap.CDS
33

44
/**
5-
* A parameter of a handler registered for a service on an event. e.g.
5+
* Either of:
6+
* a parameter of a handler registered for an (exposed) service on an event. e.g.
67
* ```javascript
78
* this.on("SomeEvent", "SomeEntity", (req) => { ... });
8-
* this.before("SomeEvent", "SomeEntity", (req, next) => { ... }); // only `req` is captured
9+
* this.before("SomeEvent", "SomeEntity", (req, next) => { ... });
910
* SomeService.on("SomeEvent", "SomeEntity", (msg) => { ... });
1011
* SomeService.after("SomeEvent", "SomeEntity", (msg) => { ... });
1112
* ```
12-
* All the parameters named `req` and `msg` are captured in the above example.
13-
*/
14-
class HandlerParameter extends ParameterNode, RemoteFlowSource {
15-
Handler handler;
16-
HandlerRegistration handlerRegistration;
17-
18-
HandlerParameter() {
19-
exists(UserDefinedApplicationService service |
20-
handler = handlerRegistration.getHandler() and
21-
this = handler.getParameter(0) and
22-
service.getAHandlerRegistration() = handlerRegistration and
23-
service.isExposed()
24-
)
25-
}
26-
27-
override string getSourceType() {
28-
result = "Parameter of an event handler belonging to an exposed service"
29-
}
30-
31-
/**
32-
* Gets the handler this is a parameter of.
33-
*/
34-
Handler getHandler() { result = handler }
35-
36-
/**
37-
* Gets the handler registration registering the handler it is a parameter of.
38-
*/
39-
HandlerRegistration getHandlerRegistration() { result = handlerRegistration }
40-
}
41-
42-
/**
43-
* A service may be described only in a CDS file, but event handlers may still be registered in a format such as:
13+
* OR
14+
* a handler parameter that is not connected to a service
15+
* possibly due to cds compilation failure
16+
* or non explicit service references in source. e.g.
4417
* ```javascript
45-
* module.exports = srv => {
46-
* srv.before('CREATE', 'Media', req => { // an entity name is used to describe which to register this handler to.
47-
* ...
48-
* });
49-
* }
18+
* cds.serve('./test-service').with((srv) => {
19+
* srv.after('READ', req => req.target.data) //req
20+
* })
5021
* ```
51-
* parameters named `req` are captured in the above example.
5222
*/
53-
class ServiceinCDSHandlerParameter extends ParameterNode, RemoteFlowSource {
54-
ServiceinCDSHandlerParameter() {
55-
exists(MethodCallNode m, CdlEntity entity, string entityName |
56-
entity.getName().regexpReplaceAll(".*\\.", "") = entityName and
57-
m.getArgument(1).asExpr().getStringValue().regexpReplaceAll("'", "") = entityName and
58-
this = m.getArgument(m.getNumArgument() - 1).(FunctionNode).getParameter(0) and
59-
m.getMethodName() in ["on", "before", "after"]
60-
)
23+
class HandlerParameterOfExposedService extends RemoteFlowSource, HandlerParameter {
24+
HandlerParameterOfExposedService() {
25+
this.getHandler().getHandlerRegistration().getService().getDefinition().isExposed()
26+
or
27+
/* no precise service definition is known */
28+
not exists(this.getHandler().getHandlerRegistration().getService().getDefinition())
6129
}
6230

31+
override string toString() { result = HandlerParameter.super.toString() }
32+
6333
override string getSourceType() {
64-
result = "Parameter of an event handler belonging to an exposed service defined in a cds file"
34+
result = "Parameter of an event handler belonging to an exposed service"
6535
}
6636
}

javascript/frameworks/cap/test/models/cds/remoteflowsources/db/schema.cds

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,8 @@ entity Entity1 {
88
entity Entity2 {
99
Attribute3 : String(100);
1010
Attribute4 : String(100)
11+
}
12+
13+
entity Entity4 {
14+
Attribute4 : String(100)
1115
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,12 @@
11
| srv/service1.js:6:29:6:31 | req |
22
| srv/service2.js:5:27:5:29 | msg |
3+
| srv/service3nocds.js:6:43:6:45 | req |
4+
| srv/service3nocds.js:7:34:7:36 | req |
5+
| srv/service3nocds.js:11:28:11:30 | req |
6+
| srv/service3nocds.js:12:26:12:28 | req |
7+
| srv/service3nocds.js:19:34:19:36 | req |
8+
| srv/service4withcds.js:5:38:5:40 | req |
9+
| srv/service4withcds.js:6:43:6:45 | req |
10+
| srv/service4withcds.js:14:33:14:35 | req |
11+
| srv/service4withcds.js:15:38:15:40 | req |
12+
| srv/service4withcds.js:16:23:16:25 | req |

javascript/frameworks/cap/test/models/cds/remoteflowsources/remoteflowsource.ql

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@ import javascript
22
import advanced_security.javascript.frameworks.cap.RemoteFlowSources
33

44
from RemoteFlowSource source
5-
select source
5+
select source
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
//this service unit test is a replica of requesthandler.js
2+
const cds = require("@sap/cds");
3+
class BooksService extends cds.ApplicationService {
4+
init() {
5+
const { Books, Authors } = this.entities
6+
this.on('READ', [Books, Authors], req => req.target.data) //req
7+
this.on('UPDATE', Books, req => { //req
8+
let [ID] = req.params
9+
return Object.assign(Books.data[ID], req.data)
10+
})
11+
this.after('READ', req => req.target.data) //req
12+
this.before('*', req => req.target.data) //req
13+
return super.init()
14+
}
15+
}
16+
module.exports = BooksService
17+
18+
cds.serve('./test-service').with((srv) => {
19+
srv.before('READ', 'Books', (req) => req.reply([])) //req
20+
})
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
using { advanced_security.log_injection.sample_entities as db_schema } from '../db/schema';
2+
3+
service Service4 @(path: '/service-4') {
4+
/* Entity to send READ/GET about. */
5+
entity Service4Entity as projection on db_schema.Entity4 excluding { Attribute4 }
6+
7+
/* API to talk to other services. */
8+
action send4 (
9+
messageToPass: String
10+
) returns String;
11+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
const cds = require("@sap/cds");
2+
3+
class TestService extends cds.ApplicationService {
4+
init() {
5+
this.before('READ', 'Test', (req) => req.reply([])) //req
6+
this.after('READ', this.entities, req => req.target.data) //req
7+
return super.init()
8+
}
9+
}
10+
module.exports = TestService
11+
12+
cds.serve('./test-service').with((srv) => {
13+
const { Test, Service4 } = this.entities
14+
srv.before('READ', 'Test', (req) => req.reply([])) //req
15+
srv.on('READ', [Test, Service4], req => req.target.data) //req
16+
srv.after('READ', req => req.target.data) //req
17+
})
Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
| requesthandler.js:5:43:5:45 | req |
22
| requesthandler.js:6:34:6:36 | req |
3-
| requesthandler.js:16:34:16:36 | req |
3+
| requesthandler.js:10:28:10:30 | req |
4+
| requesthandler.js:11:26:11:28 | req |
5+
| requesthandler.js:18:34:18:36 | req |

javascript/frameworks/cap/test/models/cds/requesthandler/requesthandler.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ class BooksService extends cds.ApplicationService {
77
let [ID] = req.params
88
return Object.assign(Books.data[ID], req.data)
99
})
10+
this.after('READ', req => req.target.data)
11+
this.before('*', req => req.target.data)
1012
return super.init()
1113
}
1214
}

0 commit comments

Comments
 (0)