thoughtbot / griddler

Simplify receiving email in Rails (deprecated)
http://griddler.io/
MIT License
1.38k stars 199 forks source link

Access to params #139

Closed marine44 closed 10 years ago

marine44 commented 10 years ago

Is there a way to access the params from the original HTTP post from mailgun?

I want to access params[:timestamp], params[:token] and params[:signature] to allow me to verify the web hooks.

See "Securing Webhooks" at http://documentation.mailgun.com/user_manual.html#webhooks

calebhearth commented 10 years ago

Best way to do that at this point would be to override the Griddler::EmailsController and throw in a before_filter that renders some status from the 400 block if the params don't match. #133 is in PR now to override the controller in the future, but it might be a bit before that gets merged since we're overhauling the gem currently.

marine44 commented 10 years ago

Thanks for getting back to me! What you say sounds very good but is a little outside my skill level (I don't know how to override the Griddler::EmailsController). Unless it's easy for you to explain or demonstrate I might just wait until a future version of the gem.

calebhearth commented 10 years ago

You'd reopen the Griddler::EmailsController with class Griddler::EmailsController; ...; end, write a method that does what mailgun talks about for securing webhooks, and before_filter :that_method in the subclassed controller.

gabebw commented 10 years ago

Full example:

# in app/controllers/griddler/emails_controller.rb
class Griddler::EmailsController
  before_filter :verify_webhook

  private

  def verify_webhook
    # write your verification method here
  end
end
marine44 commented 10 years ago

Thanks - thats great to get me started - here is where I am at:

require 'openssl'

class Griddler::EmailsController

  before_filter :verify_webhook

  private

  def verify_webhook

    signature = params[:signature]
    timestamp = params[:timestamp]
    token = params[:token]
    api_key = ENV['MAILGUN_API_KEY']

    return signature == OpenSSL::HMAC.hexdigest(
                            OpenSSL::Digest::Digest.new('sha256'),
                            api_key,
                            '%s%s' % [timestamp, token])

  end
end

But it's giving me:

* [out :: 119.9.20.198] undefined method `before_filter' for Griddler::EmailsController:Class * [out :: 119.9.20.198]( [out :: 119.9.20.198] NoMethodError [out :: 119.9.20.198])

calebhearth commented 10 years ago

Before we get too far, you don't want to return true/false, you need to redirect or render, as in my original suggestion.

try before_action, and if that doesn't work make sure Griddler::EmailsController exists before you reopen it. Could be a load issue.

marine44 commented 10 years ago

@calebthompson thanks so much but is there any chance you could elaborate your last response? Really keen to get this working otherwise there is no way for me to stop others from posting to the application...

calebhearth commented 10 years ago

TL;DR, the Rails docs will be really helpful in this for you: http://guides.rubyonrails.org/action_controller_overview.html#filters

Briefly, beforefilters in controllers, unlike before* in models, don't stop a workflow just because they return false. In a before_save, if you returned false from a callback then the model wouldn't save, but that's not how it works with controllers. In a controller, you'll need to redirect_to somewhere based on the request. rendering in the filter should also keep the controller action from running.

So after you check the signature, you'd probably want to use something like head :forbidden if the signature doesn't parse properly. http://stackoverflow.com/questions/8085353/rails3-how-to-render-403-in-before-filter-w-o-doublerender-error

dmarkow commented 9 years ago

Unfortunately, the way Rails 4 (and I think 3?) isolate Engines, you can't just re-open the controller. I just tried the recommendations above and it results in:

AbstractController::ActionNotFound (The action 'create' could not be found for Griddler::EmailsController):

It appears that because I have now defined Griddler::EmailsController in my own app, the one from the griddler gem is never used/loaded.

What worked for me was inheriting

# in config/routes.rb
post '/incoming_email' => 'griddler/custom_emails#create'

# in app/controllers/griddler/custom_emails_controller.rb
class Griddler::CustomEmailsController < Griddler::EmailsController
  before_action :foo

  private
  def foo
    # do something
  end
end

However, while this is a good way to deal with authentication, it still doesn't provide my email processor class with access to the full set of original params. For example, with Mailgun, they post additional things like their own version of the stripped out body and signature (vs. letting griddler do this). It would be nice to have access to all of those params just in case.

Is there any reason/potential downside to just making the attr_reader :params line public?

gabebw commented 9 years ago

@dmarkow I'm confused - since it's a subclass, normalized_params is still available to you, right? And since it's a controller, isn't params available as well?

dmarkow commented 9 years ago

Yes, params is available in my custom controller, but I need it in my EmailProcessor class, not the controller.

It feels to me that the Griddler::Email class should expose the params it was based off, especially considering that only some of the original params are being mapped to attributes. But for my purposes, overriding the process_email(email) method so I can send a second parameter to my EmailProcessor class seems to work:

def process_email(email)
  EmailProcessor.new(email, params).process
end