mirror of
https://github.com/Fishwaldo/huginn.git
synced 2025-03-16 03:41:41 +00:00
Merge branch 'omniauth' of https://github.com/dsander/huginn into dsander-omniauth
Conflicts: Gemfile.lock
This commit is contained in:
commit
78f6150b26
40 changed files with 914 additions and 110 deletions
12
.env.example
12
.env.example
|
@ -70,6 +70,18 @@ EMAIL_FROM_ADDRESS=from_address@gmail.com
|
|||
# Number of lines of log messages to keep per Agent
|
||||
AGENT_LOG_LENGTH=200
|
||||
|
||||
#############################
|
||||
# OAuth Configuration #
|
||||
#############################
|
||||
TWITTER_OAUTH_KEY=
|
||||
TWITTER_OAUTH_SECRET=
|
||||
|
||||
THIRTY_SEVEN_SIGNALS_OAUTH_KEY=
|
||||
THIRTY_SEVEN_SIGNALS_OAUTH_SECRET=
|
||||
|
||||
GITHUB_OAUTH_KEY=
|
||||
GITHUB_OAUTH_SECRET=
|
||||
|
||||
#############################
|
||||
# AWS and Mechanical Turk #
|
||||
#############################
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
language: ruby
|
||||
bundler_args: --without development production
|
||||
env:
|
||||
- APP_SECRET_TOKEN=b2724973fd81c2f4ac0f92ac48eb3f0152c4a11824c122bcf783419a4c51d8b9bba81c8ba6a66c7de599677c7f486242cf819775c433908e77c739c5c8ae118d
|
||||
- APP_SECRET_TOKEN=b2724973fd81c2f4ac0f92ac48eb3f0152c4a11824c122bcf783419a4c51d8b9bba81c8ba6a66c7de599677c7f486242cf819775c433908e77c739c5c8ae118d TWITTER_OAUTH_KEY=twitteroauthkey TWITTER_OAUTH_SECRET=twitteroauthsecret
|
||||
rvm:
|
||||
- 2.0.0
|
||||
- 2.1.1
|
||||
|
|
5
Gemfile
5
Gemfile
|
@ -78,6 +78,11 @@ gem 'slack-notifier', '~> 0.5.0'
|
|||
gem 'therubyracer', '~> 0.12.1'
|
||||
gem 'mqtt'
|
||||
|
||||
gem 'omniauth'
|
||||
gem 'omniauth-twitter'
|
||||
gem 'omniauth-37signals'
|
||||
gem 'omniauth-github'
|
||||
|
||||
group :development do
|
||||
gem 'binding_of_caller'
|
||||
gem 'better_errors'
|
||||
|
|
79
Gemfile.lock
79
Gemfile.lock
|
@ -60,10 +60,10 @@ GEM
|
|||
coffee-rails (4.0.1)
|
||||
coffee-script (>= 2.2.0)
|
||||
railties (>= 4.0.0, < 5.0)
|
||||
coffee-script (2.2.0)
|
||||
coffee-script (2.3.0)
|
||||
coffee-script-source
|
||||
execjs
|
||||
coffee-script-source (1.7.0)
|
||||
coffee-script-source (1.7.1)
|
||||
cookiejar (0.3.2)
|
||||
coveralls (0.7.0)
|
||||
multi_json (~> 1.3)
|
||||
|
@ -154,7 +154,7 @@ GEM
|
|||
httparty (0.13.1)
|
||||
json (~> 1.8)
|
||||
multi_xml (>= 0.5.2)
|
||||
i18n (0.6.9)
|
||||
i18n (0.6.11)
|
||||
jquery-rails (3.1.1)
|
||||
railties (>= 3.0, < 5.0)
|
||||
thor (>= 0.14, < 2.0)
|
||||
|
@ -181,7 +181,7 @@ GEM
|
|||
method_source (0.8.2)
|
||||
mime-types (1.25.1)
|
||||
mini_portile (0.6.0)
|
||||
minitest (5.3.5)
|
||||
minitest (5.4.0)
|
||||
mqtt (0.2.0)
|
||||
multi_json (1.10.1)
|
||||
multi_xml (0.5.5)
|
||||
|
@ -189,14 +189,35 @@ GEM
|
|||
mysql2 (0.3.16)
|
||||
naught (1.0.0)
|
||||
net-ftp-list (3.2.8)
|
||||
nokogiri (1.6.2.1)
|
||||
nokogiri (1.6.3.1)
|
||||
mini_portile (= 0.6.0)
|
||||
oauth (0.4.7)
|
||||
oauth2 (0.9.4)
|
||||
faraday (>= 0.8, < 0.10)
|
||||
jwt (~> 1.0)
|
||||
multi_json (~> 1.3)
|
||||
multi_xml (~> 0.5)
|
||||
rack (~> 1.2)
|
||||
omniauth (1.2.2)
|
||||
hashie (>= 1.2, < 4)
|
||||
rack (~> 1.0)
|
||||
omniauth-37signals (1.0.5)
|
||||
omniauth (~> 1.0)
|
||||
omniauth-oauth2 (~> 1.0)
|
||||
omniauth-github (1.1.2)
|
||||
omniauth (~> 1.0)
|
||||
omniauth-oauth2 (~> 1.1)
|
||||
omniauth-oauth (1.0.1)
|
||||
oauth
|
||||
omniauth (~> 1.0)
|
||||
omniauth-oauth2 (1.1.2)
|
||||
faraday (>= 0.8, < 0.10)
|
||||
multi_json (~> 1.3)
|
||||
oauth2 (~> 0.9.3)
|
||||
omniauth (~> 1.2)
|
||||
omniauth-twitter (1.0.1)
|
||||
multi_json (~> 1.3)
|
||||
omniauth-oauth (~> 1.0)
|
||||
orm_adapter (0.5.0)
|
||||
pg (0.17.1)
|
||||
polyglot (0.3.5)
|
||||
|
@ -233,27 +254,33 @@ GEM
|
|||
thor (>= 0.18.1, < 2.0)
|
||||
raindrops (0.13.0)
|
||||
rake (10.3.2)
|
||||
rdoc (4.1.1)
|
||||
json (~> 1.4)
|
||||
ref (1.0.5)
|
||||
rest-client (1.6.7)
|
||||
mime-types (>= 1.16)
|
||||
rest-client (1.6.8)
|
||||
mime-types (~> 1.16)
|
||||
rdoc (>= 2.4.2)
|
||||
retriable (1.4.1)
|
||||
rr (1.1.2)
|
||||
rspec (2.14.1)
|
||||
rspec-core (~> 2.14.0)
|
||||
rspec-expectations (~> 2.14.0)
|
||||
rspec-mocks (~> 2.14.0)
|
||||
rspec-core (2.14.8)
|
||||
rspec-expectations (2.14.5)
|
||||
rspec (2.99.0)
|
||||
rspec-core (~> 2.99.0)
|
||||
rspec-expectations (~> 2.99.0)
|
||||
rspec-mocks (~> 2.99.0)
|
||||
rspec-collection_matchers (1.0.0)
|
||||
rspec-expectations (>= 2.99.0.beta1)
|
||||
rspec-core (2.99.1)
|
||||
rspec-expectations (2.99.2)
|
||||
diff-lcs (>= 1.1.3, < 2.0)
|
||||
rspec-mocks (2.14.6)
|
||||
rspec-rails (2.14.2)
|
||||
rspec-mocks (2.99.2)
|
||||
rspec-rails (2.99.0)
|
||||
actionpack (>= 3.0)
|
||||
activemodel (>= 3.0)
|
||||
activesupport (>= 3.0)
|
||||
railties (>= 3.0)
|
||||
rspec-core (~> 2.14.0)
|
||||
rspec-expectations (~> 2.14.0)
|
||||
rspec-mocks (~> 2.14.0)
|
||||
rspec-collection_matchers
|
||||
rspec-core (~> 2.99.0)
|
||||
rspec-expectations (~> 2.99.0)
|
||||
rspec-mocks (~> 2.99.0)
|
||||
rturk (2.12.1)
|
||||
erector
|
||||
nokogiri
|
||||
|
@ -269,9 +296,9 @@ GEM
|
|||
sass (~> 3.2.0)
|
||||
sprockets (~> 2.8, <= 2.11.0)
|
||||
sprockets-rails (~> 2.0)
|
||||
select2-rails (3.5.7)
|
||||
select2-rails (3.5.9)
|
||||
thor (~> 0.14)
|
||||
shoulda-matchers (2.6.1)
|
||||
shoulda-matchers (2.6.2)
|
||||
activesupport (>= 3.0.0)
|
||||
signet (0.5.1)
|
||||
addressable (>= 2.2.3)
|
||||
|
@ -280,13 +307,13 @@ GEM
|
|||
multi_json (>= 1.0.0)
|
||||
simple-rss (1.3.1)
|
||||
simple_oauth (0.2.0)
|
||||
simplecov (0.8.2)
|
||||
simplecov (0.9.0)
|
||||
docile (~> 1.1.0)
|
||||
multi_json
|
||||
simplecov-html (~> 0.8.0)
|
||||
simplecov-html (0.8.0)
|
||||
slack-notifier (0.5.0)
|
||||
slop (3.5.0)
|
||||
slop (3.6.0)
|
||||
sprockets (2.11.0)
|
||||
hike (~> 1.2)
|
||||
multi_json (~> 1.0)
|
||||
|
@ -309,7 +336,7 @@ GEM
|
|||
treetop (1.4.15)
|
||||
polyglot
|
||||
polyglot (>= 0.3.1)
|
||||
twilio-ruby (3.11.5)
|
||||
twilio-ruby (3.11.6)
|
||||
builder (>= 2.1.2)
|
||||
jwt (>= 0.1.2)
|
||||
multi_json (>= 1.3.0)
|
||||
|
@ -328,7 +355,7 @@ GEM
|
|||
ethon (>= 0.7.1)
|
||||
tzinfo (1.2.1)
|
||||
thread_safe (~> 0.1)
|
||||
uglifier (2.5.1)
|
||||
uglifier (2.5.3)
|
||||
execjs (>= 0.3.0)
|
||||
json (>= 1.8.0)
|
||||
unicorn (4.8.3)
|
||||
|
@ -394,6 +421,10 @@ DEPENDENCIES
|
|||
mysql2 (~> 0.3.16)
|
||||
net-ftp-list (~> 3.2.8)
|
||||
nokogiri (~> 1.6.1)
|
||||
omniauth
|
||||
omniauth-37signals
|
||||
omniauth-github
|
||||
omniauth-twitter
|
||||
pg
|
||||
protected_attributes (~> 1.0.8)
|
||||
pry
|
||||
|
|
|
@ -164,6 +164,8 @@ $(document).ready ->
|
|||
|
||||
$(".description").html(json.description_html) if json.description_html?
|
||||
|
||||
$('.oauthable-form').html($(json.form).find('.oauthable-form').html()) if json.form?
|
||||
|
||||
if $("#agent_options").hasClass("showing-default") || $("#agent_options").val().match(/\A\s*(\{\s*\}|)\s*\Z/g)
|
||||
window.jsonEditor.json = json.options
|
||||
window.jsonEditor.rebuild()
|
||||
|
|
32
app/concerns/oauthable.rb
Normal file
32
app/concerns/oauthable.rb
Normal file
|
@ -0,0 +1,32 @@
|
|||
module Oauthable
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do |base|
|
||||
@valid_oauth_providers = :all
|
||||
attr_accessible :service_id
|
||||
validates_presence_of :service_id
|
||||
end
|
||||
|
||||
def oauthable?
|
||||
true
|
||||
end
|
||||
|
||||
def valid_services(current_user)
|
||||
if valid_oauth_providers == :all
|
||||
current_user.available_services
|
||||
else
|
||||
current_user.available_services.where(provider: valid_oauth_providers)
|
||||
end
|
||||
end
|
||||
|
||||
def valid_oauth_providers
|
||||
self.class.valid_oauth_providers
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
def valid_oauth_providers(*providers)
|
||||
return @valid_oauth_providers if providers == []
|
||||
@valid_oauth_providers = providers
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,8 +1,10 @@
|
|||
module TwitterConcern
|
||||
extend ActiveSupport::Concern
|
||||
include Oauthable
|
||||
|
||||
included do
|
||||
validate :validate_twitter_options
|
||||
valid_oauth_providers :twitter
|
||||
end
|
||||
|
||||
def validate_twitter_options
|
||||
|
@ -15,19 +17,19 @@ module TwitterConcern
|
|||
end
|
||||
|
||||
def twitter_consumer_key
|
||||
options['consumer_key'].presence || credential('twitter_consumer_key')
|
||||
ENV['TWITTER_OAUTH_KEY']
|
||||
end
|
||||
|
||||
def twitter_consumer_secret
|
||||
options['consumer_secret'].presence || credential('twitter_consumer_secret')
|
||||
ENV['TWITTER_OAUTH_SECRET']
|
||||
end
|
||||
|
||||
def twitter_oauth_token
|
||||
options['oauth_token'].presence || options['access_key'].presence || credential('twitter_oauth_token')
|
||||
self.service.token
|
||||
end
|
||||
|
||||
def twitter_oauth_token_secret
|
||||
options['oauth_token_secret'].presence || options['access_secret'].presence || credential('twitter_oauth_token_secret')
|
||||
self.service.secret
|
||||
end
|
||||
|
||||
def twitter
|
||||
|
|
40
app/controllers/services_controller.rb
Normal file
40
app/controllers/services_controller.rb
Normal file
|
@ -0,0 +1,40 @@
|
|||
class ServicesController < ApplicationController
|
||||
|
||||
def index
|
||||
@services = current_user.services.page(params[:page])
|
||||
|
||||
respond_to do |format|
|
||||
format.html
|
||||
format.json { render json: @services }
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
@services = current_user.services.find(params[:id])
|
||||
@services.destroy
|
||||
|
||||
respond_to do |format|
|
||||
format.html { redirect_to services_path }
|
||||
format.json { head :no_content }
|
||||
end
|
||||
end
|
||||
|
||||
def toggle_availability
|
||||
@service = current_user.services.find(params[:id])
|
||||
@service.toggle_availability!
|
||||
|
||||
respond_to do |format|
|
||||
format.html { redirect_to services_path }
|
||||
format.json { render json: @service }
|
||||
end
|
||||
end
|
||||
|
||||
def callback
|
||||
@service = current_user.services.initialize_or_update_via_omniauth(request.env['omniauth.auth'])
|
||||
if @service && @service.save
|
||||
redirect_to services_path, notice: "The service was successfully created."
|
||||
else
|
||||
redirect_to services_path, error: "Error creating the service."
|
||||
end
|
||||
end
|
||||
end
|
|
@ -44,6 +44,7 @@ class Agent < ActiveRecord::Base
|
|||
after_save :possibly_update_event_expirations
|
||||
|
||||
belongs_to :user, :inverse_of => :agents
|
||||
belongs_to :service
|
||||
has_many :events, -> { order("events.id desc") }, :dependent => :delete_all, :inverse_of => :agent
|
||||
has_one :most_recent_event, :inverse_of => :agent, :class_name => "Event", :order => "events.id desc"
|
||||
has_many :logs, -> { order("agent_logs.id desc") }, :dependent => :delete_all, :inverse_of => :agent, :class_name => "AgentLog"
|
||||
|
|
|
@ -2,17 +2,18 @@ module Agents
|
|||
class BasecampAgent < Agent
|
||||
cannot_receive_events!
|
||||
|
||||
include Oauthable
|
||||
valid_oauth_providers '37signals'
|
||||
|
||||
description <<-MD
|
||||
The BasecampAgent checks a Basecamp project for new Events
|
||||
|
||||
It is required that you enter your Basecamp credentials (`username` and `password`).
|
||||
To be able to use this Agent you need to authenticate with 37signals in the [Services](/services) section first.
|
||||
|
||||
You also need to provide your Basecamp `user_id` and the `project_id` of the project you want to monitor.
|
||||
You need to provide the `project_id` of the project you want to monitor.
|
||||
If you have your Basecamp project opened in your browser you can find the user_id and project_id as follows:
|
||||
|
||||
`https://basecamp.com/`
|
||||
user_id
|
||||
`/projects/`
|
||||
`https://basecamp.com/123456/projects/`
|
||||
project_id
|
||||
`-explore-basecamp`
|
||||
MD
|
||||
|
@ -45,17 +46,11 @@ module Agents
|
|||
|
||||
def default_options
|
||||
{
|
||||
'username' => '',
|
||||
'password' => '',
|
||||
'user_id' => '',
|
||||
'project_id' => '',
|
||||
}
|
||||
end
|
||||
|
||||
def validate_options
|
||||
errors.add(:base, "you need to specify your basecamp username") unless options['username'].present?
|
||||
errors.add(:base, "you need to specify your basecamp password") unless options['password'].present?
|
||||
errors.add(:base, "you need to specify your basecamp user id") unless options['user_id'].present?
|
||||
errors.add(:base, "you need to specify the basecamp project id of which you want to receive events") unless options['project_id'].present?
|
||||
end
|
||||
|
||||
|
@ -64,6 +59,7 @@ module Agents
|
|||
end
|
||||
|
||||
def check
|
||||
self.service.prepare_request
|
||||
reponse = HTTParty.get request_url, request_options.merge(query_parameters)
|
||||
memory[:last_run] = Time.now.utc.iso8601
|
||||
if last_check_at != nil
|
||||
|
@ -76,11 +72,11 @@ module Agents
|
|||
|
||||
private
|
||||
def request_url
|
||||
"https://basecamp.com/#{URI.encode(interpolated[:user_id].to_s)}/api/v1/projects/#{URI.encode(interpolated[:project_id].to_s)}/events.json"
|
||||
"https://basecamp.com/#{URI.encode(self.service.options[:user_id].to_s)}/api/v1/projects/#{URI.encode(interpolated[:project_id].to_s)}/events.json"
|
||||
end
|
||||
|
||||
def request_options
|
||||
{:basic_auth => {:username => interpolated[:username], :password => interpolated[:password]}, :headers => {"User-Agent" => "Huginn (https://github.com/cantino/huginn)"}}
|
||||
{:headers => {"User-Agent" => "Huginn (https://github.com/cantino/huginn)", "Authorization" => "Bearer \"#{self.service.token}\""}}
|
||||
end
|
||||
|
||||
def query_parameters
|
||||
|
|
|
@ -9,11 +9,7 @@ module Agents
|
|||
description <<-MD
|
||||
The TwitterPublishAgent publishes tweets from the events it receives.
|
||||
|
||||
Twitter credentials must be supplied as either [credentials](/user_credentials) called
|
||||
`twitter_consumer_key`, `twitter_consumer_secret`, `twitter_oauth_token`, and `twitter_oauth_token_secret`,
|
||||
or as options to this Agent called `consumer_key`, `consumer_secret`, `oauth_token`, and `oauth_token_secret`.
|
||||
|
||||
To get oAuth credentials for Twitter, [follow these instructions](https://github.com/cantino/huginn/wiki/Getting-a-twitter-oauth-token).
|
||||
To be able to use this Agent you need to authenticate with Twitter in the [Services](/services) section first.
|
||||
|
||||
You must also specify a `message` parameter, you can use [Liquid](https://github.com/cantino/huginn/wiki/Formatting-Events-using-Liquid) to format the message.
|
||||
|
||||
|
|
|
@ -10,11 +10,7 @@ module Agents
|
|||
To follow the Twitter stream, provide an array of `filters`. Multiple words in a filter must all show up in a tweet, but are independent of order.
|
||||
If you provide an array instead of a filter, the first entry will be considered primary and any additional values will be treated as aliases.
|
||||
|
||||
Twitter credentials must be supplied as either [credentials](/user_credentials) called
|
||||
`twitter_consumer_key`, `twitter_consumer_secret`, `twitter_oauth_token`, and `twitter_oauth_token_secret`,
|
||||
or as options to this Agent called `consumer_key`, `consumer_secret`, `oauth_token`, and `oauth_token_secret`.
|
||||
|
||||
To get oAuth credentials for Twitter, [follow these instructions](https://github.com/cantino/huginn/wiki/Getting-a-twitter-oauth-token).
|
||||
To be able to use this Agent you need to authenticate with Twitter in the [Services](/services) section first.
|
||||
|
||||
Set `expected_update_period_in_days` to the maximum amount of time that you'd expect to pass between Events being created by this Agent.
|
||||
|
||||
|
|
|
@ -9,11 +9,7 @@ module Agents
|
|||
description <<-MD
|
||||
The TwitterUserAgent follows the timeline of a specified Twitter user.
|
||||
|
||||
Twitter credentials must be supplied as either [credentials](/user_credentials) called
|
||||
`twitter_consumer_key`, `twitter_consumer_secret`, `twitter_oauth_token`, and `twitter_oauth_token_secret`,
|
||||
or as options to this Agent called `consumer_key`, `consumer_secret`, `oauth_token`, and `oauth_token_secret`.
|
||||
|
||||
To get oAuth credentials for Twitter, [follow these instructions](https://github.com/cantino/huginn/wiki/Getting-a-twitter-oauth-token).
|
||||
To be able to use this Agent you need to authenticate with Twitter in the [Services](/services) section first.
|
||||
|
||||
You must also provide the `username` of the Twitter user to monitor.
|
||||
|
||||
|
|
|
@ -76,17 +76,19 @@ class ScenarioImport
|
|||
agent.schedule = agent_diff.schedule.updated if agent_diff.schedule.present?
|
||||
agent.keep_events_for = agent_diff.keep_events_for.updated if agent_diff.keep_events_for.present?
|
||||
agent.propagate_immediately = agent_diff.propagate_immediately.updated if agent_diff.propagate_immediately.present? # == "true"
|
||||
agent.service_id = agent_diff.service_id.updated if agent_diff.service_id.present?
|
||||
unless agent.save
|
||||
success = false
|
||||
errors.add(:base, "Errors when saving '#{agent_diff.name.incoming}': #{agent.errors.full_messages.to_sentence}")
|
||||
end
|
||||
agent
|
||||
end
|
||||
|
||||
links.each do |link|
|
||||
receiver = created_agents[link['receiver']]
|
||||
source = created_agents[link['source']]
|
||||
receiver.sources << source unless receiver.sources.include?(source)
|
||||
if success
|
||||
links.each do |link|
|
||||
receiver = created_agents[link['receiver']]
|
||||
source = created_agents[link['source']]
|
||||
receiver.sources << source unless receiver.sources.include?(source)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -149,6 +151,9 @@ class ScenarioImport
|
|||
errors.add(:base, "Your updated options for '#{agent_data['name']}' were unparsable.")
|
||||
end
|
||||
end
|
||||
if agent_diff.requires_service? && merges.present? && merges[index.to_s].present? && merges[index.to_s]['service_id'].present?
|
||||
agent_diff.service_id = AgentDiff::FieldDiff.new(merges[index.to_s]['service_id'].to_i)
|
||||
end
|
||||
agent_diff
|
||||
end
|
||||
end
|
||||
|
@ -192,6 +197,10 @@ class ScenarioImport
|
|||
@requires_merge
|
||||
end
|
||||
|
||||
def requires_service?
|
||||
!!agent_instance.try(:oauthable?)
|
||||
end
|
||||
|
||||
def store!(agent_data)
|
||||
self.type = FieldDiff.new(agent_data["type"].split("::").pop)
|
||||
self.options = FieldDiff.new(agent_data['options'] || {})
|
||||
|
@ -252,5 +261,9 @@ class ScenarioImport
|
|||
key.gsub(/[^a-zA-Z0-9_-]/, '')
|
||||
end
|
||||
end
|
||||
|
||||
def agent_instance
|
||||
"Agents::#{self.type.updated}".constantize.new
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
70
app/models/service.rb
Normal file
70
app/models/service.rb
Normal file
|
@ -0,0 +1,70 @@
|
|||
class Service < ActiveRecord::Base
|
||||
attr_accessible :provider, :name, :token, :secret, :refresh_token, :expires_at, :global, :options
|
||||
|
||||
serialize :options, Hash
|
||||
|
||||
belongs_to :user
|
||||
has_many :agents
|
||||
|
||||
validates_presence_of :user_id, :provider, :name, :token
|
||||
|
||||
before_destroy :disable_agents
|
||||
|
||||
def disable_agents
|
||||
self.agents.each do |agent|
|
||||
agent.service_id = nil
|
||||
agent.disabled = true
|
||||
agent.save!(validate: false)
|
||||
end
|
||||
end
|
||||
|
||||
def toggle_availability!
|
||||
self.global = !self.global
|
||||
self.save!
|
||||
end
|
||||
|
||||
def prepare_request
|
||||
if self.expires_at && Time.now > self.expires_at
|
||||
self.refresh_token!
|
||||
end
|
||||
end
|
||||
|
||||
def refresh_token!
|
||||
response = HTTParty.post(endpoint, query: {
|
||||
type: 'refresh',
|
||||
client_id: ENV["#{self.provider.upcase}_OAUTH_KEY"],
|
||||
client_secret: ENV["#{self.provider.upcase}_OAUTH_SECRET"],
|
||||
refresh_token: self.refresh_token
|
||||
})
|
||||
data = JSON.parse(response.body)
|
||||
self.update(expires_at: Time.now + data['expires_in'], token: data['access_token'], refresh_token: data['refresh_token'].presence || self.refresh_token)
|
||||
end
|
||||
|
||||
def self.initialize_or_update_via_omniauth(omniauth)
|
||||
case omniauth['provider']
|
||||
when 'twitter'
|
||||
find_or_initialize_by(provider: omniauth['provider'], name: omniauth['info']['nickname']).tap do |service|
|
||||
service.assign_attributes(token: omniauth['credentials']['token'], secret: omniauth['credentials']['secret'])
|
||||
end
|
||||
when 'github'
|
||||
find_or_initialize_by(provider: omniauth['provider'], name: omniauth['info']['nickname']).tap do |service|
|
||||
service.assign_attributes(token: omniauth['credentials']['token'])
|
||||
end
|
||||
when '37signals'
|
||||
find_or_initialize_by(provider: omniauth['provider'], name: omniauth['info']['name']).tap do |service|
|
||||
service.assign_attributes(token: omniauth['credentials']['token'],
|
||||
refresh_token: omniauth['credentials']['refresh_token'],
|
||||
expires_at: Time.at(omniauth['credentials']['expires_at']),
|
||||
options: {user_id: omniauth['extra']['accounts'][0]['id']})
|
||||
end
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def endpoint
|
||||
client_options = "OmniAuth::Strategies::#{OmniAuth::Utils.camelize(self.provider)}".constantize.default_options['client_options']
|
||||
URI.join(client_options['site'], client_options['token_url'])
|
||||
end
|
||||
end
|
|
@ -27,6 +27,11 @@ class User < ActiveRecord::Base
|
|||
has_many :agents, -> { order("agents.created_at desc") }, :dependent => :destroy, :inverse_of => :user
|
||||
has_many :logs, :through => :agents, :class_name => "AgentLog"
|
||||
has_many :scenarios, :inverse_of => :user, :dependent => :destroy
|
||||
has_many :services, -> { order("services.name")}, :dependent => :destroy
|
||||
|
||||
def available_services
|
||||
Service.where("user_id = ? or global = true", self.id).order("services.name desc")
|
||||
end
|
||||
|
||||
# Allow users to login via either email or username.
|
||||
def self.find_first_by_auth_conditions(warden_conditions)
|
||||
|
|
|
@ -25,11 +25,20 @@
|
|||
</div>
|
||||
<% end %>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="form-group type-select">
|
||||
<%= f.label :name %>
|
||||
<%= f.text_field :name, :class => 'form-control' %>
|
||||
</div>
|
||||
|
||||
<div class='oauthable-form'>
|
||||
<% if @agent.try(:oauthable?) %>
|
||||
<div class="form-group type-select">
|
||||
<%= f.label :service %>
|
||||
<%= f.select :service_id, options_for_select(@agent.valid_services(current_user).collect { |s| ["(#{s.provider}) #{s.name}", s.id]}, @agent.service_id),{}, class: 'form-control' %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<%= f.label :schedule, :class => 'control-label' %>
|
||||
<div class="schedule-region" data-can-be-scheduled="<%= @agent.can_be_scheduled? %>">
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
<%= nav_link "Scenarios", scenarios_path %>
|
||||
<%= nav_link "Events", events_path %>
|
||||
<%= nav_link "Credentials", user_credentials_path %>
|
||||
<%= nav_link "Services", services_path %>
|
||||
</ul>
|
||||
<% end %>
|
||||
|
||||
|
|
|
@ -120,6 +120,16 @@
|
|||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
<% if agent_diff.requires_service? %>
|
||||
<div class='row'>
|
||||
<div class='col-md-4'>
|
||||
<div class="form-group type-select">
|
||||
<%= label_tag "scenario_import[merges][#{index}][service_id]", 'Service' %>
|
||||
<%= select_tag "scenario_import[merges][#{index}][service_id]", options_for_select(agent_diff.agent_instance.valid_services(current_user).collect { |s| ["(#{s.provider}) #{s.name}", s.id]}, agent_diff.service_id.try(:current)), class: 'form-control' %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
|
|
52
app/views/services/index.html.erb
Normal file
52
app/views/services/index.html.erb
Normal file
|
@ -0,0 +1,52 @@
|
|||
<div class='container'>
|
||||
<div class='row'>
|
||||
<div class='col-md-12'>
|
||||
<div class="page-header">
|
||||
<h2>
|
||||
Your Services
|
||||
</h2>
|
||||
</div>
|
||||
<p>
|
||||
Before you can authenticate with a service, you need to set it up. Have a look at the
|
||||
<%= link_to 'wiki', 'https://github.com/cantino/huginn/wiki/Configuring-OAuth-applications', target: :_blank %>
|
||||
for guidance.
|
||||
</p>
|
||||
<p><%= link_to "Authenticate with Twitter", "/auth/twitter" %></p>
|
||||
<p><%= link_to "Authenticate with 37Signals (Basecamp)", "/auth/37signals" %></p>
|
||||
<p><%= link_to "Authenticate with Github", "/auth/github" %></p>
|
||||
<hr>
|
||||
|
||||
<div class='table-responsive'>
|
||||
<table class='table table-striped events'>
|
||||
<tr>
|
||||
<th>Provider</th>
|
||||
<th>Username</th>
|
||||
<th>Global?</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
|
||||
<% @services.each do |service| %>
|
||||
<tr>
|
||||
<td><%= service.provider %></td>
|
||||
<td><%= service.name %></td>
|
||||
<td><%= service.global ? 'Yes' : 'No' %></td>
|
||||
<td>
|
||||
<div class="btn-group btn-group-xs">
|
||||
<% if service.global %>
|
||||
<%= link_to 'Make private', toggle_availability_service_path(service), method: :post, data: { confirm: 'Are you sure you want to remove the access to this service for every user?'}, class: "btn btn-default" %>
|
||||
<% else %>
|
||||
<%= link_to 'Make global', toggle_availability_service_path(service), method: :post, data: { confirm: 'Are you sure you want to grant every user access to this service?'}, class: "btn btn-default" %>
|
||||
<% end %>
|
||||
<%= link_to 'Delete', service_path(service), method: :delete, data: { confirm: 'Are you sure?' }, class: "btn btn-default btn-danger" %>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<%= paginate @services, :theme => 'twitter-bootstrap-3' %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
5
config/initializers/omniauth.rb
Normal file
5
config/initializers/omniauth.rb
Normal file
|
@ -0,0 +1,5 @@
|
|||
Rails.application.config.middleware.use OmniAuth::Builder do
|
||||
provider :twitter, ENV['TWITTER_OAUTH_KEY'], ENV['TWITTER_OAUTH_SECRET'], authorize_params: {force_login: 'true', use_authorize: 'true'}
|
||||
provider '37signals', ENV['THIRTY_SEVEN_SIGNALS_OAUTH_KEY'], ENV['THIRTY_SEVEN_SIGNALS_OAUTH_SECRET']
|
||||
provider :github, ENV['GITHUB_OAUTH_KEY'], ENV['GITHUB_OAUTH_SECRET']
|
||||
end
|
|
@ -45,6 +45,12 @@ Huginn::Application.routes.draw do
|
|||
|
||||
resources :user_credentials, :except => :show
|
||||
|
||||
resources :services, :only => [:index, :destroy] do
|
||||
member do
|
||||
post :toggle_availability
|
||||
end
|
||||
end
|
||||
|
||||
get "/worker_status" => "worker_status#show"
|
||||
|
||||
post "/users/:user_id/update_location/:secret" => "user_location_updates#create"
|
||||
|
@ -56,6 +62,7 @@ Huginn::Application.routes.draw do
|
|||
# get "/delayed_job" => DelayedJobWeb, :anchor => false
|
||||
|
||||
devise_for :users, :sign_out_via => [ :post, :delete ]
|
||||
get '/auth/:provider/callback', to: 'services#callback'
|
||||
|
||||
get "/about" => "home#about"
|
||||
root :to => "home#index"
|
||||
|
|
18
db/migrate/20140515211100_create_services.rb
Normal file
18
db/migrate/20140515211100_create_services.rb
Normal file
|
@ -0,0 +1,18 @@
|
|||
class CreateServices < ActiveRecord::Migration
|
||||
def change
|
||||
create_table :services do |t|
|
||||
t.integer :user_id, null: false
|
||||
t.string :provider, null: false
|
||||
t.string :name, null: false
|
||||
t.text :token, null: false
|
||||
t.text :secret
|
||||
t.text :refresh_token
|
||||
t.datetime :expires_at
|
||||
t.boolean :global, default: false
|
||||
t.text :options
|
||||
t.timestamps
|
||||
end
|
||||
add_index :services, :user_id
|
||||
add_index :services, [:user_id, :global]
|
||||
end
|
||||
end
|
5
db/migrate/20140525150040_add_service_id_to_agents.rb
Normal file
5
db/migrate/20140525150040_add_service_id_to_agents.rb
Normal file
|
@ -0,0 +1,5 @@
|
|||
class AddServiceIdToAgents < ActiveRecord::Migration
|
||||
def change
|
||||
add_column :agents, :service_id, :integer
|
||||
end
|
||||
end
|
|
@ -0,0 +1,55 @@
|
|||
class MigrateAgentsToServiceAuthentication < ActiveRecord::Migration
|
||||
def twitter_consumer_key(agent)
|
||||
agent.options['consumer_key'].presence || agent.credential('twitter_consumer_key')
|
||||
end
|
||||
|
||||
def twitter_consumer_secret(agent)
|
||||
agent.options['consumer_secret'].presence || agent.credential('twitter_consumer_secret')
|
||||
end
|
||||
|
||||
def twitter_oauth_token(agent)
|
||||
agent.options['oauth_token'].presence || agent.options['access_key'].presence || agent.credential('twitter_oauth_token')
|
||||
end
|
||||
|
||||
def twitter_oauth_token_secret(agent)
|
||||
agent.options['oauth_token_secret'].presence || agent.options['access_secret'].presence || agent.credential('twitter_oauth_token_secret')
|
||||
end
|
||||
|
||||
def up
|
||||
agents = Agent.where(type: ['Agents::TwitterUserAgent', 'Agents::TwitterStreamAgent', 'Agents::TwitterPublishAgent']).each do |agent|
|
||||
service = agent.user.services.create!(
|
||||
provider: 'twitter',
|
||||
name: "Migrated '#{agent.name}'",
|
||||
token: twitter_oauth_token(agent),
|
||||
secret: twitter_oauth_token_secret(agent)
|
||||
)
|
||||
agent.service_id = service.id
|
||||
agent.save!(validate: false)
|
||||
end
|
||||
if agents.length > 0
|
||||
puts <<-EOF.strip_heredoc
|
||||
|
||||
Your Twitter agents were successfully migrated. You need to update your .env file and add the following two lines:
|
||||
|
||||
TWITTER_OAUTH_KEY=#{twitter_consumer_key(agents.first)}
|
||||
TWITTER_OAUTH_SECRET=#{twitter_consumer_secret(agents.first)}
|
||||
|
||||
|
||||
EOF
|
||||
end
|
||||
if Agent.where(type: ['Agents::BasecampAgent']).count > 0
|
||||
puts <<-EOF.strip_heredoc
|
||||
|
||||
Your Basecamp agents can not be migrated automatically. You need to manually register an application with 37signals and authenticate huginn to use it.
|
||||
Have a look at the wiki (https://github.com/cantino/huginn/wiki/Configuring-OAuth-applications) if you need help.
|
||||
|
||||
|
||||
EOF
|
||||
end
|
||||
end
|
||||
|
||||
def down
|
||||
raise ActiveRecord::IrreversibleMigration, "Cannot revert migration to OAuth services"
|
||||
end
|
||||
end
|
||||
|
71
db/schema.rb
71
db/schema.rb
|
@ -11,21 +11,24 @@
|
|||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema.define(version: 20140605032822) do
|
||||
ActiveRecord::Schema.define(version: 20140723110551) do
|
||||
|
||||
# These are extensions that must be enabled in order to support this database
|
||||
enable_extension "plpgsql"
|
||||
|
||||
create_table "agent_logs", force: true do |t|
|
||||
t.integer "agent_id", null: false
|
||||
t.text "message", limit: 16777215, null: false
|
||||
t.integer "level", default: 3, null: false
|
||||
t.integer "agent_id", null: false
|
||||
t.text "message", null: false
|
||||
t.integer "level", default: 3, null: false
|
||||
t.integer "inbound_event_id"
|
||||
t.integer "outbound_event_id"
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
end
|
||||
|
||||
create_table "agents", force: true do |t|
|
||||
t.integer "user_id"
|
||||
t.text "options", limit: 16777215
|
||||
t.text "options"
|
||||
t.string "type"
|
||||
t.string "name"
|
||||
t.string "schedule"
|
||||
|
@ -33,16 +36,17 @@ ActiveRecord::Schema.define(version: 20140605032822) do
|
|||
t.datetime "last_check_at"
|
||||
t.datetime "last_receive_at"
|
||||
t.integer "last_checked_event_id"
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.text "memory", limit: 2147483647
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.text "memory"
|
||||
t.datetime "last_web_request_at"
|
||||
t.integer "keep_events_for", default: 0, null: false
|
||||
t.datetime "last_event_at"
|
||||
t.datetime "last_error_log_at"
|
||||
t.integer "keep_events_for", default: 0, null: false
|
||||
t.boolean "propagate_immediately", default: false, null: false
|
||||
t.boolean "disabled", default: false, null: false
|
||||
t.string "guid", null: false
|
||||
t.boolean "propagate_immediately", default: false, null: false
|
||||
t.boolean "disabled", default: false, null: false
|
||||
t.integer "service_id"
|
||||
t.string "guid", null: false
|
||||
end
|
||||
|
||||
add_index "agents", ["guid"], name: "index_agents_on_guid", using: :btree
|
||||
|
@ -51,17 +55,17 @@ ActiveRecord::Schema.define(version: 20140605032822) do
|
|||
add_index "agents", ["user_id", "created_at"], name: "index_agents_on_user_id_and_created_at", using: :btree
|
||||
|
||||
create_table "delayed_jobs", force: true do |t|
|
||||
t.integer "priority", default: 0
|
||||
t.integer "attempts", default: 0
|
||||
t.text "handler", limit: 16777215
|
||||
t.text "last_error", limit: 16777215
|
||||
t.integer "priority", default: 0
|
||||
t.integer "attempts", default: 0
|
||||
t.text "handler"
|
||||
t.text "last_error"
|
||||
t.datetime "run_at"
|
||||
t.datetime "locked_at"
|
||||
t.datetime "failed_at"
|
||||
t.string "locked_by"
|
||||
t.string "queue"
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
end
|
||||
|
||||
add_index "delayed_jobs", ["priority", "run_at"], name: "delayed_jobs_priority", using: :btree
|
||||
|
@ -69,11 +73,11 @@ ActiveRecord::Schema.define(version: 20140605032822) do
|
|||
create_table "events", force: true do |t|
|
||||
t.integer "user_id"
|
||||
t.integer "agent_id"
|
||||
t.decimal "lat", precision: 15, scale: 10
|
||||
t.decimal "lng", precision: 15, scale: 10
|
||||
t.text "payload", limit: 2147483647
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.decimal "lat", precision: 15, scale: 10
|
||||
t.decimal "lng", precision: 15, scale: 10
|
||||
t.text "payload"
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.datetime "expires_at"
|
||||
end
|
||||
|
||||
|
@ -115,6 +119,23 @@ ActiveRecord::Schema.define(version: 20140605032822) do
|
|||
|
||||
add_index "scenarios", ["user_id", "guid"], name: "index_scenarios_on_user_id_and_guid", unique: true, using: :btree
|
||||
|
||||
create_table "services", force: true do |t|
|
||||
t.integer "user_id"
|
||||
t.string "provider"
|
||||
t.string "name"
|
||||
t.text "token"
|
||||
t.text "secret"
|
||||
t.text "refresh_token"
|
||||
t.datetime "expires_at"
|
||||
t.boolean "global", default: false
|
||||
t.text "options"
|
||||
t.datetime "created_at"
|
||||
t.datetime "updated_at"
|
||||
end
|
||||
|
||||
add_index "services", ["user_id", "global"], name: "index_accounts_on_user_id_and_global", using: :btree
|
||||
add_index "services", ["user_id"], name: "index_accounts_on_user_id", using: :btree
|
||||
|
||||
create_table "user_credentials", force: true do |t|
|
||||
t.integer "user_id", null: false
|
||||
t.string "credential_name", null: false
|
||||
|
|
57
spec/controllers/services_controller_spec.rb
Normal file
57
spec/controllers/services_controller_spec.rb
Normal file
|
@ -0,0 +1,57 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe ServicesController do
|
||||
before do
|
||||
sign_in users(:bob)
|
||||
OmniAuth.config.test_mode = true
|
||||
request.env["omniauth.auth"] = JSON.parse(File.read(Rails.root.join('spec/data_fixtures/services/twitter.json')))
|
||||
end
|
||||
|
||||
describe "GET index" do
|
||||
it "only returns sevices of the current user" do
|
||||
get :index
|
||||
assigns(:services).all? {|i| i.user.should == users(:bob) }.should be_true
|
||||
end
|
||||
end
|
||||
|
||||
describe "POST toggle_availability" do
|
||||
it "should work for service of the user" do
|
||||
post :toggle_availability, :id => services(:generic).to_param
|
||||
assigns(:service).should eq(services(:generic))
|
||||
redirect_to(services_path)
|
||||
end
|
||||
|
||||
it "should not work for a service of another user" do
|
||||
lambda {
|
||||
post :toggle_availability, :id => services(:global).to_param
|
||||
}.should raise_error(ActiveRecord::RecordNotFound)
|
||||
end
|
||||
end
|
||||
|
||||
describe "DELETE destroy" do
|
||||
it "destroys only services owned by the current user" do
|
||||
expect {
|
||||
delete :destroy, :id => services(:generic).to_param
|
||||
}.to change(Service, :count).by(-1)
|
||||
|
||||
lambda {
|
||||
delete :destroy, :id => services(:global).to_param
|
||||
}.should raise_error(ActiveRecord::RecordNotFound)
|
||||
end
|
||||
end
|
||||
|
||||
describe "accepting a callback url" do
|
||||
it "should update the users credentials" do
|
||||
expect {
|
||||
get :callback, provider: 'twitter'
|
||||
}.to change { users(:bob).services.count }.by(1)
|
||||
end
|
||||
|
||||
it "should not work with an unknown provider" do
|
||||
request.env["omniauth.auth"]['provider'] = 'unknown'
|
||||
expect {
|
||||
get :callback, provider: 'unknown'
|
||||
}.to change { users(:bob).services.count }.by(0)
|
||||
end
|
||||
end
|
||||
end
|
43
spec/data_fixtures/services/37signals.json
Normal file
43
spec/data_fixtures/services/37signals.json
Normal file
|
@ -0,0 +1,43 @@
|
|||
{
|
||||
"provider": "37signals",
|
||||
"uid": 12345,
|
||||
"info": {
|
||||
"email": "basecamp@none.de",
|
||||
"first_name": "Dominik",
|
||||
"last_name": "Sander",
|
||||
"name": "Dominik Sander"
|
||||
},
|
||||
"credentials": {
|
||||
"token": "abcde",
|
||||
"refresh_token": "fghrefresh",
|
||||
"expires_at": 1401554352,
|
||||
"expires": true
|
||||
},
|
||||
"extra": {
|
||||
"accounts": [
|
||||
{
|
||||
"product": "bcx",
|
||||
"name": "Dominik Sander's Basecamp",
|
||||
"id": 12345,
|
||||
"href": "https://basecamp.com/12345/api/v1"
|
||||
}
|
||||
],
|
||||
"raw_info": {
|
||||
"expires_at": "2014-05-31T16:39:12Z",
|
||||
"identity": {
|
||||
"first_name": "Dominik",
|
||||
"last_name": "Sander",
|
||||
"email_address": "basecamp@none.de",
|
||||
"id": 12345
|
||||
},
|
||||
"accounts": [
|
||||
{
|
||||
"product": "bcx",
|
||||
"name": "Dominik Sander's Basecamp",
|
||||
"id": 12345,
|
||||
"href": "https://basecamp.com/12345/api/v1"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
52
spec/data_fixtures/services/github.json
Normal file
52
spec/data_fixtures/services/github.json
Normal file
|
@ -0,0 +1,52 @@
|
|||
{
|
||||
"provider": "github",
|
||||
"uid": "12345",
|
||||
"info": {
|
||||
"nickname": "dsander",
|
||||
"email": null,
|
||||
"name": "Dominik Sander",
|
||||
"image": "https://avatars.githubusercontent.com/u/12345?",
|
||||
"urls": {
|
||||
"GitHub": "https://github.com/dsander",
|
||||
"Blog": "http://www.dsander.de"
|
||||
}
|
||||
},
|
||||
"credentials": {
|
||||
"token": "agithubtoken",
|
||||
"expires": false
|
||||
},
|
||||
"extra": {
|
||||
"raw_info": {
|
||||
"login": "dsander",
|
||||
"id": 12345,
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/12345?",
|
||||
"gravatar_id": "fsdfsdf",
|
||||
"url": "https://api.github.com/users/dsander",
|
||||
"html_url": "https://github.com/dsander",
|
||||
"followers_url": "https://api.github.com/users/dsander/followers",
|
||||
"following_url": "https://api.github.com/users/dsander/following{/other_user}",
|
||||
"gists_url": "https://api.github.com/users/dsander/gists{/gist_id}",
|
||||
"starred_url": "https://api.github.com/users/dsander/starred{/owner}{/repo}",
|
||||
"subscriptions_url": "https://api.github.com/users/dsander/subscriptions",
|
||||
"organizations_url": "https://api.github.com/users/dsander/orgs",
|
||||
"repos_url": "https://api.github.com/users/dsander/repos",
|
||||
"events_url": "https://api.github.com/users/dsander/events{/privacy}",
|
||||
"received_events_url": "https://api.github.com/users/dsander/received_events",
|
||||
"type": "User",
|
||||
"site_admin": false,
|
||||
"name": "Dominik Sander",
|
||||
"company": null,
|
||||
"blog": "http://www.url.de",
|
||||
"location": null,
|
||||
"email": null,
|
||||
"hireable": false,
|
||||
"bio": null,
|
||||
"public_repos": 29,
|
||||
"public_gists": 2,
|
||||
"followers": 21,
|
||||
"following": 9,
|
||||
"created_at": "2008-08-17T18:17:50Z",
|
||||
"updated_at": "2014-05-19T09:30:08Z"
|
||||
}
|
||||
}
|
||||
}
|
66
spec/data_fixtures/services/twitter.json
Normal file
66
spec/data_fixtures/services/twitter.json
Normal file
|
@ -0,0 +1,66 @@
|
|||
{
|
||||
"provider": "twitter",
|
||||
"uid": "123456",
|
||||
"info": {
|
||||
"nickname": "johnqpublic",
|
||||
"name": "John Q Public",
|
||||
"location": "Anytown, USA",
|
||||
"image": "http://si0.twimg.com/sticky/default_profile_images/default_profile_2_normal.png",
|
||||
"description": "a very normal guy.",
|
||||
"urls": {
|
||||
"Website": null,
|
||||
"Twitter": "https://twitter.com/johnqpublic"
|
||||
}
|
||||
},
|
||||
"credentials": {
|
||||
"token": "a1b2c3d4...",
|
||||
"secret": "abcdef1234"
|
||||
},
|
||||
"extra": {
|
||||
"access_token": "",
|
||||
"raw_info": {
|
||||
"name": "John Q Public",
|
||||
"listed_count": 0,
|
||||
"profile_sidebar_border_color": "181A1E",
|
||||
"url": null,
|
||||
"lang": "en",
|
||||
"statuses_count": 129,
|
||||
"profile_image_url": "http://si0.twimg.com/sticky/default_profile_images/default_profile_2_normal.png",
|
||||
"profile_background_image_url_https": "https://twimg0-a.akamaihd.net/profile_background_images/229171796/pattern_036.gif",
|
||||
"location": "Anytown, USA",
|
||||
"time_zone": "Chicago",
|
||||
"follow_request_sent": false,
|
||||
"id": 123456,
|
||||
"profile_background_tile": true,
|
||||
"profile_sidebar_fill_color": "666666",
|
||||
"followers_count": 1,
|
||||
"default_profile_image": false,
|
||||
"screen_name": "",
|
||||
"following": false,
|
||||
"utc_offset": -3600,
|
||||
"verified": false,
|
||||
"favourites_count": 0,
|
||||
"profile_background_color": "1A1B1F",
|
||||
"is_translator": false,
|
||||
"friends_count": 1,
|
||||
"notifications": false,
|
||||
"geo_enabled": true,
|
||||
"profile_background_image_url": "http://twimg0-a.akamaihd.net/profile_background_images/229171796/pattern_036.gif",
|
||||
"protected": false,
|
||||
"description": "a very normal guy.",
|
||||
"profile_link_color": "2FC2EF",
|
||||
"created_at": "Thu Jul 4 00:00:00 +0000 2013",
|
||||
"id_str": "123456",
|
||||
"profile_image_url_https": "https://si0.twimg.com/sticky/default_profile_images/default_profile_2_normal.png",
|
||||
"default_profile": false,
|
||||
"profile_use_background_image": false,
|
||||
"entities": {
|
||||
"description": {
|
||||
"urls": []
|
||||
}
|
||||
},
|
||||
"profile_text_color": "666666",
|
||||
"contributors_enabled": false
|
||||
}
|
||||
}
|
||||
}
|
6
spec/fixtures/agents.yml
vendored
6
spec/fixtures/agents.yml
vendored
|
@ -109,3 +109,9 @@ bob_manual_event_agent:
|
|||
user: bob
|
||||
name: "Bob's event testing agent"
|
||||
guid: <%= SecureRandom.hex %>
|
||||
|
||||
bob_basecamp_agent:
|
||||
type: Agents::BasecampAgent
|
||||
user: bob
|
||||
service: generic
|
||||
guid: <%= SecureRandom.hex %>
|
||||
|
|
17
spec/fixtures/services.yml
vendored
Normal file
17
spec/fixtures/services.yml
vendored
Normal file
|
@ -0,0 +1,17 @@
|
|||
generic:
|
||||
token: 1234token
|
||||
secret: 56789secret
|
||||
refresh_token: refresh12345
|
||||
provider: testprovider
|
||||
name: test
|
||||
expires_at: <%= Time.parse("2015-01-01 00:00:00") %>
|
||||
options: <%= { user_id: 12345 }.to_yaml.inspect %>
|
||||
user: bob
|
||||
global:
|
||||
token: 1234token
|
||||
provider: testprovider
|
||||
name: test
|
||||
expires_at: <%= Time.parse("2015-01-01 00:00:00") %>
|
||||
options: <%= { user_id: 12345 }.to_yaml.inspect %>
|
||||
user: jane
|
||||
global: true
|
|
@ -1,17 +1,16 @@
|
|||
require 'spec_helper'
|
||||
require 'models/concerns/oauthable'
|
||||
|
||||
describe Agents::BasecampAgent do
|
||||
it_behaves_like Oauthable
|
||||
|
||||
before(:each) do
|
||||
stub_request(:get, /json$/).to_return(:body => File.read(Rails.root.join("spec/data_fixtures/basecamp.json")), :status => 200, :headers => {"Content-Type" => "text/json"})
|
||||
stub_request(:get, /Z$/).to_return(:body => File.read(Rails.root.join("spec/data_fixtures/basecamp.json")), :status => 200, :headers => {"Content-Type" => "text/json"})
|
||||
@valid_params = {
|
||||
:username => "user",
|
||||
:password => "pass",
|
||||
:user_id => 12345,
|
||||
:project_id => 6789,
|
||||
}
|
||||
@valid_params = { :project_id => 6789 }
|
||||
|
||||
@checker = Agents::BasecampAgent.new(:name => "somename", :options => @valid_params)
|
||||
@checker.service = services(:generic)
|
||||
@checker.user = users(:jane)
|
||||
@checker.save!
|
||||
end
|
||||
|
@ -21,21 +20,6 @@ describe Agents::BasecampAgent do
|
|||
@checker.should be_valid
|
||||
end
|
||||
|
||||
it "should require the basecamp username" do
|
||||
@checker.options['username'] = nil
|
||||
@checker.should_not be_valid
|
||||
end
|
||||
|
||||
it "should require the basecamp password" do
|
||||
@checker.options['password'] = nil
|
||||
@checker.should_not be_valid
|
||||
end
|
||||
|
||||
it "should require the basecamp user_id" do
|
||||
@checker.options['user_id'] = nil
|
||||
@checker.should_not be_valid
|
||||
end
|
||||
|
||||
it "should require the basecamp project_id" do
|
||||
@checker.options['project_id'] = nil
|
||||
@checker.should_not be_valid
|
||||
|
@ -45,7 +29,7 @@ describe Agents::BasecampAgent do
|
|||
|
||||
describe "helpers" do
|
||||
it "should generate a correct request options hash" do
|
||||
@checker.send(:request_options).should == {:basic_auth=>{:username=>"user", :password=>"pass"}, :headers => {"User-Agent" => "Huginn (https://github.com/cantino/huginn)"}}
|
||||
@checker.send(:request_options).should == {:headers => {"User-Agent" => "Huginn (https://github.com/cantino/huginn)", "Authorization" => 'Bearer "1234token"'}}
|
||||
end
|
||||
|
||||
it "should generate the currect request url" do
|
||||
|
|
|
@ -13,6 +13,7 @@ describe Agents::TwitterPublishAgent do
|
|||
}
|
||||
|
||||
@checker = Agents::TwitterPublishAgent.new(:name => "HuginnBot", :options => @opts)
|
||||
@checker.service = services(:generic)
|
||||
@checker.user = users(:bob)
|
||||
@checker.save!
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@ describe Agents::TwitterStreamAgent do
|
|||
}
|
||||
|
||||
@agent = Agents::TwitterStreamAgent.new(:name => "HuginnBot", :options => @opts)
|
||||
@agent.service = services(:generic)
|
||||
@agent.user = users(:bob)
|
||||
@agent.save!
|
||||
end
|
||||
|
|
|
@ -16,6 +16,7 @@ describe Agents::TwitterUserAgent do
|
|||
}
|
||||
|
||||
@checker = Agents::TwitterUserAgent.new(:name => "tectonic", :options => @opts)
|
||||
@checker.service = services(:generic)
|
||||
@checker.user = users(:bob)
|
||||
@checker.save!
|
||||
end
|
||||
|
@ -31,6 +32,7 @@ describe Agents::TwitterUserAgent do
|
|||
opts = @opts.merge({ :starting_at => "Jan 01 00:00:01 +0000 2999", })
|
||||
|
||||
checker = Agents::TwitterUserAgent.new(:name => "tectonic", :options => opts)
|
||||
checker.service = services(:generic)
|
||||
checker.user = users(:bob)
|
||||
checker.save!
|
||||
|
||||
|
|
29
spec/models/concerns/oauthable.rb
Normal file
29
spec/models/concerns/oauthable.rb
Normal file
|
@ -0,0 +1,29 @@
|
|||
require 'spec_helper'
|
||||
|
||||
module Agents
|
||||
class OauthableTestAgent < Agent
|
||||
include Oauthable
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples_for Oauthable do
|
||||
before(:each) do
|
||||
@agent = described_class.new(:name => "somename")
|
||||
@agent.user = users(:jane)
|
||||
end
|
||||
|
||||
it "should be oauthable" do
|
||||
@agent.oauthable?.should == true
|
||||
end
|
||||
|
||||
describe "valid_services" do
|
||||
it "should return all available services without specifying valid_oauth_providers" do
|
||||
@agent = Agents::OauthableTestAgent.new
|
||||
@agent.valid_services(users(:bob)).collect(&:id).sort.should == [services(:generic), services(:global)].collect(&:id).sort
|
||||
end
|
||||
|
||||
it "should filter the services based on the agent defaults" do
|
||||
@agent.valid_services(users(:bob)).to_a.should == Service.where(provider: @agent.valid_oauth_providers)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -45,6 +45,18 @@ describe ScenarioImport do
|
|||
:options => trigger_agent_options
|
||||
}
|
||||
end
|
||||
let(:valid_parsed_basecamp_agent_data) do
|
||||
{
|
||||
:type => "Agents::BasecampAgent",
|
||||
:name => "Basecamp test",
|
||||
:schedule => "every_2m",
|
||||
:keep_events_for => 0,
|
||||
:propagate_immediately => true,
|
||||
:disabled => false,
|
||||
:guid => "a-basecamp-agent",
|
||||
:options => {project_id: 12345}
|
||||
}
|
||||
end
|
||||
let(:valid_parsed_data) do
|
||||
{
|
||||
:name => name,
|
||||
|
@ -407,5 +419,48 @@ describe ScenarioImport do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "agents which require a service" do
|
||||
let(:valid_parsed_services) do
|
||||
data = valid_parsed_data
|
||||
data[:agents] = [valid_parsed_basecamp_agent_data,
|
||||
valid_parsed_trigger_agent_data]
|
||||
data
|
||||
end
|
||||
|
||||
let(:valid_parsed_services_data) { valid_parsed_services.to_json }
|
||||
|
||||
let(:services_scenario_import) {
|
||||
_import = ScenarioImport.new(:data => valid_parsed_services_data)
|
||||
_import.set_user users(:bob)
|
||||
_import
|
||||
}
|
||||
|
||||
describe "#generate_diff" do
|
||||
it "should check if the agent requires a service" do
|
||||
agent_diffs = services_scenario_import.agent_diffs
|
||||
basecamp_agent_diff = agent_diffs[0]
|
||||
basecamp_agent_diff.requires_service?.should == true
|
||||
end
|
||||
|
||||
it "should add an error when no service is selected" do
|
||||
services_scenario_import.import.should == false
|
||||
services_scenario_import.errors[:base].length.should == 1
|
||||
end
|
||||
end
|
||||
|
||||
describe "#import" do
|
||||
it "should import" do
|
||||
services_scenario_import.merges = {
|
||||
"0" => {
|
||||
"service_id" => "0",
|
||||
}
|
||||
}
|
||||
lambda {
|
||||
services_scenario_import.import.should == true
|
||||
}.should change { users(:bob).agents.count }.by(2)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
109
spec/models/service_spec.rb
Normal file
109
spec/models/service_spec.rb
Normal file
|
@ -0,0 +1,109 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Service do
|
||||
before(:each) do
|
||||
@user = users(:bob)
|
||||
end
|
||||
|
||||
it "should toggle the global flag" do
|
||||
@service = services(:generic)
|
||||
@service.global.should == false
|
||||
@service.toggle_availability!
|
||||
@service.global.should == true
|
||||
@service.toggle_availability!
|
||||
@service.global.should == false
|
||||
end
|
||||
|
||||
it "disables all agents before beeing destroyed" do
|
||||
agent = agents(:bob_basecamp_agent)
|
||||
service = agent.service
|
||||
service.destroy
|
||||
agent.reload
|
||||
agent.service_id.should be_nil
|
||||
agent.disabled.should be_true
|
||||
end
|
||||
|
||||
describe "preparing for a request" do
|
||||
before(:each) do
|
||||
@service = services(:generic)
|
||||
end
|
||||
|
||||
it "should not update the token if the token never expires" do
|
||||
@service.expires_at = nil
|
||||
@service.prepare_request.should == nil
|
||||
end
|
||||
|
||||
it "should not update the token if the token is still valid" do
|
||||
@service.expires_at = Time.now + 1.hour
|
||||
@service.prepare_request.should == nil
|
||||
end
|
||||
|
||||
it "should call refresh_token! if the token expired" do
|
||||
stub(@service).refresh_token! { @service }
|
||||
@service.expires_at = Time.now - 1.hour
|
||||
@service.prepare_request.should == @service
|
||||
end
|
||||
end
|
||||
|
||||
describe "updating the access token" do
|
||||
before(:each) do
|
||||
@service = services(:generic)
|
||||
end
|
||||
|
||||
it "should return the correct endpoint" do
|
||||
@service.provider = '37signals'
|
||||
@service.send(:endpoint).to_s.should == "https://launchpad.37signals.com/authorization/token"
|
||||
end
|
||||
|
||||
it "should update the token" do
|
||||
stub_request(:post, "https://launchpad.37signals.com/authorization/token?client_id=TESTKEY&client_secret=TESTSECRET&refresh_token=refreshtokentest&type=refresh").
|
||||
to_return(:status => 200, :body => '{"expires_in":1209600,"access_token": "NEWTOKEN"}', :headers => {})
|
||||
@service.provider = '37signals'
|
||||
ENV['THIRTY_SEVEN_SIGNALS_OAUTH_KEY'] = 'TESTKEY'
|
||||
ENV['THIRTY_SEVEN_SIGNALS_OAUTH_SECRET'] = 'TESTSECRET'
|
||||
@service.refresh_token = 'refreshtokentest'
|
||||
@service.refresh_token!
|
||||
@service.token.should == 'NEWTOKEN'
|
||||
end
|
||||
end
|
||||
|
||||
describe "creating services via omniauth" do
|
||||
it "should work with twitter services" do
|
||||
twitter = JSON.parse(File.read(Rails.root.join('spec/data_fixtures/services/twitter.json')))
|
||||
expect {
|
||||
service = @user.services.initialize_or_update_via_omniauth(twitter)
|
||||
service.save!
|
||||
}.to change { @user.services.count }.by(1)
|
||||
service = @user.services.first
|
||||
service.name.should == 'johnqpublic'
|
||||
service.provider.should == 'twitter'
|
||||
service.token.should == 'a1b2c3d4...'
|
||||
service.secret.should == 'abcdef1234'
|
||||
end
|
||||
it "should work with 37signals services" do
|
||||
signals = JSON.parse(File.read(Rails.root.join('spec/data_fixtures/services/37signals.json')))
|
||||
expect {
|
||||
service = @user.services.initialize_or_update_via_omniauth(signals)
|
||||
service.save!
|
||||
}.to change { @user.services.count }.by(1)
|
||||
service = @user.services.first
|
||||
service.provider.should == '37signals'
|
||||
service.name.should == 'Dominik Sander'
|
||||
service.token.should == 'abcde'
|
||||
service.refresh_token.should == 'fghrefresh'
|
||||
service.options[:user_id].should == 12345
|
||||
service.expires_at = Time.at(1401554352)
|
||||
end
|
||||
it "should work with github services" do
|
||||
signals = JSON.parse(File.read(Rails.root.join('spec/data_fixtures/services/github.json')))
|
||||
expect {
|
||||
service = @user.services.initialize_or_update_via_omniauth(signals)
|
||||
service.save!
|
||||
}.to change { @user.services.count }.by(1)
|
||||
service = @user.services.first
|
||||
service.provider.should == 'github'
|
||||
service.name.should == 'dsander'
|
||||
service.token.should == 'agithubtoken'
|
||||
end
|
||||
end
|
||||
end
|
|
@ -21,6 +21,8 @@ WebMock.disable_net_connect!
|
|||
# in spec/support/ and its subdirectories.
|
||||
Dir[Rails.root.join("spec/support/**/*.rb")].each {|f| require f}
|
||||
|
||||
ActiveRecord::Migration.maintain_test_schema!
|
||||
|
||||
RSpec.configure do |config|
|
||||
config.mock_with :rr
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue