From d0715756f08eeefa353194d4c0b589c4fb4fb392 Mon Sep 17 00:00:00 2001 From: Andrew Cantino Date: Wed, 8 May 2013 23:24:08 -0700 Subject: [PATCH] add webhooks controller --- Gemfile | 1 + app/controllers/webhooks_controller.rb | 39 ++++++++++++++ app/models/agent.rb | 12 +++++ config/routes.rb | 1 + ...509053743_add_last_webhook_at_to_agents.rb | 5 ++ db/schema.rb | 3 +- spec/controllers/webhooks_controller_spec.rb | 54 +++++++++++++++++++ spec/models/agent_spec.rb | 2 +- 8 files changed, 115 insertions(+), 2 deletions(-) create mode 100644 app/controllers/webhooks_controller.rb create mode 100644 db/migrate/20130509053743_add_last_webhook_at_to_agents.rb create mode 100644 spec/controllers/webhooks_controller_spec.rb diff --git a/Gemfile b/Gemfile index 9eabe41c..d2ee71da 100644 --- a/Gemfile +++ b/Gemfile @@ -1,6 +1,7 @@ source 'https://rubygems.org' gem 'rails' +gem 'rake' gem 'mysql2' gem 'devise' gem 'rails_admin' diff --git a/app/controllers/webhooks_controller.rb b/app/controllers/webhooks_controller.rb new file mode 100644 index 00000000..c5733869 --- /dev/null +++ b/app/controllers/webhooks_controller.rb @@ -0,0 +1,39 @@ +# This controller is designed to allow your Agents to receive cross-site Webhooks (posts). When POSTed, your Agent will +# have #receive_webhook called on itself with the POST params. +# +# Make POSTs to the following URL: +# http://yourserver.com/users/:user_id/webhooks/:agent_id/:secret +# where :user_id is your User's id, :agent_id is an Agent's id, and :secret is a token that should be +# user-specifiable in your Agent. It is highly recommended that you verify this token whenever #receive_webhook +# is called. For example, one of your Agent's options could be :secret and you could compare this value +# to params[:secret] whenever #receive_webhook is called on your Agent, rejecting invalid requests. +# +# Your Agent's #receive_webhook method should return an Array of [json_or_string_response, status_code]. For example: +# [{status: "success"}, 200] +# or +# ["not found", 404] + +class WebhooksController < ApplicationController + skip_before_filter :authenticate_user! + + def create + user = User.find_by_id(params[:user_id]) + if user + agent = user.agents.find_by_id(params[:agent_id]) + if agent + response, status = agent.trigger_webhook(params.except(:action, :controller, :agent_id, :user_id)) + if response.is_a?(String) + render :text => response, :status => status || 200 + elsif response.is_a?(Hash) + render :json => response, :status => status || 200 + else + head :ok + end + else + render :text => "agent not found", :status => :not_found + end + else + render :text => "user not found", :status => :not_found + end + end +end diff --git a/app/models/agent.rb b/app/models/agent.rb index c5e7570a..c350def1 100644 --- a/app/models/agent.rb +++ b/app/models/agent.rb @@ -60,6 +60,11 @@ class Agent < ActiveRecord::Base # Implement me in your subclass of Agent. end + def receive_webhook(params) + # Implement me in your subclass of Agent. + ["not implemented", 404] + end + # Implement me in your subclass to decide if your Agent is working. def working? raise "Implement me in your subclass" @@ -88,6 +93,13 @@ class Agent < ActiveRecord::Base message.gsub(/<([^>]+)>/) { Utils.value_at(payload, $1) || "??" } end + def trigger_webhook(params) + receive_webhook(params).tap do + self.last_webhook_at = Time.now + save! + end + end + def set_default_schedule self.schedule = default_schedule unless schedule.present? || cannot_be_scheduled? end diff --git a/config/routes.rb b/config/routes.rb index c5488d15..4b0394a1 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -16,6 +16,7 @@ Huginn::Application.routes.draw do match "/worker_status" => "worker_status#show" post "/users/:user_id/update_location/:secret" => "user_location_updates#create" + post "/users/:user_id/webhooks/:agent_id/:secret" => "webhooks#create" mount RailsAdmin::Engine => '/admin', :as => 'rails_admin' # match "/delayed_job" => DelayedJobWeb, :anchor => false diff --git a/db/migrate/20130509053743_add_last_webhook_at_to_agents.rb b/db/migrate/20130509053743_add_last_webhook_at_to_agents.rb new file mode 100644 index 00000000..2623f9a8 --- /dev/null +++ b/db/migrate/20130509053743_add_last_webhook_at_to_agents.rb @@ -0,0 +1,5 @@ +class AddLastWebhookAtToAgents < ActiveRecord::Migration + def change + add_column :agents, :last_webhook_at, :datetime + end +end diff --git a/db/schema.rb b/db/schema.rb index 15044bae..78f5ade8 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended to check this file into your version control system. -ActiveRecord::Schema.define(:version => 20130126080736) do +ActiveRecord::Schema.define(:version => 20130509053743) do create_table "agents", :force => true do |t| t.integer "user_id" @@ -26,6 +26,7 @@ ActiveRecord::Schema.define(:version => 20130126080736) do t.datetime "created_at", :null => false t.datetime "updated_at", :null => false t.text "memory", :limit => 2147483647 + t.datetime "last_webhook_at" end add_index "agents", ["schedule"], :name => "index_agents_on_schedule" diff --git a/spec/controllers/webhooks_controller_spec.rb b/spec/controllers/webhooks_controller_spec.rb new file mode 100644 index 00000000..8a42f8ec --- /dev/null +++ b/spec/controllers/webhooks_controller_spec.rb @@ -0,0 +1,54 @@ +require 'spec_helper' + +describe WebhooksController do + class Agents::WebhookReceiverAgent < Agent + cannot_receive_events! + cannot_be_scheduled! + + def receive_webhook(params) + if params.delete(:secret) == options[:secret] + memory[:webhook_values] = params + ["success", 200] + else + ["failure", 404] + end + end + end + + before do + stub(Agents::WebhookReceiverAgent).valid_type?("Agents::WebhookReceiverAgent") { true } + @agent = Agents::WebhookReceiverAgent.new(:name => "something", :options => { :secret => "my_secret" }) + @agent.user = users(:bob) + @agent.save! + end + + it "should not require login to trigger a webhook" do + @agent.last_webhook_at.should be_nil + post :create, :user_id => users(:bob).to_param, :agent_id => @agent.id, :secret => "my_secret", :key => "value", :another_key => "5" + @agent.reload.last_webhook_at.should be_within(2).of(Time.now) + response.body.should == "success" + response.should be_success + end + + it "should call receive_webhook" do + post :create, :user_id => users(:bob).to_param, :agent_id => @agent.id, :secret => "my_secret", :key => "value", :another_key => "5" + @agent.reload.memory[:webhook_values].should == { :key => "value", :another_key => "5" } + response.body.should == "success" + response.should be_success + + post :create, :user_id => users(:bob).to_param, :agent_id => @agent.id, :secret => "not_my_secret", :no => "go" + @agent.reload.memory[:webhook_values].should_not == { :no => "go" } + response.body.should == "failure" + response.should be_missing + end + + it "should fail on incorrect users" do + post :create, :user_id => users(:jane).to_param, :agent_id => @agent.id, :secret => "my_secret", :no => "go" + response.should be_missing + end + + it "should fail on incorrect agents" do + post :create, :user_id => users(:bob).to_param, :agent_id => 454545, :secret => "my_secret", :no => "go" + response.should be_missing + end +end \ No newline at end of file diff --git a/spec/models/agent_spec.rb b/spec/models/agent_spec.rb index ebd8a5b6..950ce19f 100644 --- a/spec/models/agent_spec.rb +++ b/spec/models/agent_spec.rb @@ -49,7 +49,7 @@ describe Agent do end end - describe "with a mock source" do + describe "with an example Agent" do class Agents::SomethingSource < Agent default_schedule "2pm"