mirror of
https://github.com/github/codeql.git
synced 2026-04-28 18:25:24 +02:00
Ruby: Model ActiveResource
This commit is contained in:
274
ruby/ql/lib/codeql/ruby/frameworks/ActiveResource.qll
Normal file
274
ruby/ql/lib/codeql/ruby/frameworks/ActiveResource.qll
Normal file
@@ -0,0 +1,274 @@
|
||||
/**
|
||||
* Provides modeling for the `ActiveResource` library.
|
||||
* Version: 6.0.0.
|
||||
*/
|
||||
|
||||
private import ruby
|
||||
private import codeql.ruby.Concepts
|
||||
private import codeql.ruby.controlflow.CfgNodes
|
||||
private import codeql.ruby.ast.internal.Module
|
||||
private import codeql.ruby.DataFlow
|
||||
private import codeql.ruby.ApiGraphs
|
||||
|
||||
/**
|
||||
* Provides modeling for the `ActiveResource` library.
|
||||
* Version: 6.0.0.
|
||||
*/
|
||||
module ActiveResource {
|
||||
/**
|
||||
* An ActiveResource model class. This is any (transitive) subclass of ActiveResource.
|
||||
*/
|
||||
private API::Node modelApiNode() {
|
||||
result = API::getTopLevelMember("ActiveResource").getMember("Base").getASubclass+()
|
||||
}
|
||||
|
||||
/**
|
||||
* An ActiveResource class.
|
||||
*
|
||||
* ``rb
|
||||
* class Person < ActiveResource::Base
|
||||
* end
|
||||
* ```
|
||||
*/
|
||||
class ModelClass extends ClassDeclaration {
|
||||
API::Node model;
|
||||
|
||||
ModelClass() {
|
||||
model = modelApiNode() and
|
||||
this.getSuperclassExpr() = model.getAValueReachableFromSource().asExpr().getExpr()
|
||||
}
|
||||
|
||||
API::Node getModelApiNode() { result = model }
|
||||
|
||||
SiteAssignCall getASiteAssignment() { result.getModelClass() = this }
|
||||
|
||||
predicate disablesCertificateValidation(SiteAssignCall c) {
|
||||
c = this.getASiteAssignment() and
|
||||
c.disablesCertificateValidation()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to a class method on an ActiveResource model class.
|
||||
*
|
||||
* ```rb
|
||||
* class Person < ActiveResource::Base
|
||||
* end
|
||||
*
|
||||
* Person.find(1)
|
||||
* ```
|
||||
*/
|
||||
class ModelClassMethodCall extends DataFlow::CallNode {
|
||||
API::Node model;
|
||||
|
||||
ModelClassMethodCall() {
|
||||
model = modelApiNode() and
|
||||
this = classMethodCall(model, _)
|
||||
}
|
||||
|
||||
ModelClass getModelClass() { result.getModelApiNode() = model }
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to `site=` on an ActiveResource model class.
|
||||
* This sets the base URL for all HTTP requests made by this class.
|
||||
*/
|
||||
private class SiteAssignCall extends DataFlow::CallNode {
|
||||
API::Node model;
|
||||
|
||||
SiteAssignCall() { model = modelApiNode() and this = classMethodCall(model, "site=") }
|
||||
|
||||
/**
|
||||
* A node that contributes to the URLs used for HTTP requests by the parent
|
||||
* class.
|
||||
*/
|
||||
DataFlow::Node getAUrlPart() { result = this.getArgument(0) }
|
||||
|
||||
ModelClass getModelClass() { result.getModelApiNode() = model }
|
||||
|
||||
predicate disablesCertificateValidation() {
|
||||
this.getAUrlPart().asExpr().getConstantValue().getString().regexpMatch("^http(^s)")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to the `find` class method, which returns an ActiveResource model
|
||||
* object.
|
||||
*
|
||||
* ```rb
|
||||
* alice = Person.find(1)
|
||||
* ```
|
||||
*/
|
||||
class FindCall extends ModelClassMethodCall {
|
||||
FindCall() { this.getMethodName() = "find" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to the `create(!)` class method, which returns an ActiveResource model
|
||||
* object.
|
||||
*
|
||||
* ```rb
|
||||
* alice = Person.create(name: "alice")
|
||||
* ```
|
||||
*/
|
||||
class CreateCall extends ModelClassMethodCall {
|
||||
CreateCall() { this.getMethodName() = ["create", "create!"] }
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to a method that sends a custom HTTP request outside of the
|
||||
* ActiveResource conventions. This typically returns an ActiveResource model
|
||||
* object. It may return a collection, but we don't currently model that.
|
||||
*
|
||||
* ```rb
|
||||
* alice = Person.get(:active)
|
||||
* ```
|
||||
*/
|
||||
class CustomHttpCall extends ModelClassMethodCall {
|
||||
CustomHttpCall() { this.getMethodName() = ["get", "post", "put", "patch", "delete"] }
|
||||
}
|
||||
|
||||
/**
|
||||
* An ActiveResource model object.
|
||||
*/
|
||||
class ModelInstance extends DataFlow::Node {
|
||||
ModelClass cls;
|
||||
|
||||
ModelInstance() {
|
||||
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()
|
||||
)
|
||||
}
|
||||
|
||||
ModelClass getModelClass() { result = cls }
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to a method on an ActiveResource model object.
|
||||
*/
|
||||
class ModelInstanceMethodCall extends DataFlow::CallNode {
|
||||
ModelInstance i;
|
||||
|
||||
ModelInstanceMethodCall() { this.getReceiver() = i }
|
||||
|
||||
ModelInstance getInstance() { result = i }
|
||||
|
||||
ModelClass getModelClass() { result = this.getReceiver().(ModelInstance).getModelClass() }
|
||||
}
|
||||
|
||||
/**
|
||||
*A collection of ActiveResource model objects.
|
||||
*/
|
||||
class Collection extends DataFlow::Node {
|
||||
ModelClassMethodCall classMethodCall;
|
||||
|
||||
Collection() {
|
||||
exists(ModelClassMethodCall c | c.flowsTo(this) |
|
||||
c.getMethodName() = "all"
|
||||
or
|
||||
c.getMethodName() = "find" and
|
||||
c.getArgument(0).asExpr().getConstantValue().isStringlikeValue("all")
|
||||
)
|
||||
}
|
||||
|
||||
ModelClass getModelClass() { result = classMethodCall.getModelClass() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A method call on a collection.
|
||||
*/
|
||||
class CollectionCall extends DataFlow::CallNode {
|
||||
CollectionCall() { this.getReceiver() instanceof Collection }
|
||||
|
||||
Collection getCollection() { result = this.getReceiver() }
|
||||
}
|
||||
|
||||
private class ModelClassMethodCallAsHttpRequest extends HTTP::Client::Request::Range {
|
||||
ModelClassMethodCall call;
|
||||
ModelClass cls;
|
||||
|
||||
ModelClassMethodCallAsHttpRequest() {
|
||||
this = call.asExpr().getExpr() and
|
||||
call.getModelClass() = cls and
|
||||
call.getMethodName() = ["all", "build", "create", "create!", "find", "first", "last"]
|
||||
}
|
||||
|
||||
override string getFramework() { result = "ActiveResource" }
|
||||
|
||||
override predicate disablesCertificateValidation(DataFlow::Node disablingNode) {
|
||||
cls.disablesCertificateValidation(disablingNode)
|
||||
}
|
||||
|
||||
override DataFlow::Node getAUrlPart() { result = cls.getASiteAssignment().getAUrlPart() }
|
||||
|
||||
override DataFlow::Node getResponseBody() { result = call }
|
||||
}
|
||||
|
||||
private class ModelInstanceMethodCallAsHttpRequest extends HTTP::Client::Request::Range {
|
||||
ModelInstanceMethodCall call;
|
||||
ModelClass cls;
|
||||
|
||||
ModelInstanceMethodCallAsHttpRequest() {
|
||||
this = call.asExpr().getExpr() and
|
||||
call.getModelClass() = cls and
|
||||
call.getMethodName() =
|
||||
[
|
||||
"exists?", "reload", "save", "save!", "destroy", "delete", "get", "patch", "post", "put",
|
||||
"update_attribute", "update_attributes"
|
||||
]
|
||||
}
|
||||
|
||||
override string getFramework() { result = "ActiveResource" }
|
||||
|
||||
override predicate disablesCertificateValidation(DataFlow::Node disablingNode) {
|
||||
cls.disablesCertificateValidation(disablingNode)
|
||||
}
|
||||
|
||||
override DataFlow::Node getAUrlPart() { result = cls.getASiteAssignment().getAUrlPart() }
|
||||
|
||||
override DataFlow::Node getResponseBody() { result = call }
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
modelClasses
|
||||
| active_resource.rb:1:1:3:3 | Person | active_resource.rb:2:3:2:11 | call to site= |
|
||||
| active_resource.rb:29:1:31:3 | Post | active_resource.rb:30:3:30:11 | call to site= |
|
||||
modelClassMethodCalls
|
||||
| active_resource.rb:2:3:2:11 | call to site= |
|
||||
| active_resource.rb:5:9:5:33 | call to new |
|
||||
| active_resource.rb:8:9:8:22 | call to find |
|
||||
| active_resource.rb:16:1:16:23 | call to new |
|
||||
| active_resource.rb:18:1:18:22 | call to get |
|
||||
| active_resource.rb:23:10:23:19 | call to all |
|
||||
| active_resource.rb:24:10:24:26 | call to find |
|
||||
| active_resource.rb:30:3:30:11 | call to site= |
|
||||
modelInstances
|
||||
| active_resource.rb:5:1:5:33 | ... = ... |
|
||||
| active_resource.rb:5:1:5:33 | ... = ... |
|
||||
| active_resource.rb:5:9:5:33 | call to new |
|
||||
| active_resource.rb:6:1:6:5 | alice |
|
||||
| active_resource.rb:8:1:8:22 | ... = ... |
|
||||
| active_resource.rb:8:1:8:22 | ... = ... |
|
||||
| active_resource.rb:8:9:8:22 | call to find |
|
||||
| active_resource.rb:9:1:9:5 | alice |
|
||||
| active_resource.rb:10:1:10:5 | alice |
|
||||
| active_resource.rb:12:1:12:5 | alice |
|
||||
| active_resource.rb:16:1:16:23 | call to new |
|
||||
| active_resource.rb:17:1:17:5 | alice |
|
||||
| active_resource.rb:18:1:18:22 | call to get |
|
||||
| active_resource.rb:19:1:19:5 | alice |
|
||||
| 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:26:1:26:20 | ... = ... |
|
||||
| active_resource.rb:26:1:26:20 | ... = ... |
|
||||
| active_resource.rb:26:9:26:14 | people |
|
||||
| active_resource.rb:26:9:26:20 | call to first |
|
||||
| active_resource.rb:27:1:27:5 | alice |
|
||||
modelInstanceMethodCalls
|
||||
| active_resource.rb:6:1:6:10 | call to save |
|
||||
| active_resource.rb:9:1:9:13 | call to address= |
|
||||
| active_resource.rb:10:1:10:10 | call to save |
|
||||
| active_resource.rb:12:1:12:13 | call to destroy |
|
||||
| active_resource.rb:16:1:16:39 | call to post |
|
||||
| active_resource.rb:17:1:17:19 | call to put |
|
||||
| active_resource.rb:19:1:19:19 | call to delete |
|
||||
| active_resource.rb:26:9:26:20 | call to first |
|
||||
| active_resource.rb:27:1:27:10 | call to save |
|
||||
collections
|
||||
| active_resource.rb:23:1:23:19 | ... = ... |
|
||||
| active_resource.rb:23:10:23:19 | call to all |
|
||||
| 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:26:9:26:14 | people |
|
||||
@@ -0,0 +1,15 @@
|
||||
import ruby
|
||||
import codeql.ruby.DataFlow
|
||||
import codeql.ruby.frameworks.ActiveResource
|
||||
|
||||
query predicate modelClasses(ActiveResource::ModelClass c, DataFlow::Node siteAssignCall) {
|
||||
c.getASiteAssignment() = siteAssignCall
|
||||
}
|
||||
|
||||
query predicate modelClassMethodCalls(ActiveResource::ModelClassMethodCall c) { any() }
|
||||
|
||||
query predicate modelInstances(ActiveResource::ModelInstance c) { any() }
|
||||
|
||||
query predicate modelInstanceMethodCalls(ActiveResource::ModelInstanceMethodCall c) { any() }
|
||||
|
||||
query predicate collections(ActiveResource::Collection c) { any() }
|
||||
@@ -0,0 +1,31 @@
|
||||
class Person < ActiveResource::Base
|
||||
self.site = "https://api.example.com"
|
||||
end
|
||||
|
||||
alice = Person.new(name: "Alice")
|
||||
alice.save
|
||||
|
||||
alice = Person.find(1)
|
||||
alice.address = "123 Main Street"
|
||||
alice.save
|
||||
|
||||
alice.destroy
|
||||
|
||||
# Custom REST methods
|
||||
|
||||
Person.new(name: "Bob").post(:register)
|
||||
alice.put(:promote)
|
||||
Person.get(:positions)
|
||||
alice.delete(:fire)
|
||||
|
||||
# Collections
|
||||
|
||||
people = Person.all
|
||||
people = Person.find(:all)
|
||||
|
||||
alice = people.first
|
||||
alice.save
|
||||
|
||||
class Post < ActiveResource::Base
|
||||
self.site = "http://api.insecure.com"
|
||||
end
|
||||
Reference in New Issue
Block a user