Rails Form block in helper - How do i include “Protect from forgery”

≡放荡痞女 提交于 2019-12-04 14:54:34

I agree to Rodrigo but it will be hard to identify method names that will be deleagated to controller.

That's why I prefer to extend Liquid::Block class and delegate missing methods to controller if responds..

class LiquidFormTag < Liquid::Block
  include ActionView::Context
  include ActionView::Helpers::FormHelper

  attr_reader :controller

  def initialize(tag_name, markup, tokens)
    super
  end

  def render(context)
    @controller = context.registers[:controller]
    form_tag('#') do
      super(context).html_safe
    end
  end

end


Liquid::Block.class_eval do
  # This delegates missing - including private & protected - methods (like protect_against_forgery?) to controller.
  def method_missing(*args)
    begin
      if controller.respond_to?(args.first, true)
        controller.send(args.first)
      else
        super
      end
    rescue
      super
    end
  end
end

The method protect_against_forgery came from ActionController::RequestForgeryProtection module. But include this module in class Liquid::Block doesn't seems to be a good practice.

The solution I use is:

class LiquidFormTag < Liquid::Block
  include ActionView::Context
  include ActionView::Helpers::FormHelper

  attr_reader :controller

  def initialize(tag_name, markup, tokens)
    super
  end

  def render(context)
    @controller = context.registers[:controller]
    form_tag('#') do
      super(context).html_safe
    end
  end

  delegate :form_authenticity_token, :request_forgery_protection_token, :protect_against_forgery?, to: :controller

end

Liquid::Template.register_tag 'liquid_form', LiquidFormTag

To this code work, you need to provide current controller to Liquid render method:

class OffersController < ApplicationController

   def create
      @offer = Offer.new
      Liquid::Template.parse(template).render 'offer' => @offer, registers: {controller: self}
   end

end
Juan Artau

I had the same problem this days, and I solved this way:

class LiquidForm < Liquid::Block
  include ActionView::Context
  include ActionView::Helpers::FormHelper

  attr_reader :csrf_token

  def initialize(tag_name, markup, options)
   super
   @model = markup.strip
  end

  def render(context)
    csrf = context.registers[:csrf_token]

    form_tag "/new_obj", authenticity_token: false do
      "#{hidden_field_tag('authenticity_token', csrf)}
       #{super(context)}".html_safe
    end

  end
end

Now I needed to generate the security token from my App. I investigated that inside the form_tag I could called the method form_authenticity_token that is available from the ActionController::RequestForgeryProtection module. Based on this answer.

This form_authenticity_token only creates a secure random key, you can see it in form_authenticity_token

# File actionpack/lib/action_controller/metal/request_forgery_protection.rb, line 249
  def form_authenticity_token
    session[:_csrf_token] ||= SecureRandom.base64(32)
  end

As we cannot call this inside our LiquidForm class. We need to generate the token manually exactly like this method in our Liquid render method. This can be in your view or controller. Example:

=Liquid::Template.parse(template).render('offer' => @offer, registers: {:csrf_token => session[:_csrf_token] ||= SecureRandom.base64(32)})
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!