Ruby: minor overhaul of ActiveResource model

This commit is contained in:
Asger F 2023-06-19 12:07:45 +02:00
Родитель 8bc4193ce0
Коммит e3a04499f6
3 изменённых файлов: 112 добавлений и 109 удалений

Просмотреть файл

@ -18,8 +18,12 @@ module ActiveResource {
* An ActiveResource model class. This is any (transitive) subclass of ActiveResource. * An ActiveResource model class. This is any (transitive) subclass of ActiveResource.
*/ */
pragma[nomagic] pragma[nomagic]
private API::Node modelApiNode() { private API::Node activeResourceBaseClass() {
result = API::getTopLevelMember("ActiveResource").getMember("Base").getASubclass() result = API::getTopLevelMember("ActiveResource").getMember("Base")
}
private DataFlow::ClassNode activeResourceClass() {
result = activeResourceBaseClass().getADescendentModule()
} }
/** /**
@ -30,16 +34,8 @@ module ActiveResource {
* end * end
* ``` * ```
*/ */
class ModelClass extends ClassDeclaration { class ModelClassNode extends DataFlow::ClassNode {
API::Node model; ModelClassNode() { this = activeResourceClass() }
ModelClass() {
model = modelApiNode() and
this.getSuperclassExpr() = model.getAValueReachableFromSource().asExpr().getExpr()
}
/** Gets the API node for this model */
API::Node getModelApiNode() { result = model }
/** Gets a call to `site=`, which sets the base URL for this model. */ /** Gets a call to `site=`, which sets the base URL for this model. */
SiteAssignCall getASiteAssignment() { result.getModelClass() = this } SiteAssignCall getASiteAssignment() { result.getModelClass() = this }
@ -49,6 +45,46 @@ module ActiveResource {
c = this.getASiteAssignment() and c = this.getASiteAssignment() and
c.disablesCertificateValidation() c.disablesCertificateValidation()
} }
/** Gets a method call on this class that returns an instance of the class. */
private DataFlow::CallNode getAChainedCall() {
result.(FindCall).getModelClass() = this
or
result.(CreateCall).getModelClass() = this
or
result.(CustomHttpCall).getModelClass() = this
or
result.(CollectionCall).getCollection().getModelClass() = this and
result.getMethodName() = ["first", "last"]
}
/** Gets an API node referring to an instance of this class. */
API::Node getAnInstanceReference() {
result = this.trackInstance()
or
result = this.getAChainedCall().track()
}
}
/** DEPRECATED. Use `ModelClassNode` instead. */
deprecated class ModelClass extends ClassDeclaration {
private ModelClassNode cls;
ModelClass() { this = cls.getADeclaration() }
/** Gets the class for which this is a declaration. */
ModelClassNode getClassNode() { result = cls }
/** Gets the API node for this class object. */
deprecated API::Node getModelApiNode() { result = cls.trackModule() }
/** Gets a call to `site=`, which sets the base URL for this model. */
SiteAssignCall getASiteAssignment() { result = cls.getASiteAssignment() }
/** Holds if `c` sets a base URL which does not use HTTPS. */
predicate disablesCertificateValidation(SiteAssignCall c) {
cls.disablesCertificateValidation(c)
}
} }
/** /**
@ -62,25 +98,20 @@ module ActiveResource {
* ``` * ```
*/ */
class ModelClassMethodCall extends DataFlow::CallNode { class ModelClassMethodCall extends DataFlow::CallNode {
API::Node model; private ModelClassNode cls;
ModelClassMethodCall() { ModelClassMethodCall() { this = cls.trackModule().getAMethodCall(_) }
model = modelApiNode() and
this = classMethodCall(model, _)
}
/** Gets the model class for this call. */ /** Gets the model class for this call. */
ModelClass getModelClass() { result.getModelApiNode() = model } ModelClassNode getModelClass() { result = cls }
} }
/** /**
* A call to `site=` on an ActiveResource model class. * A call to `site=` on an ActiveResource model class.
* This sets the base URL for all HTTP requests made by this class. * This sets the base URL for all HTTP requests made by this class.
*/ */
private class SiteAssignCall extends DataFlow::CallNode { private class SiteAssignCall extends ModelClassMethodCall {
API::Node model; SiteAssignCall() { this.getMethodName() = "site=" }
SiteAssignCall() { model = modelApiNode() and this = classMethodCall(model, "site=") }
/** /**
* Gets a node that contributes to the URLs used for HTTP requests by the parent * Gets a node that contributes to the URLs used for HTTP requests by the parent
@ -88,12 +119,10 @@ module ActiveResource {
*/ */
DataFlow::Node getAUrlPart() { result = this.getArgument(0) } DataFlow::Node getAUrlPart() { result = this.getArgument(0) }
/** Gets the model class for this call. */
ModelClass getModelClass() { result.getModelApiNode() = model }
/** Holds if this site value specifies HTTP rather than HTTPS. */ /** Holds if this site value specifies HTTP rather than HTTPS. */
predicate disablesCertificateValidation() { predicate disablesCertificateValidation() {
this.getAUrlPart() this.getAUrlPart()
// TODO: We should not need all this just to get the string value
.asExpr() .asExpr()
.(ExprNodes::AssignExprCfgNode) .(ExprNodes::AssignExprCfgNode)
.getRhs() .getRhs()
@ -141,87 +170,70 @@ module ActiveResource {
} }
/** /**
* DEPRECATED. Use `ModelClassNode.getAnInstanceReference()` instead.
*
* An ActiveResource model object. * An ActiveResource model object.
*/ */
class ModelInstance extends DataFlow::Node { deprecated class ModelInstance extends DataFlow::Node {
ModelClass cls; private ModelClassNode cls;
ModelInstance() { ModelInstance() { this = cls.getAnInstanceReference().getAValueReachableFromSource() }
exists(API::Node model | model = modelApiNode() |
this = model.getInstance().getAValueReachableFromSource() and
cls.getModelApiNode() = model
)
or
exists(FindCall call | call.flowsTo(this) | cls = call.getModelClass())
or
exists(CreateCall call | call.flowsTo(this) | cls = call.getModelClass())
or
exists(CustomHttpCall call | call.flowsTo(this) | cls = call.getModelClass())
or
exists(CollectionCall call |
call.getMethodName() = ["first", "last"] and
call.flowsTo(this)
|
cls = call.getCollection().getModelClass()
)
}
/** Gets the model class for this instance. */ /** Gets the model class for this instance. */
ModelClass getModelClass() { result = cls } ModelClassNode getModelClass() { result = cls }
} }
/** /**
* A call to a method on an ActiveResource model object. * A call to a method on an ActiveResource model object.
*/ */
class ModelInstanceMethodCall extends DataFlow::CallNode { class ModelInstanceMethodCall extends DataFlow::CallNode {
ModelInstance i; private ModelClassNode cls;
ModelInstanceMethodCall() { this.getReceiver() = i } ModelInstanceMethodCall() { this = cls.getAnInstanceReference().getAMethodCall(_) }
/** Gets the model instance for this call. */ /** Gets the model instance for this call. */
ModelInstance getInstance() { result = i } deprecated ModelInstance getInstance() { result = this.getReceiver() }
/** Gets the model class for this call. */ /** Gets the model class for this call. */
ModelClass getModelClass() { result = i.getModelClass() } ModelClassNode getModelClass() { result = cls }
} }
/** /**
* A collection of ActiveResource model objects. * DEPRECATED. Use `CollectionSource` instead.
*
* A data flow node that may refer to a collection of ActiveResource model objects.
*/ */
class Collection extends DataFlow::Node { deprecated class Collection extends DataFlow::Node {
ModelClassMethodCall classMethodCall; Collection() { this = any(CollectionSource src).track().getAValueReachableFromSource() }
Collection() {
classMethodCall.flowsTo(this) and
(
classMethodCall.getMethodName() = "all"
or
classMethodCall.getMethodName() = "find" and
classMethodCall.getArgument(0).asExpr().getConstantValue().isStringlikeValue("all")
)
} }
/** Gets the model class for this collection. */ /**
ModelClass getModelClass() { result = classMethodCall.getModelClass() } * A call that returns a collection of ActiveResource model objects.
*/
class CollectionSource extends ModelClassMethodCall {
CollectionSource() {
this.getMethodName() = "all"
or
this.getArgument(0).asExpr().getConstantValue().isStringlikeValue("all")
}
} }
/** /**
* A method call on a collection. * A method call on a collection.
*/ */
class CollectionCall extends DataFlow::CallNode { class CollectionCall extends DataFlow::CallNode {
CollectionCall() { this.getReceiver() instanceof Collection } private CollectionSource collection;
CollectionCall() { this = collection.track().getAMethodCall(_) }
/** Gets the collection for this call. */ /** Gets the collection for this call. */
Collection getCollection() { result = this.getReceiver() } CollectionSource getCollection() { result = collection }
} }
private class ModelClassMethodCallAsHttpRequest extends Http::Client::Request::Range, private class ModelClassMethodCallAsHttpRequest extends Http::Client::Request::Range,
ModelClassMethodCall ModelClassMethodCall
{ {
ModelClass cls;
ModelClassMethodCallAsHttpRequest() { ModelClassMethodCallAsHttpRequest() {
this.getModelClass() = cls and
this.getMethodName() = ["all", "build", "create", "create!", "find", "first", "last"] this.getMethodName() = ["all", "build", "create", "create!", "find", "first", "last"]
} }
@ -230,12 +242,14 @@ module ActiveResource {
override predicate disablesCertificateValidation( override predicate disablesCertificateValidation(
DataFlow::Node disablingNode, DataFlow::Node argumentOrigin DataFlow::Node disablingNode, DataFlow::Node argumentOrigin
) { ) {
cls.disablesCertificateValidation(disablingNode) and this.getModelClass().disablesCertificateValidation(disablingNode) and
// TODO: highlight real argument origin // TODO: highlight real argument origin
argumentOrigin = disablingNode argumentOrigin = disablingNode
} }
override DataFlow::Node getAUrlPart() { result = cls.getASiteAssignment().getAUrlPart() } override DataFlow::Node getAUrlPart() {
result = this.getModelClass().getASiteAssignment().getAUrlPart()
}
override DataFlow::Node getResponseBody() { result = this } override DataFlow::Node getResponseBody() { result = this }
} }
@ -243,10 +257,7 @@ module ActiveResource {
private class ModelInstanceMethodCallAsHttpRequest extends Http::Client::Request::Range, private class ModelInstanceMethodCallAsHttpRequest extends Http::Client::Request::Range,
ModelInstanceMethodCall ModelInstanceMethodCall
{ {
ModelClass cls;
ModelInstanceMethodCallAsHttpRequest() { ModelInstanceMethodCallAsHttpRequest() {
this.getModelClass() = cls and
this.getMethodName() = this.getMethodName() =
[ [
"exists?", "reload", "save", "save!", "destroy", "delete", "get", "patch", "post", "put", "exists?", "reload", "save", "save!", "destroy", "delete", "get", "patch", "post", "put",
@ -259,42 +270,15 @@ module ActiveResource {
override predicate disablesCertificateValidation( override predicate disablesCertificateValidation(
DataFlow::Node disablingNode, DataFlow::Node argumentOrigin DataFlow::Node disablingNode, DataFlow::Node argumentOrigin
) { ) {
cls.disablesCertificateValidation(disablingNode) and this.getModelClass().disablesCertificateValidation(disablingNode) and
// TODO: highlight real argument origin // TODO: highlight real argument origin
argumentOrigin = disablingNode argumentOrigin = disablingNode
} }
override DataFlow::Node getAUrlPart() { result = cls.getASiteAssignment().getAUrlPart() } override DataFlow::Node getAUrlPart() {
result = this.getModelClass().getASiteAssignment().getAUrlPart()
}
override DataFlow::Node getResponseBody() { result = this } override DataFlow::Node getResponseBody() { result = this }
} }
/**
* A call to a class method.
*
* TODO: is this general enough to be useful elsewhere?
*
* Examples:
* ```rb
* class A
* def self.m; end
*
* m # call
* end
*
* A.m # call
* ```
*/
private DataFlow::CallNode classMethodCall(API::Node classNode, string methodName) {
// A.m
result = classNode.getAMethodCall(methodName)
or
// class A
// A.m
// end
result.getReceiver().asExpr() instanceof ExprNodes::SelfVariableAccessCfgNode and
result.asExpr().getExpr().getEnclosingModule().(ClassDeclaration).getSuperclassExpr() =
classNode.getAValueReachableFromSource().asExpr().getExpr() and
result.getMethodName() = methodName
}
} }

Просмотреть файл

@ -33,6 +33,13 @@ modelInstances
| active_resource.rb:26:9:26:14 | people | | active_resource.rb:26:9:26:14 | people |
| active_resource.rb:26:9:26:20 | call to first | | active_resource.rb:26:9:26:20 | call to first |
| active_resource.rb:27:1:27:5 | alice | | active_resource.rb:27:1:27:5 | alice |
modelInstancesAsSource
| active_resource.rb:1:1:3:3 | Person | active_resource.rb:5:9:5:33 | call to new |
| active_resource.rb:1:1:3:3 | Person | active_resource.rb:8:9:8:22 | call to find |
| active_resource.rb:1:1:3:3 | Person | active_resource.rb:16:1:16:23 | call to new |
| active_resource.rb:1:1:3:3 | Person | active_resource.rb:18:1:18:22 | call to get |
| active_resource.rb:1:1:3:3 | Person | active_resource.rb:24:10:24:26 | call to find |
| active_resource.rb:1:1:3:3 | Person | active_resource.rb:26:9:26:20 | call to first |
modelInstanceMethodCalls modelInstanceMethodCalls
| active_resource.rb:6:1:6:10 | call to save | | active_resource.rb:6:1:6:10 | call to save |
| active_resource.rb:9:1:9:13 | call to address= | | active_resource.rb:9:1:9:13 | call to address= |
@ -50,3 +57,6 @@ collections
| active_resource.rb:24:1:24:26 | ... = ... | | active_resource.rb:24:1:24:26 | ... = ... |
| active_resource.rb:24:10:24:26 | call to find | | active_resource.rb:24:10:24:26 | call to find |
| active_resource.rb:26:9:26:14 | people | | active_resource.rb:26:9:26:14 | people |
collectionSources
| active_resource.rb:23:10:23:19 | call to all |
| active_resource.rb:24:10:24:26 | call to find |

Просмотреть файл

@ -3,7 +3,8 @@ import codeql.ruby.DataFlow
import codeql.ruby.frameworks.ActiveResource import codeql.ruby.frameworks.ActiveResource
query predicate modelClasses( query predicate modelClasses(
ActiveResource::ModelClass c, DataFlow::Node siteAssignCall, boolean disablesCertificateValidation ActiveResource::ModelClassNode c, DataFlow::Node siteAssignCall,
boolean disablesCertificateValidation
) { ) {
c.getASiteAssignment() = siteAssignCall and c.getASiteAssignment() = siteAssignCall and
if c.disablesCertificateValidation(siteAssignCall) if c.disablesCertificateValidation(siteAssignCall)
@ -13,8 +14,16 @@ query predicate modelClasses(
query predicate modelClassMethodCalls(ActiveResource::ModelClassMethodCall c) { any() } query predicate modelClassMethodCalls(ActiveResource::ModelClassMethodCall c) { any() }
query predicate modelInstances(ActiveResource::ModelInstance c) { any() } deprecated query predicate modelInstances(ActiveResource::ModelInstance c) { any() }
query predicate modelInstancesAsSource(
ActiveResource::ModelClassNode cls, DataFlow::LocalSourceNode node
) {
node = cls.getAnInstanceReference().asSource()
}
query predicate modelInstanceMethodCalls(ActiveResource::ModelInstanceMethodCall c) { any() } query predicate modelInstanceMethodCalls(ActiveResource::ModelInstanceMethodCall c) { any() }
query predicate collections(ActiveResource::Collection c) { any() } deprecated query predicate collections(ActiveResource::Collection c) { any() }
query predicate collectionSources(ActiveResource::CollectionSource c) { any() }