@@ -4,6 +4,7 @@ private import codeql_ruby.controlflow.CfgNodes
44private import codeql_ruby.DataFlow
55private import codeql_ruby.dataflow.RemoteFlowSources
66private import codeql_ruby.ast.internal.Module
7+ private import ActionView
78
89private class ActionControllerBaseAccess extends ConstantReadAccess {
910 ActionControllerBaseAccess ( ) {
@@ -45,42 +46,175 @@ class ActionControllerControllerClass extends ClassDeclaration {
4546 other .getModule ( ) = resolveScopeExpr ( this .getSuperclassExpr ( ) )
4647 )
4748 }
49+
50+ /**
51+ * Gets a `ActionControllerActionMethod` defined in this class.
52+ */
53+ ActionControllerActionMethod getAnAction ( ) { result = this .getAMethod ( ) }
4854}
4955
5056/**
51- * A call to the `params` method within the context of an
52- * `ActionControllerControllerClass`. For example, the `params` call in:
57+ * An instance method defined within an `ActionController` controller class.
58+ * This may be the target of a route handler, if such a route is defined.
59+ */
60+ class ActionControllerActionMethod extends Method , HTTP:: Server:: RequestHandler:: Range {
61+ private ActionControllerControllerClass controllerClass ;
62+
63+ ActionControllerActionMethod ( ) { this = controllerClass .getAMethod ( ) }
64+
65+ /**
66+ * Establishes a mapping between a method within the file
67+ * `<sourcePrefix>app/controllers/<subpath>_controller.rb` and the
68+ * corresponding template file at
69+ * `<sourcePrefix>app/views/<subpath>/<method_name>.html.erb`.
70+ */
71+ // TODO: result should be `ErbFile`
72+ File getDefaultTemplateFile ( ) {
73+ controllerTemplatesFolder ( this .getControllerClass ( ) , result .getParentContainer ( ) ) and
74+ result .getBaseName ( ) = this .getName ( ) + ".html.erb"
75+ }
76+
77+ // params come from `params` method rather than a method parameter
78+ override Parameter getARoutedParameter ( ) { none ( ) }
79+
80+ override string getFramework ( ) { result = "ActionController" }
81+
82+ /** Gets a call to render from within this method. */
83+ RenderCall getARenderCall ( ) { result .getParent + ( ) = this }
84+
85+ // TODO: model the implicit render call when a path through the method does
86+ // not end at an explicit render or redirect
87+ /** Gets the controller class containing this method. */
88+ ActionControllerControllerClass getControllerClass ( ) { result = controllerClass }
89+ }
90+
91+ // A method call with a `self` receiver from within a controller class
92+ private class ActionControllerContextCall extends MethodCall {
93+ private ActionControllerControllerClass controllerClass ;
94+
95+ ActionControllerContextCall ( ) {
96+ this .getReceiver ( ) instanceof Self and
97+ this .getEnclosingModule ( ) = controllerClass
98+ }
99+
100+ ActionControllerControllerClass getControllerClass ( ) { result = controllerClass }
101+ }
102+
103+ /**
104+ * A call to the `params` method to fetch the request parameters.
105+ */
106+ abstract class ParamsCall extends MethodCall {
107+ ParamsCall ( ) { this .getMethodName ( ) = "params" }
108+ }
109+
110+ /**
111+ * A `RemoteFlowSource::Range` to represent accessing the
112+ * ActionController parameters available via the `params` method.
113+ */
114+ class ParamsSource extends RemoteFlowSource:: Range {
115+ ParamsCall call ;
116+
117+ ParamsSource ( ) { this .asExpr ( ) .getExpr ( ) = call }
118+
119+ override string getSourceType ( ) { result = "ActionController::Metal#params" }
120+ }
121+
122+ // A call to `params` from within a controller.
123+ private class ActionControllerParamsCall extends ActionControllerContextCall , ParamsCall { }
124+
125+ // A call to `render` from within a controller.
126+ private class ActionControllerRenderCall extends ActionControllerContextCall , RenderCall { }
127+
128+ // A call to `render_to` from within a controller.
129+ private class ActionControllerRenderToCall extends ActionControllerContextCall , RenderToCall { }
130+
131+ // A call to `html_safe` from within a controller.
132+ private class ActionControllerHtmlSafeCall extends HtmlSafeCall {
133+ ActionControllerHtmlSafeCall ( ) {
134+ this .getEnclosingModule ( ) instanceof ActionControllerControllerClass
135+ }
136+ }
137+
138+ /**
139+ * A call to the `redirect_to` method, used in an action to redirect to a
140+ * specific URL/path or to a different action in this controller.
141+ */
142+ class RedirectToCall extends ActionControllerContextCall {
143+ RedirectToCall ( ) { this .getMethodName ( ) = "redirect_to" }
144+
145+ /** Gets the `Expr` representing the URL to redirect to, if any */
146+ Expr getRedirectUrl ( ) { result = this .getArgument ( 0 ) }
147+
148+ /** Gets the `ActionControllerActionMethod` to redirect to, if any */
149+ ActionControllerActionMethod getRedirectActionMethod ( ) {
150+ exists ( string methodName |
151+ methodName = this .getKeywordArgument ( "action" ) .( StringlikeLiteral ) .getValueText ( ) and
152+ methodName = result .getName ( ) and
153+ result .getEnclosingModule ( ) = this .getControllerClass ( )
154+ )
155+ }
156+ }
157+
158+ /**
159+ * A method in an `ActionController` class that is accessible from within a
160+ * Rails view as a helper method. For instance, in:
53161 *
54162 * ```rb
55163 * class FooController < ActionController::Base
56- * def delete_handler
57- * uid = params[:id]
58- * User.delete_all("id = ?", uid)
164+ * helper_method :logged_in?
165+ * def logged_in?
166+ * @current_user != nil
59167 * end
60168 * end
61169 * ```
170+ *
171+ * the `logged_in?` method is a helper method.
172+ * See also https://api.rubyonrails.org/classes/AbstractController/Helpers/ClassMethods.html#method-i-helper_method
62173 */
63- class ActionControllerParamsCall extends MethodCall {
174+ class ActionControllerHelperMethod extends Method {
64175 private ActionControllerControllerClass controllerClass ;
65176
66- ActionControllerParamsCall ( ) {
67- this .getMethodName ( ) = "params" and
68- this .getReceiver ( ) instanceof Self and
69- this .getEnclosingModule ( ) = controllerClass
177+ ActionControllerHelperMethod ( ) {
178+ this .getEnclosingModule ( ) = controllerClass and
179+ exists ( MethodCall helperMethodMarker |
180+ helperMethodMarker .getMethodName ( ) = "helper_method" and
181+ helperMethodMarker .getAnArgument ( ) .( StringlikeLiteral ) .getValueText ( ) = this .getName ( ) and
182+ helperMethodMarker .getEnclosingModule ( ) = controllerClass
183+ )
70184 }
71185
186+ /** Gets the class containing this helper method. */
72187 ActionControllerControllerClass getControllerClass ( ) { result = controllerClass }
73188}
74189
75190/**
76- * A `RemoteFlowSource::Range` to represent accessing the Action Controller
77- * parameters available to a controller via the `params` method.
191+ * Gets an `ActionControllerControllerClass` associated with the given `ErbFile`
192+ * according to Rails path conventions.
193+ * For instance, a template file at `app/views/foo/bar/baz.html.erb` will be
194+ * mapped to a controller class in `app/controllers/foo/bar/baz_controller.rb`,
195+ * if such a controller class exists.
78196 */
79- class ActionControllerParamsSource extends RemoteFlowSource :: Range {
80- ActionControllerParamsCall call ;
81-
82- ActionControllerParamsSource ( ) { this . asExpr ( ) . getExpr ( ) = call }
197+ // TODO: parameter should be `ErbFile`
198+ ActionControllerControllerClass getAssociatedControllerClass ( File f ) {
199+ controllerTemplatesFolder ( result , f . getParentContainer ( ) )
200+ }
83201
84- // TODO: what to use here?
85- override string getSourceType ( ) { result = "ActionController::Metal#params" }
202+ /**
203+ * Holds if `templatesFolder` is in the correct location to contain template
204+ * files "belonging" to the given `ActionControllerControllerClass`, according
205+ * to Rails conventions.
206+ *
207+ * In particular, this means that an action method in `cls` will by default
208+ * render a correspondingly named template file within `templatesFolder`.
209+ */
210+ predicate controllerTemplatesFolder ( ActionControllerControllerClass cls , Folder templatesFolder ) {
211+ exists ( string templatesPath , string sourcePrefix , string subPath , string controllerPath |
212+ controllerPath = cls .getLocation ( ) .getFile ( ) .getRelativePath ( ) and
213+ templatesPath = templatesFolder .getRelativePath ( ) and
214+ // `sourcePrefix` is either a prefix path ending in a slash, or empty if
215+ // the rails app is at the source root
216+ sourcePrefix = [ controllerPath .regexpCapture ( "^(.*/)app/controllers/(?:.*?)/(?:[^/]*)$" , 1 ) , "" ] and
217+ controllerPath = sourcePrefix + "app/controllers/" + subPath + "_controller.rb" and
218+ templatesPath = sourcePrefix + "app/views/" + subPath
219+ )
86220}
0 commit comments