A while ago a concept called “Strong Parameters” was added to Ruby on Rails, the general idea of which was to explicitly allow or deny parameters passing into your application code.
Since most of my work right now is in Sinatra, I was interested in creating something similar with my setup in mind. I decided to give it a go using Sinatra’s conditions and the result, is my “sinatra-strong-params” Ruby gem.
Here’s an example Sinatra application using StrongParams:
1require 'sinatra/base'
2require 'sinatra/strong-params'
3
4class ExampleApp < Sinatra::Base
5 configure do
6 register Sinatra::StrongParams
7 end
8
9 get '/', allows: [:search] do
10 # Only the 'search' parameter will make it to the execution scope.
11 end
12
13 post '/search', needs: [:search, :_csrf] do
14 # Will only ever return if both the 'search' and '_csrf' parameters are present.
15 # Otherwise, it will raise an instance of RequiredParamMissing
16 end
17
18 error RequiredParamMissing do
19 # Handle parameter failures here.
20 [400, "No dice"]
21 end
22end
There are two new conditions we can add to any request: “allows” and “needs”. As you might have guessed, one specifies which parameters you’d like to allow through to the request handler and the other specifies which parameters must be present for the request to be handled.
Allows
Here, we simply filter out parameters to the request that are not explicitly allowed by the condition.
1#
2# A way to whitelist parameters.
3#
4# get '/', allows: [:id, :action] do
5# erb :index
6# end
7#
8# Modifies the parameters available in the request scope.
9# Stashes unmodified params in @_params
10#
11app.set(:allows) do |*passable|
12 condition do
13 unless @params.empty?
14 @_params = @_params || @params # for safety
15 globals = settings.globally_allowed_parameters
16 passable = (globals | passable).map(&:to_sym) # make sure it's a symbol
17
18 # trim the params down
19 @params = @params.select do |param, _value|
20 passable.include?(param.to_sym)
21 end
22 end
23 end
24end
Needs
And here, we actually fail the request if one of the defined parameters is missing.
1#
2# A way to require parameters
3#
4# get '/', needs: [:id, :action] do
5# erb :index
6# end
7#
8# Does not modify the parameters available to the request scope.
9# Raises a RequiredParamMissing error if a needed param is missing
10#
11app.set(:needs) do |*needed|
12 condition do
13 if @params.nil? || @params.empty? && !needed.empty?
14 fail RequiredParamMissing, settings.missing_parameter_message
15 else
16 needed = needed.map(&:to_sym) # make sure it's a symbol
17 sym_params = @params.dup
18
19 # symbolize the keys so we know what we're looking at
20 sym_params.keys.each do |key|
21 sym_params[(key.to_sym rescue key) || key] = sym_params.delete(key)
22 end
23
24 if needed.any? { |key| sym_params[key].nil? || sym_params[key].empty? }
25 fail RequiredParamMissing, settings.missing_parameter_message
26 end
27 end
28 end
29end
And there ya go! Lock down those parameters!