@@ -71,6 +71,18 @@ EMAIL_FROM_ADDRESS=from_address@gmail.com |
||
71 | 71 |
AGENT_LOG_LENGTH=200 |
72 | 72 |
|
73 | 73 |
############################# |
74 |
+# OAuth Configuration # |
|
75 |
+############################# |
|
76 |
+TWITTER_OAUTH_KEY= |
|
77 |
+TWITTER_OAUTH_SECRET= |
|
78 |
+ |
|
79 |
+37SIGNALS_OAUTH_KEY= |
|
80 |
+37SIGNALS_OAUTH_SECRET= |
|
81 |
+ |
|
82 |
+GITHUB_OAUTH_KEY= |
|
83 |
+GITHUB_OAUTH_SECRET= |
|
84 |
+ |
|
85 |
+############################# |
|
74 | 86 |
# AWS and Mechanical Turk # |
75 | 87 |
############################# |
76 | 88 |
|
@@ -1,7 +1,7 @@ |
||
1 | 1 |
language: ruby |
2 | 2 |
bundler_args: --without development production |
3 | 3 |
env: |
4 |
- - APP_SECRET_TOKEN=b2724973fd81c2f4ac0f92ac48eb3f0152c4a11824c122bcf783419a4c51d8b9bba81c8ba6a66c7de599677c7f486242cf819775c433908e77c739c5c8ae118d |
|
4 |
+ - APP_SECRET_TOKEN=b2724973fd81c2f4ac0f92ac48eb3f0152c4a11824c122bcf783419a4c51d8b9bba81c8ba6a66c7de599677c7f486242cf819775c433908e77c739c5c8ae118d TWITTER_OAUTH_KEY=twitteroauthkey TWITTER_OAUTH_SECRET=twitteroauthsecret |
|
5 | 5 |
rvm: |
6 | 6 |
- 2.0.0 |
7 | 7 |
- 2.1.1 |
@@ -76,6 +76,11 @@ gem 'therubyracer', '~> 0.12.1' |
||
76 | 76 |
|
77 | 77 |
gem 'mqtt' |
78 | 78 |
|
79 |
+gem 'omniauth' |
|
80 |
+gem 'omniauth-twitter' |
|
81 |
+gem 'omniauth-37signals' |
|
82 |
+gem 'omniauth-github' |
|
83 |
+ |
|
79 | 84 |
group :development do |
80 | 85 |
gem 'binding_of_caller' |
81 | 86 |
gem 'better_errors' |
@@ -168,12 +168,33 @@ GEM |
||
168 | 168 |
naught (1.0.0) |
169 | 169 |
nokogiri (1.6.2.1) |
170 | 170 |
mini_portile (= 0.6.0) |
171 |
+ oauth (0.4.7) |
|
171 | 172 |
oauth2 (0.9.3) |
172 | 173 |
faraday (>= 0.8, < 0.10) |
173 | 174 |
jwt (~> 0.1.8) |
174 | 175 |
multi_json (~> 1.3) |
175 | 176 |
multi_xml (~> 0.5) |
176 | 177 |
rack (~> 1.2) |
178 |
+ omniauth (1.2.1) |
|
179 |
+ hashie (>= 1.2, < 3) |
|
180 |
+ rack (~> 1.0) |
|
181 |
+ omniauth-37signals (1.0.5) |
|
182 |
+ omniauth (~> 1.0) |
|
183 |
+ omniauth-oauth2 (~> 1.0) |
|
184 |
+ omniauth-github (1.1.2) |
|
185 |
+ omniauth (~> 1.0) |
|
186 |
+ omniauth-oauth2 (~> 1.1) |
|
187 |
+ omniauth-oauth (1.0.1) |
|
188 |
+ oauth |
|
189 |
+ omniauth (~> 1.0) |
|
190 |
+ omniauth-oauth2 (1.1.2) |
|
191 |
+ faraday (>= 0.8, < 0.10) |
|
192 |
+ multi_json (~> 1.3) |
|
193 |
+ oauth2 (~> 0.9.3) |
|
194 |
+ omniauth (~> 1.2) |
|
195 |
+ omniauth-twitter (1.0.1) |
|
196 |
+ multi_json (~> 1.3) |
|
197 |
+ omniauth-oauth (~> 1.0) |
|
177 | 198 |
orm_adapter (0.5.0) |
178 | 199 |
polyglot (0.3.5) |
179 | 200 |
protected_attributes (1.0.7) |
@@ -345,6 +366,10 @@ DEPENDENCIES |
||
345 | 366 |
mqtt |
346 | 367 |
mysql2 (~> 0.3.15) |
347 | 368 |
nokogiri (~> 1.6.1) |
369 |
+ omniauth |
|
370 |
+ omniauth-37signals |
|
371 |
+ omniauth-github |
|
372 |
+ omniauth-twitter |
|
348 | 373 |
protected_attributes (~> 1.0.7) |
349 | 374 |
pry |
350 | 375 |
rack |
@@ -0,0 +1,32 @@ |
||
1 |
+module Oauthable |
|
2 |
+ extend ActiveSupport::Concern |
|
3 |
+ |
|
4 |
+ included do |base| |
|
5 |
+ attr_accessible :service_id |
|
6 |
+ validates_presence_of :service_id |
|
7 |
+ base.extend ClassMethods |
|
8 |
+ self.class_variable_set(:@@valid_oauth_providers, :all) |
|
9 |
+ end |
|
10 |
+ |
|
11 |
+ def oauthable? |
|
12 |
+ true |
|
13 |
+ end |
|
14 |
+ |
|
15 |
+ def valid_services(current_user) |
|
16 |
+ if valid_oauth_providers == :all |
|
17 |
+ current_user.available_services |
|
18 |
+ else |
|
19 |
+ current_user.available_services.where(provider: valid_oauth_providers) |
|
20 |
+ end |
|
21 |
+ end |
|
22 |
+ |
|
23 |
+ def valid_oauth_providers |
|
24 |
+ self.class.class_variable_get(:@@valid_oauth_providers) |
|
25 |
+ end |
|
26 |
+ |
|
27 |
+ module ClassMethods |
|
28 |
+ def valid_oauth_providers(*providers) |
|
29 |
+ self.class_variable_set(:@@valid_oauth_providers, providers) |
|
30 |
+ end |
|
31 |
+ end |
|
32 |
+end |
@@ -1,8 +1,10 @@ |
||
1 | 1 |
module TwitterConcern |
2 | 2 |
extend ActiveSupport::Concern |
3 |
+ include Oauthable |
|
3 | 4 |
|
4 | 5 |
included do |
5 | 6 |
validate :validate_twitter_options |
7 |
+ valid_oauth_providers :twitter |
|
6 | 8 |
end |
7 | 9 |
|
8 | 10 |
def validate_twitter_options |
@@ -15,19 +17,19 @@ module TwitterConcern |
||
15 | 17 |
end |
16 | 18 |
|
17 | 19 |
def twitter_consumer_key |
18 |
- options['consumer_key'].presence || credential('twitter_consumer_key') |
|
20 |
+ ENV['TWITTER_OAUTH_KEY'] |
|
19 | 21 |
end |
20 | 22 |
|
21 | 23 |
def twitter_consumer_secret |
22 |
- options['consumer_secret'].presence || credential('twitter_consumer_secret') |
|
24 |
+ ENV['TWITTER_OAUTH_SECRET'] |
|
23 | 25 |
end |
24 | 26 |
|
25 | 27 |
def twitter_oauth_token |
26 |
- options['oauth_token'].presence || options['access_key'].presence || credential('twitter_oauth_token') |
|
28 |
+ self.service.token |
|
27 | 29 |
end |
28 | 30 |
|
29 | 31 |
def twitter_oauth_token_secret |
30 |
- options['oauth_token_secret'].presence || options['access_secret'].presence || credential('twitter_oauth_token_secret') |
|
32 |
+ self.service.secret |
|
31 | 33 |
end |
32 | 34 |
|
33 | 35 |
def twitter |
@@ -0,0 +1,40 @@ |
||
1 |
+class ServicesController < ApplicationController |
|
2 |
+ |
|
3 |
+ def index |
|
4 |
+ @services = current_user.services.page(params[:page]) |
|
5 |
+ |
|
6 |
+ respond_to do |format| |
|
7 |
+ format.html |
|
8 |
+ format.json { render json: @services } |
|
9 |
+ end |
|
10 |
+ end |
|
11 |
+ |
|
12 |
+ def destroy |
|
13 |
+ @services = current_user.services.find(params[:id]) |
|
14 |
+ @services.destroy |
|
15 |
+ |
|
16 |
+ respond_to do |format| |
|
17 |
+ format.html { redirect_to services_path } |
|
18 |
+ format.json { head :no_content } |
|
19 |
+ end |
|
20 |
+ end |
|
21 |
+ |
|
22 |
+ def toggle_availability |
|
23 |
+ @service = current_user.services.find(params[:id]) |
|
24 |
+ @service.toggle_availability! |
|
25 |
+ |
|
26 |
+ respond_to do |format| |
|
27 |
+ format.html { redirect_to services_path } |
|
28 |
+ format.json { render json: @service } |
|
29 |
+ end |
|
30 |
+ end |
|
31 |
+ |
|
32 |
+ def callback |
|
33 |
+ @service = current_user.services.initialize_or_update_via_omniauth(request.env['omniauth.auth']) |
|
34 |
+ if @service && @service.save |
|
35 |
+ redirect_to services_path, notice: "The service was successfully created." |
|
36 |
+ else |
|
37 |
+ redirect_to services_path, error: "Error creating the service." |
|
38 |
+ end |
|
39 |
+ end |
|
40 |
+end |
@@ -40,6 +40,7 @@ class Agent < ActiveRecord::Base |
||
40 | 40 |
after_save :possibly_update_event_expirations |
41 | 41 |
|
42 | 42 |
belongs_to :user, :inverse_of => :agents |
43 |
+ belongs_to :service |
|
43 | 44 |
has_many :events, -> { order("events.id desc") }, :dependent => :delete_all, :inverse_of => :agent |
44 | 45 |
has_one :most_recent_event, :inverse_of => :agent, :class_name => "Event", :order => "events.id desc" |
45 | 46 |
has_many :logs, -> { order("agent_logs.id desc") }, :dependent => :delete_all, :inverse_of => :agent, :class_name => "AgentLog" |
@@ -2,17 +2,16 @@ module Agents |
||
2 | 2 |
class BasecampAgent < Agent |
3 | 3 |
cannot_receive_events! |
4 | 4 |
|
5 |
+ include Oauthable |
|
6 |
+ valid_oauth_providers '37signals' |
|
7 |
+ |
|
5 | 8 |
description <<-MD |
6 | 9 |
The BasecampAgent checks a Basecamp project for new Events |
7 | 10 |
|
8 |
- It is required that you enter your Basecamp credentials (`username` and `password`). |
|
9 |
- |
|
10 |
- You also need to provide your Basecamp `user_id` and the `project_id` of the project you want to monitor. |
|
11 |
+ You need to provide the `project_id` of the project you want to monitor. |
|
11 | 12 |
If you have your Basecamp project opened in your browser you can find the user_id and project_id as follows: |
12 | 13 |
|
13 |
- `https://basecamp.com/` |
|
14 |
- user_id |
|
15 |
- `/projects/` |
|
14 |
+ `https://basecamp.com/123456/projects/` |
|
16 | 15 |
project_id |
17 | 16 |
`-explore-basecamp` |
18 | 17 |
MD |
@@ -45,17 +44,11 @@ module Agents |
||
45 | 44 |
|
46 | 45 |
def default_options |
47 | 46 |
{ |
48 |
- 'username' => '', |
|
49 |
- 'password' => '', |
|
50 |
- 'user_id' => '', |
|
51 | 47 |
'project_id' => '', |
52 | 48 |
} |
53 | 49 |
end |
54 | 50 |
|
55 | 51 |
def validate_options |
56 |
- errors.add(:base, "you need to specify your basecamp username") unless options['username'].present? |
|
57 |
- errors.add(:base, "you need to specify your basecamp password") unless options['password'].present? |
|
58 |
- errors.add(:base, "you need to specify your basecamp user id") unless options['user_id'].present? |
|
59 | 52 |
errors.add(:base, "you need to specify the basecamp project id of which you want to receive events") unless options['project_id'].present? |
60 | 53 |
end |
61 | 54 |
|
@@ -64,6 +57,7 @@ module Agents |
||
64 | 57 |
end |
65 | 58 |
|
66 | 59 |
def check |
60 |
+ self.service.prepare_request |
|
67 | 61 |
reponse = HTTParty.get request_url, request_options.merge(query_parameters) |
68 | 62 |
memory[:last_run] = Time.now.utc.iso8601 |
69 | 63 |
if last_check_at != nil |
@@ -76,11 +70,11 @@ module Agents |
||
76 | 70 |
|
77 | 71 |
private |
78 | 72 |
def request_url |
79 |
- "https://basecamp.com/#{URI.encode(options[:user_id].to_s)}/api/v1/projects/#{URI.encode(options[:project_id].to_s)}/events.json" |
|
73 |
+ "https://basecamp.com/#{URI.encode(self.service.options[:user_id].to_s)}/api/v1/projects/#{URI.encode(options[:project_id].to_s)}/events.json" |
|
80 | 74 |
end |
81 | 75 |
|
82 | 76 |
def request_options |
83 |
- {:basic_auth => {:username =>options[:username], :password=>options[:password]}, :headers => {"User-Agent" => "Huginn (https://github.com/cantino/huginn)"}} |
|
77 |
+ {:headers => {"User-Agent" => "Huginn (https://github.com/cantino/huginn)", "Authorization" => "Bearer \"#{self.service.token}\""}} |
|
84 | 78 |
end |
85 | 79 |
|
86 | 80 |
def query_parameters |
@@ -0,0 +1,59 @@ |
||
1 |
+class Service < ActiveRecord::Base |
|
2 |
+ attr_accessible :provider, :name, :token, :secret, :refresh_token, :expires_at, :global, :options |
|
3 |
+ |
|
4 |
+ serialize :options, Hash |
|
5 |
+ |
|
6 |
+ belongs_to :user |
|
7 |
+ |
|
8 |
+ validates_presence_of :user_id, :provider, :name, :token |
|
9 |
+ |
|
10 |
+ def toggle_availability! |
|
11 |
+ self.global = !self.global |
|
12 |
+ self.save! |
|
13 |
+ end |
|
14 |
+ |
|
15 |
+ def prepare_request |
|
16 |
+ if self.expires_at && Time.now > self.expires_at |
|
17 |
+ self.refresh_token! |
|
18 |
+ end |
|
19 |
+ end |
|
20 |
+ |
|
21 |
+ def refresh_token! |
|
22 |
+ response = HTTParty.post(endpoint, query: { |
|
23 |
+ type: 'refresh', |
|
24 |
+ client_id: ENV["#{self.provider.upcase}_OAUTH_KEY"], |
|
25 |
+ client_secret: ENV["#{self.provider.upcase}_OAUTH_SECRET"], |
|
26 |
+ refresh_token: self.refresh_token |
|
27 |
+ }) |
|
28 |
+ data = JSON.parse(response.body) |
|
29 |
+ self.update(expires_at: Time.now + data['expires_in'], token: data['access_token'], refresh_token: data['refresh_token'].presence || self.refresh_token) |
|
30 |
+ end |
|
31 |
+ |
|
32 |
+ def self.initialize_or_update_via_omniauth(omniauth) |
|
33 |
+ case omniauth['provider'] |
|
34 |
+ when 'twitter' |
|
35 |
+ find_or_initialize_by(provider: omniauth['provider'], name: omniauth['info']['nickname']).tap do |service| |
|
36 |
+ service.assign_attributes(token: omniauth['credentials']['token'], secret: omniauth['credentials']['secret']) |
|
37 |
+ end |
|
38 |
+ when 'github' |
|
39 |
+ find_or_initialize_by(provider: omniauth['provider'], name: omniauth['info']['nickname']).tap do |service| |
|
40 |
+ service.assign_attributes(token: omniauth['credentials']['token']) |
|
41 |
+ end |
|
42 |
+ when '37signals' |
|
43 |
+ find_or_initialize_by(provider: omniauth['provider'], name: omniauth['info']['name']).tap do |service| |
|
44 |
+ service.assign_attributes(token: omniauth['credentials']['token'], |
|
45 |
+ refresh_token: omniauth['credentials']['refresh_token'], |
|
46 |
+ expires_at: Time.at(omniauth['credentials']['expires_at']), |
|
47 |
+ options: {user_id: omniauth['extra']['accounts'][0]['id']}) |
|
48 |
+ end |
|
49 |
+ else |
|
50 |
+ false |
|
51 |
+ end |
|
52 |
+ end |
|
53 |
+ |
|
54 |
+ private |
|
55 |
+ def endpoint |
|
56 |
+ client_options = "OmniAuth::Strategies::#{OmniAuth::Utils.camelize(self.provider)}".constantize.default_options['client_options'] |
|
57 |
+ URI.join(client_options['site'], client_options['token_url']) |
|
58 |
+ end |
|
59 |
+end |
@@ -26,6 +26,12 @@ class User < ActiveRecord::Base |
||
26 | 26 |
has_many :events, -> { order("events.created_at desc") }, :dependent => :delete_all, :inverse_of => :user |
27 | 27 |
has_many :agents, -> { order("agents.created_at desc") }, :dependent => :destroy, :inverse_of => :user |
28 | 28 |
has_many :logs, :through => :agents, :class_name => "AgentLog" |
29 |
+ has_many :services, -> { order("services.name")}, :dependent => :destroy |
|
30 |
+ |
|
31 |
+ |
|
32 |
+ def available_services |
|
33 |
+ Service.where("user_id = #{self.id} or global = true").order("services.name desc") |
|
34 |
+ end |
|
29 | 35 |
|
30 | 36 |
# Allow users to login via either email or username. |
31 | 37 |
def self.find_first_by_auth_conditions(warden_conditions) |
@@ -25,11 +25,18 @@ |
||
25 | 25 |
</div> |
26 | 26 |
<% end %> |
27 | 27 |
|
28 |
- <div class="form-group"> |
|
28 |
+ <div class="form-group type-select"> |
|
29 | 29 |
<%= f.label :name %> |
30 | 30 |
<%= f.text_field :name, :class => 'form-control' %> |
31 | 31 |
</div> |
32 | 32 |
|
33 |
+ <% if @agent.try(:oauthable?) %> |
|
34 |
+ <div class="form-group type-select"> |
|
35 |
+ <%= f.label :service %> |
|
36 |
+ <%= 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' %> |
|
37 |
+ </div> |
|
38 |
+ <% end %> |
|
39 |
+ |
|
33 | 40 |
<div class="form-group"> |
34 | 41 |
<%= f.label :schedule, :class => 'control-label' %> |
35 | 42 |
<div class="schedule-region" data-can-be-scheduled="<%= @agent.can_be_scheduled? %>"> |
@@ -15,6 +15,7 @@ |
||
15 | 15 |
<%= nav_link "Agents", agents_path %> |
16 | 16 |
<%= nav_link "Events", events_path %> |
17 | 17 |
<%= nav_link "Credentials", user_credentials_path %> |
18 |
+ <%= nav_link "Services", services_path %> |
|
18 | 19 |
</ul> |
19 | 20 |
<% end %> |
20 | 21 |
|
@@ -0,0 +1,52 @@ |
||
1 |
+<div class='container'> |
|
2 |
+ <div class='row'> |
|
3 |
+ <div class='col-md-12'> |
|
4 |
+ <div class="page-header"> |
|
5 |
+ <h2> |
|
6 |
+ Your Services |
|
7 |
+ </h2> |
|
8 |
+ </div> |
|
9 |
+ <p> |
|
10 |
+ Before you can authenticate with a service, you need to set it up. Have a look at the |
|
11 |
+ <%= link_to 'wiki', 'tobedone', target: :_blank %> |
|
12 |
+ for guidance. |
|
13 |
+ </p> |
|
14 |
+ <p><%= link_to "Authenticate with Twitter", "/auth/twitter" %></p> |
|
15 |
+ <p><%= link_to "Authenticate with 37Signals (Basecamp)", "/auth/37signals" %></p> |
|
16 |
+ <p><%= link_to "Authenticate with Github", "/auth/github" %></p> |
|
17 |
+ <hr> |
|
18 |
+ |
|
19 |
+ <div class='table-responsive'> |
|
20 |
+ <table class='table table-striped events'> |
|
21 |
+ <tr> |
|
22 |
+ <th>Provider</th> |
|
23 |
+ <th>Username</th> |
|
24 |
+ <th>Global?</th> |
|
25 |
+ <th></th> |
|
26 |
+ </tr> |
|
27 |
+ |
|
28 |
+ <% @services.each do |service| %> |
|
29 |
+ <tr> |
|
30 |
+ <td><%= service.provider %></td> |
|
31 |
+ <td><%= service.name %></td> |
|
32 |
+ <td><%= service.global ? 'Yes' : 'No' %></td> |
|
33 |
+ <td> |
|
34 |
+ <div class="btn-group btn-group-xs"> |
|
35 |
+ <% if service.global %> |
|
36 |
+ <%= 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" %> |
|
37 |
+ <% else %> |
|
38 |
+ <%= 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" %> |
|
39 |
+ <% end %> |
|
40 |
+ <%= link_to 'Delete', service_path(service), method: :delete, data: { confirm: 'Are you sure?' }, class: "btn btn-default btn-danger" %> |
|
41 |
+ </div> |
|
42 |
+ </td> |
|
43 |
+ </tr> |
|
44 |
+ <% end %> |
|
45 |
+ </table> |
|
46 |
+ </div> |
|
47 |
+ |
|
48 |
+ <%= paginate @services, :theme => 'twitter-bootstrap-3' %> |
|
49 |
+ </div> |
|
50 |
+ </div> |
|
51 |
+</div> |
|
52 |
+ |
@@ -0,0 +1,5 @@ |
||
1 |
+Rails.application.config.middleware.use OmniAuth::Builder do |
|
2 |
+ provider :twitter, ENV['TWITTER_OAUTH_KEY'], ENV['TWITTER_OAUTH_SECRET'], authorize_params: {force_login: 'true', use_authorize: 'true'} |
|
3 |
+ provider '37signals', ENV['37SIGNALS_OAUTH_KEY'], ENV['37SIGNALS_OAUTH_SECRET'] |
|
4 |
+ provider :github, ENV['GITHUB_OAUTH_KEY'], ENV['GITHUB_OAUTH_SECRET'] |
|
5 |
+end |
@@ -28,6 +28,12 @@ Huginn::Application.routes.draw do |
||
28 | 28 |
|
29 | 29 |
resources :user_credentials, :except => :show |
30 | 30 |
|
31 |
+ resources :services, :only => [:index, :destroy] do |
|
32 |
+ member do |
|
33 |
+ post :toggle_availability |
|
34 |
+ end |
|
35 |
+ end |
|
36 |
+ |
|
31 | 37 |
get "/worker_status" => "worker_status#show" |
32 | 38 |
|
33 | 39 |
post "/users/:user_id/update_location/:secret" => "user_location_updates#create" |
@@ -39,6 +45,7 @@ Huginn::Application.routes.draw do |
||
39 | 45 |
# get "/delayed_job" => DelayedJobWeb, :anchor => false |
40 | 46 |
|
41 | 47 |
devise_for :users, :sign_out_via => [ :post, :delete ] |
48 |
+ get '/auth/:provider/callback', to: 'services#callback' |
|
42 | 49 |
|
43 | 50 |
get "/about" => "home#about" |
44 | 51 |
root :to => "home#index" |
@@ -0,0 +1,18 @@ |
||
1 |
+class CreateServices < ActiveRecord::Migration |
|
2 |
+ def change |
|
3 |
+ create_table :services do |t| |
|
4 |
+ t.integer :user_id |
|
5 |
+ t.string :provider |
|
6 |
+ t.string :name |
|
7 |
+ t.text :token |
|
8 |
+ t.text :secret |
|
9 |
+ t.text :refresh_token |
|
10 |
+ t.datetime :expires_at |
|
11 |
+ t.boolean :global, default: false |
|
12 |
+ t.text :options |
|
13 |
+ t.timestamps |
|
14 |
+ end |
|
15 |
+ add_index :services, :user_id |
|
16 |
+ add_index :services, [:user_id, :global] |
|
17 |
+ end |
|
18 |
+end |
@@ -0,0 +1,5 @@ |
||
1 |
+class AddServiceIdToAgents < ActiveRecord::Migration |
|
2 |
+ def change |
|
3 |
+ add_column :agents, :service_id, :integer |
|
4 |
+ end |
|
5 |
+end |
@@ -0,0 +1,39 @@ |
||
1 |
+class MigrateAgentsToServiceAuthentication < ActiveRecord::Migration |
|
2 |
+ def up |
|
3 |
+ agents = Agent.where(type: ['Agents::TwitterUserAgent', 'Agents::TwitterStreamAgent', 'Agents::TwitterPublishAgent']).each do |agent| |
|
4 |
+ service = agent.user.services.create!( |
|
5 |
+ provider: 'twitter', |
|
6 |
+ name: "Migrated '#{agent.name}'", |
|
7 |
+ token: agent.twitter_oauth_token, |
|
8 |
+ secret: agent.twitter_oauth_token_secret |
|
9 |
+ ) |
|
10 |
+ agent.service_id = service.id |
|
11 |
+ agent.save! |
|
12 |
+ end |
|
13 |
+ if agents.length > 0 |
|
14 |
+ puts <<-EOF.strip_heredoc |
|
15 |
+ |
|
16 |
+ Your Twitter agents were successfully migrated. You need to update your .env file and add the following two lines: |
|
17 |
+ |
|
18 |
+ TWITTER_OAUTH_KEY=#{agents.first.twitter_consumer_key} |
|
19 |
+ TWITTER_OAUTH_SECRET=#{agents.first.twitter_consumer_secret} |
|
20 |
+ |
|
21 |
+ |
|
22 |
+ EOF |
|
23 |
+ end |
|
24 |
+ if Agent.where(type: ['Agents::BasecampAgent']).count > 0 |
|
25 |
+ puts <<-EOF.strip_heredoc |
|
26 |
+ |
|
27 |
+ Your Basecamp agents can not be migrated automatically. You need to manually register an application with 37signals and authenticate huginn to use it. |
|
28 |
+ Have a look at the <Wiki TBD> if you need help. |
|
29 |
+ |
|
30 |
+ |
|
31 |
+ EOF |
|
32 |
+ end |
|
33 |
+ end |
|
34 |
+ |
|
35 |
+ def down |
|
36 |
+ raise ActiveRecord::IrreversibleMigration, "Cannot revert migration to OAuth services" |
|
37 |
+ end |
|
38 |
+end |
|
39 |
+ |
@@ -9,21 +9,24 @@ |
||
9 | 9 |
# from scratch. The latter is a flawed and unsustainable approach (the more migrations |
10 | 10 |
# you'll amass, the slower it'll run and the greater likelihood for issues). |
11 | 11 |
# |
12 |
-# It's strongly recommended to check this file into your version control system. |
|
12 |
+# It's strongly recommended that you check this file into your version control system. |
|
13 | 13 |
|
14 |
-ActiveRecord::Schema.define(:version => 20140408150825) do |
|
14 |
+ActiveRecord::Schema.define(version: 20140525150140) do |
|
15 | 15 |
|
16 |
- create_table "agent_logs", :force => true do |t| |
|
17 |
- t.integer "agent_id", :null => false |
|
18 |
- t.text "message", :null => false |
|
19 |
- t.integer "level", :default => 3, :null => false |
|
16 |
+ # These are extensions that must be enabled in order to support this database |
|
17 |
+ enable_extension "plpgsql" |
|
18 |
+ |
|
19 |
+ create_table "agent_logs", force: true do |t| |
|
20 |
+ t.integer "agent_id", null: false |
|
21 |
+ t.text "message", null: false |
|
22 |
+ t.integer "level", default: 3, null: false |
|
20 | 23 |
t.integer "inbound_event_id" |
21 | 24 |
t.integer "outbound_event_id" |
22 |
- t.datetime "created_at", :null => false |
|
23 |
- t.datetime "updated_at", :null => false |
|
25 |
+ t.datetime "created_at", null: false |
|
26 |
+ t.datetime "updated_at", null: false |
|
24 | 27 |
end |
25 | 28 |
|
26 |
- create_table "agents", :force => true do |t| |
|
29 |
+ create_table "agents", force: true do |t| |
|
27 | 30 |
t.integer "user_id" |
28 | 31 |
t.text "options" |
29 | 32 |
t.string "type" |
@@ -33,98 +36,116 @@ ActiveRecord::Schema.define(:version => 20140408150825) do |
||
33 | 36 |
t.datetime "last_check_at" |
34 | 37 |
t.datetime "last_receive_at" |
35 | 38 |
t.integer "last_checked_event_id" |
36 |
- t.datetime "created_at", :null => false |
|
37 |
- t.datetime "updated_at", :null => false |
|
38 |
- t.text "memory", :limit => 2147483647 |
|
39 |
+ t.datetime "created_at", null: false |
|
40 |
+ t.datetime "updated_at", null: false |
|
41 |
+ t.text "memory" |
|
39 | 42 |
t.datetime "last_web_request_at" |
40 |
- t.integer "keep_events_for", :default => 0, :null => false |
|
43 |
+ t.integer "keep_events_for", default: 0, null: false |
|
41 | 44 |
t.datetime "last_event_at" |
42 | 45 |
t.datetime "last_error_log_at" |
43 |
- t.boolean "propagate_immediately", :default => false, :null => false |
|
44 |
- t.boolean "disabled", :default => false, :null => false |
|
46 |
+ t.boolean "propagate_immediately", default: false, null: false |
|
47 |
+ t.boolean "disabled", default: false, null: false |
|
48 |
+ t.integer "service_id" |
|
45 | 49 |
end |
46 | 50 |
|
47 |
- add_index "agents", ["schedule"], :name => "index_agents_on_schedule" |
|
48 |
- add_index "agents", ["type"], :name => "index_agents_on_type" |
|
49 |
- add_index "agents", ["user_id", "created_at"], :name => "index_agents_on_user_id_and_created_at" |
|
51 |
+ add_index "agents", ["schedule"], name: "index_agents_on_schedule", using: :btree |
|
52 |
+ add_index "agents", ["type"], name: "index_agents_on_type", using: :btree |
|
53 |
+ add_index "agents", ["user_id", "created_at"], name: "index_agents_on_user_id_and_created_at", using: :btree |
|
50 | 54 |
|
51 |
- create_table "delayed_jobs", :force => true do |t| |
|
52 |
- t.integer "priority", :default => 0 |
|
53 |
- t.integer "attempts", :default => 0 |
|
54 |
- t.text "handler", :limit => 16777215 |
|
55 |
+ create_table "delayed_jobs", force: true do |t| |
|
56 |
+ t.integer "priority", default: 0 |
|
57 |
+ t.integer "attempts", default: 0 |
|
58 |
+ t.text "handler" |
|
55 | 59 |
t.text "last_error" |
56 | 60 |
t.datetime "run_at" |
57 | 61 |
t.datetime "locked_at" |
58 | 62 |
t.datetime "failed_at" |
59 | 63 |
t.string "locked_by" |
60 | 64 |
t.string "queue" |
61 |
- t.datetime "created_at", :null => false |
|
62 |
- t.datetime "updated_at", :null => false |
|
65 |
+ t.datetime "created_at", null: false |
|
66 |
+ t.datetime "updated_at", null: false |
|
63 | 67 |
end |
64 | 68 |
|
65 |
- add_index "delayed_jobs", ["priority", "run_at"], :name => "delayed_jobs_priority" |
|
69 |
+ add_index "delayed_jobs", ["priority", "run_at"], name: "delayed_jobs_priority", using: :btree |
|
66 | 70 |
|
67 |
- create_table "events", :force => true do |t| |
|
71 |
+ create_table "events", force: true do |t| |
|
68 | 72 |
t.integer "user_id" |
69 | 73 |
t.integer "agent_id" |
70 |
- t.decimal "lat", :precision => 15, :scale => 10 |
|
71 |
- t.decimal "lng", :precision => 15, :scale => 10 |
|
72 |
- t.text "payload", :limit => 16777215 |
|
73 |
- t.datetime "created_at", :null => false |
|
74 |
- t.datetime "updated_at", :null => false |
|
74 |
+ t.decimal "lat", precision: 15, scale: 10 |
|
75 |
+ t.decimal "lng", precision: 15, scale: 10 |
|
76 |
+ t.text "payload" |
|
77 |
+ t.datetime "created_at", null: false |
|
78 |
+ t.datetime "updated_at", null: false |
|
75 | 79 |
t.datetime "expires_at" |
76 | 80 |
end |
77 | 81 |
|
78 |
- add_index "events", ["agent_id", "created_at"], :name => "index_events_on_agent_id_and_created_at" |
|
79 |
- add_index "events", ["expires_at"], :name => "index_events_on_expires_at" |
|
80 |
- add_index "events", ["user_id", "created_at"], :name => "index_events_on_user_id_and_created_at" |
|
82 |
+ add_index "events", ["agent_id", "created_at"], name: "index_events_on_agent_id_and_created_at", using: :btree |
|
83 |
+ add_index "events", ["expires_at"], name: "index_events_on_expires_at", using: :btree |
|
84 |
+ add_index "events", ["user_id", "created_at"], name: "index_events_on_user_id_and_created_at", using: :btree |
|
81 | 85 |
|
82 |
- create_table "links", :force => true do |t| |
|
86 |
+ create_table "links", force: true do |t| |
|
83 | 87 |
t.integer "source_id" |
84 | 88 |
t.integer "receiver_id" |
85 |
- t.datetime "created_at", :null => false |
|
86 |
- t.datetime "updated_at", :null => false |
|
87 |
- t.integer "event_id_at_creation", :default => 0, :null => false |
|
89 |
+ t.datetime "created_at", null: false |
|
90 |
+ t.datetime "updated_at", null: false |
|
91 |
+ t.integer "event_id_at_creation", default: 0, null: false |
|
92 |
+ end |
|
93 |
+ |
|
94 |
+ add_index "links", ["receiver_id", "source_id"], name: "index_links_on_receiver_id_and_source_id", using: :btree |
|
95 |
+ add_index "links", ["source_id", "receiver_id"], name: "index_links_on_source_id_and_receiver_id", using: :btree |
|
96 |
+ |
|
97 |
+ create_table "services", force: true do |t| |
|
98 |
+ t.integer "user_id" |
|
99 |
+ t.string "provider" |
|
100 |
+ t.string "name" |
|
101 |
+ t.text "token" |
|
102 |
+ t.text "secret" |
|
103 |
+ t.text "refresh_token" |
|
104 |
+ t.datetime "expires_at" |
|
105 |
+ t.boolean "global", default: false |
|
106 |
+ t.text "options" |
|
107 |
+ t.datetime "created_at" |
|
108 |
+ t.datetime "updated_at" |
|
88 | 109 |
end |
89 | 110 |
|
90 |
- add_index "links", ["receiver_id", "source_id"], :name => "index_links_on_receiver_id_and_source_id" |
|
91 |
- add_index "links", ["source_id", "receiver_id"], :name => "index_links_on_source_id_and_receiver_id" |
|
111 |
+ add_index "services", ["user_id", "global"], name: "index_accounts_on_user_id_and_global", using: :btree |
|
112 |
+ add_index "services", ["user_id"], name: "index_accounts_on_user_id", using: :btree |
|
92 | 113 |
|
93 |
- create_table "user_credentials", :force => true do |t| |
|
94 |
- t.integer "user_id", :null => false |
|
95 |
- t.string "credential_name", :null => false |
|
96 |
- t.text "credential_value", :null => false |
|
97 |
- t.datetime "created_at", :null => false |
|
98 |
- t.datetime "updated_at", :null => false |
|
99 |
- t.string "mode", :default => "text", :null => false |
|
114 |
+ create_table "user_credentials", force: true do |t| |
|
115 |
+ t.integer "user_id", null: false |
|
116 |
+ t.string "credential_name", null: false |
|
117 |
+ t.text "credential_value", null: false |
|
118 |
+ t.datetime "created_at", null: false |
|
119 |
+ t.datetime "updated_at", null: false |
|
120 |
+ t.string "mode", default: "text", null: false |
|
100 | 121 |
end |
101 | 122 |
|
102 |
- add_index "user_credentials", ["user_id", "credential_name"], :name => "index_user_credentials_on_user_id_and_credential_name", :unique => true |
|
123 |
+ add_index "user_credentials", ["user_id", "credential_name"], name: "index_user_credentials_on_user_id_and_credential_name", unique: true, using: :btree |
|
103 | 124 |
|
104 |
- create_table "users", :force => true do |t| |
|
105 |
- t.string "email", :default => "", :null => false |
|
106 |
- t.string "encrypted_password", :default => "", :null => false |
|
125 |
+ create_table "users", force: true do |t| |
|
126 |
+ t.string "email", default: "", null: false |
|
127 |
+ t.string "encrypted_password", default: "", null: false |
|
107 | 128 |
t.string "reset_password_token" |
108 | 129 |
t.datetime "reset_password_sent_at" |
109 | 130 |
t.datetime "remember_created_at" |
110 |
- t.integer "sign_in_count", :default => 0 |
|
131 |
+ t.integer "sign_in_count", default: 0 |
|
111 | 132 |
t.datetime "current_sign_in_at" |
112 | 133 |
t.datetime "last_sign_in_at" |
113 | 134 |
t.string "current_sign_in_ip" |
114 | 135 |
t.string "last_sign_in_ip" |
115 |
- t.datetime "created_at", :null => false |
|
116 |
- t.datetime "updated_at", :null => false |
|
117 |
- t.boolean "admin", :default => false, :null => false |
|
118 |
- t.integer "failed_attempts", :default => 0 |
|
136 |
+ t.datetime "created_at", null: false |
|
137 |
+ t.datetime "updated_at", null: false |
|
138 |
+ t.boolean "admin", default: false, null: false |
|
139 |
+ t.integer "failed_attempts", default: 0 |
|
119 | 140 |
t.string "unlock_token" |
120 | 141 |
t.datetime "locked_at" |
121 |
- t.string "username", :null => false |
|
122 |
- t.string "invitation_code", :null => false |
|
142 |
+ t.string "username", null: false |
|
143 |
+ t.string "invitation_code", null: false |
|
123 | 144 |
end |
124 | 145 |
|
125 |
- add_index "users", ["email"], :name => "index_users_on_email", :unique => true |
|
126 |
- add_index "users", ["reset_password_token"], :name => "index_users_on_reset_password_token", :unique => true |
|
127 |
- add_index "users", ["unlock_token"], :name => "index_users_on_unlock_token", :unique => true |
|
128 |
- add_index "users", ["username"], :name => "index_users_on_username", :unique => true |
|
146 |
+ add_index "users", ["email"], name: "index_users_on_email", unique: true, using: :btree |
|
147 |
+ add_index "users", ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true, using: :btree |
|
148 |
+ add_index "users", ["unlock_token"], name: "index_users_on_unlock_token", unique: true, using: :btree |
|
149 |
+ add_index "users", ["username"], name: "index_users_on_username", unique: true, using: :btree |
|
129 | 150 |
|
130 | 151 |
end |
@@ -0,0 +1,57 @@ |
||
1 |
+require 'spec_helper' |
|
2 |
+ |
|
3 |
+describe ServicesController do |
|
4 |
+ before do |
|
5 |
+ sign_in users(:bob) |
|
6 |
+ OmniAuth.config.test_mode = true |
|
7 |
+ request.env["omniauth.auth"] = JSON.parse(File.read(Rails.root.join('spec/data_fixtures/services/twitter.json'))) |
|
8 |
+ end |
|
9 |
+ |
|
10 |
+ describe "GET index" do |
|
11 |
+ it "only returns sevices of the current user" do |
|
12 |
+ get :index |
|
13 |
+ assigns(:services).all? {|i| i.user.should == users(:bob) }.should be_true |
|
14 |
+ end |
|
15 |
+ end |
|
16 |
+ |
|
17 |
+ describe "POST toggle_availability" do |
|
18 |
+ it "should work for service of the user" do |
|
19 |
+ post :toggle_availability, :id => services(:generic).to_param |
|
20 |
+ assigns(:service).should eq(services(:generic)) |
|
21 |
+ redirect_to(services_path) |
|
22 |
+ end |
|
23 |
+ |
|
24 |
+ it "should not work for a service of another user" do |
|
25 |
+ lambda { |
|
26 |
+ post :toggle_availability, :id => services(:global).to_param |
|
27 |
+ }.should raise_error(ActiveRecord::RecordNotFound) |
|
28 |
+ end |
|
29 |
+ end |
|
30 |
+ |
|
31 |
+ describe "DELETE destroy" do |
|
32 |
+ it "destroys only services owned by the current user" do |
|
33 |
+ expect { |
|
34 |
+ delete :destroy, :id => services(:generic).to_param |
|
35 |
+ }.to change(Service, :count).by(-1) |
|
36 |
+ |
|
37 |
+ lambda { |
|
38 |
+ delete :destroy, :id => services(:global).to_param |
|
39 |
+ }.should raise_error(ActiveRecord::RecordNotFound) |
|
40 |
+ end |
|
41 |
+ end |
|
42 |
+ |
|
43 |
+ describe "accepting a callback url" do |
|
44 |
+ it "should update the users credentials" do |
|
45 |
+ expect { |
|
46 |
+ get :callback, provider: 'twitter' |
|
47 |
+ }.to change { users(:bob).services.count }.by(1) |
|
48 |
+ end |
|
49 |
+ |
|
50 |
+ it "should not work with an unknown provider" do |
|
51 |
+ request.env["omniauth.auth"]['provider'] = 'unknown' |
|
52 |
+ expect { |
|
53 |
+ get :callback, provider: 'unknown' |
|
54 |
+ }.to change { users(:bob).services.count }.by(0) |
|
55 |
+ end |
|
56 |
+ end |
|
57 |
+end |
@@ -0,0 +1,43 @@ |
||
1 |
+{ |
|
2 |
+ "provider": "37signals", |
|
3 |
+ "uid": 12345, |
|
4 |
+ "info": { |
|
5 |
+ "email": "basecamp@none.de", |
|
6 |
+ "first_name": "Dominik", |
|
7 |
+ "last_name": "Sander", |
|
8 |
+ "name": "Dominik Sander" |
|
9 |
+ }, |
|
10 |
+ "credentials": { |
|
11 |
+ "token": "abcde", |
|
12 |
+ "refresh_token": "fghrefresh", |
|
13 |
+ "expires_at": 1401554352, |
|
14 |
+ "expires": true |
|
15 |
+ }, |
|
16 |
+ "extra": { |
|
17 |
+ "accounts": [ |
|
18 |
+ { |
|
19 |
+ "product": "bcx", |
|
20 |
+ "name": "Dominik Sander's Basecamp", |
|
21 |
+ "id": 12345, |
|
22 |
+ "href": "https://basecamp.com/12345/api/v1" |
|
23 |
+ } |
|
24 |
+ ], |
|
25 |
+ "raw_info": { |
|
26 |
+ "expires_at": "2014-05-31T16:39:12Z", |
|
27 |
+ "identity": { |
|
28 |
+ "first_name": "Dominik", |
|
29 |
+ "last_name": "Sander", |
|
30 |
+ "email_address": "basecamp@none.de", |
|
31 |
+ "id": 12345 |
|
32 |
+ }, |
|
33 |
+ "accounts": [ |
|
34 |
+ { |
|
35 |
+ "product": "bcx", |
|
36 |
+ "name": "Dominik Sander's Basecamp", |
|
37 |
+ "id": 12345, |
|
38 |
+ "href": "https://basecamp.com/12345/api/v1" |
|
39 |
+ } |
|
40 |
+ ] |
|
41 |
+ } |
|
42 |
+ } |
|
43 |
+} |
@@ -0,0 +1,52 @@ |
||
1 |
+{ |
|
2 |
+ "provider": "github", |
|
3 |
+ "uid": "12345", |
|
4 |
+ "info": { |
|
5 |
+ "nickname": "dsander", |
|
6 |
+ "email": null, |
|
7 |
+ "name": "Dominik Sander", |
|
8 |
+ "image": "https://avatars.githubusercontent.com/u/12345?", |
|
9 |
+ "urls": { |
|
10 |
+ "GitHub": "https://github.com/dsander", |
|
11 |
+ "Blog": "http://www.dsander.de" |
|
12 |
+ } |
|
13 |
+ }, |
|
14 |
+ "credentials": { |
|
15 |
+ "token": "agithubtoken", |
|
16 |
+ "expires": false |
|
17 |
+ }, |
|
18 |
+ "extra": { |
|
19 |
+ "raw_info": { |
|
20 |
+ "login": "dsander", |
|
21 |
+ "id": 12345, |
|
22 |
+ "avatar_url": "https://avatars.githubusercontent.com/u/12345?", |
|
23 |
+ "gravatar_id": "fsdfsdf", |
|
24 |
+ "url": "https://api.github.com/users/dsander", |
|
25 |
+ "html_url": "https://github.com/dsander", |
|
26 |
+ "followers_url": "https://api.github.com/users/dsander/followers", |
|
27 |
+ "following_url": "https://api.github.com/users/dsander/following{/other_user}", |
|
28 |
+ "gists_url": "https://api.github.com/users/dsander/gists{/gist_id}", |
|
29 |
+ "starred_url": "https://api.github.com/users/dsander/starred{/owner}{/repo}", |
|
30 |
+ "subscriptions_url": "https://api.github.com/users/dsander/subscriptions", |
|
31 |
+ "organizations_url": "https://api.github.com/users/dsander/orgs", |
|
32 |
+ "repos_url": "https://api.github.com/users/dsander/repos", |
|
33 |
+ "events_url": "https://api.github.com/users/dsander/events{/privacy}", |
|
34 |
+ "received_events_url": "https://api.github.com/users/dsander/received_events", |
|
35 |
+ "type": "User", |
|
36 |
+ "site_admin": false, |
|
37 |
+ "name": "Dominik Sander", |
|
38 |
+ "company": null, |
|
39 |
+ "blog": "http://www.url.de", |
|
40 |
+ "location": null, |
|
41 |
+ "email": null, |
|
42 |
+ "hireable": false, |
|
43 |
+ "bio": null, |
|
44 |
+ "public_repos": 29, |
|
45 |
+ "public_gists": 2, |
|
46 |
+ "followers": 21, |
|
47 |
+ "following": 9, |
|
48 |
+ "created_at": "2008-08-17T18:17:50Z", |
|
49 |
+ "updated_at": "2014-05-19T09:30:08Z" |
|
50 |
+ } |
|
51 |
+ } |
|
52 |
+} |
@@ -0,0 +1,66 @@ |
||
1 |
+{ |
|
2 |
+ "provider": "twitter", |
|
3 |
+ "uid": "123456", |
|
4 |
+ "info": { |
|
5 |
+ "nickname": "johnqpublic", |
|
6 |
+ "name": "John Q Public", |
|
7 |
+ "location": "Anytown, USA", |
|
8 |
+ "image": "http://si0.twimg.com/sticky/default_profile_images/default_profile_2_normal.png", |
|
9 |
+ "description": "a very normal guy.", |
|
10 |
+ "urls": { |
|
11 |
+ "Website": null, |
|
12 |
+ "Twitter": "https://twitter.com/johnqpublic" |
|
13 |
+ } |
|
14 |
+ }, |
|
15 |
+ "credentials": { |
|
16 |
+ "token": "a1b2c3d4...", |
|
17 |
+ "secret": "abcdef1234" |
|
18 |
+ }, |
|
19 |
+ "extra": { |
|
20 |
+ "access_token": "", |
|
21 |
+ "raw_info": { |
|
22 |
+ "name": "John Q Public", |
|
23 |
+ "listed_count": 0, |
|
24 |
+ "profile_sidebar_border_color": "181A1E", |
|
25 |
+ "url": null, |
|
26 |
+ "lang": "en", |
|
27 |
+ "statuses_count": 129, |
|
28 |
+ "profile_image_url": "http://si0.twimg.com/sticky/default_profile_images/default_profile_2_normal.png", |
|
29 |
+ "profile_background_image_url_https": "https://twimg0-a.akamaihd.net/profile_background_images/229171796/pattern_036.gif", |
|
30 |
+ "location": "Anytown, USA", |
|
31 |
+ "time_zone": "Chicago", |
|
32 |
+ "follow_request_sent": false, |
|
33 |
+ "id": 123456, |
|
34 |
+ "profile_background_tile": true, |
|
35 |
+ "profile_sidebar_fill_color": "666666", |
|
36 |
+ "followers_count": 1, |
|
37 |
+ "default_profile_image": false, |
|
38 |
+ "screen_name": "", |
|
39 |
+ "following": false, |
|
40 |
+ "utc_offset": -3600, |
|
41 |
+ "verified": false, |
|
42 |
+ "favourites_count": 0, |
|
43 |
+ "profile_background_color": "1A1B1F", |
|
44 |
+ "is_translator": false, |
|
45 |
+ "friends_count": 1, |
|
46 |
+ "notifications": false, |
|
47 |
+ "geo_enabled": true, |
|
48 |
+ "profile_background_image_url": "http://twimg0-a.akamaihd.net/profile_background_images/229171796/pattern_036.gif", |
|
49 |
+ "protected": false, |
|
50 |
+ "description": "a very normal guy.", |
|
51 |
+ "profile_link_color": "2FC2EF", |
|
52 |
+ "created_at": "Thu Jul 4 00:00:00 +0000 2013", |
|
53 |
+ "id_str": "123456", |
|
54 |
+ "profile_image_url_https": "https://si0.twimg.com/sticky/default_profile_images/default_profile_2_normal.png", |
|
55 |
+ "default_profile": false, |
|
56 |
+ "profile_use_background_image": false, |
|
57 |
+ "entities": { |
|
58 |
+ "description": { |
|
59 |
+ "urls": [] |
|
60 |
+ } |
|
61 |
+ }, |
|
62 |
+ "profile_text_color": "666666", |
|
63 |
+ "contributors_enabled": false |
|
64 |
+ } |
|
65 |
+ } |
|
66 |
+} |
@@ -0,0 +1,17 @@ |
||
1 |
+generic: |
|
2 |
+ token: 1234token |
|
3 |
+ secret: 56789secret |
|
4 |
+ refresh_token: refresh12345 |
|
5 |
+ provider: testprovider |
|
6 |
+ name: test |
|
7 |
+ expires_at: <%= Time.parse("2015-01-01 00:00:00") %> |
|
8 |
+ options: <%= { user_id: 12345 }.to_yaml.inspect %> |
|
9 |
+ user: bob |
|
10 |
+global: |
|
11 |
+ token: 1234token |
|
12 |
+ provider: testprovider |
|
13 |
+ name: test |
|
14 |
+ expires_at: <%= Time.parse("2015-01-01 00:00:00") %> |
|
15 |
+ options: <%= { user_id: 12345 }.to_yaml.inspect %> |
|
16 |
+ user: jane |
|
17 |
+ global: true |
@@ -1,17 +1,16 @@ |
||
1 | 1 |
require 'spec_helper' |
2 |
+require 'models/concerns/oauthable' |
|
2 | 3 |
|
3 | 4 |
describe Agents::BasecampAgent do |
5 |
+ it_behaves_like Oauthable |
|
6 |
+ |
|
4 | 7 |
before(:each) do |
5 | 8 |
stub_request(:get, /json$/).to_return(:body => File.read(Rails.root.join("spec/data_fixtures/basecamp.json")), :status => 200, :headers => {"Content-Type" => "text/json"}) |
6 | 9 |
stub_request(:get, /Z$/).to_return(:body => File.read(Rails.root.join("spec/data_fixtures/basecamp.json")), :status => 200, :headers => {"Content-Type" => "text/json"}) |
7 |
- @valid_params = { |
|
8 |
- :username => "user", |
|
9 |
- :password => "pass", |
|
10 |
- :user_id => 12345, |
|
11 |
- :project_id => 6789, |
|
12 |
- } |
|
10 |
+ @valid_params = { :project_id => 6789 } |
|
13 | 11 |
|
14 | 12 |
@checker = Agents::BasecampAgent.new(:name => "somename", :options => @valid_params) |
13 |
+ @checker.service = services(:generic) |
|
15 | 14 |
@checker.user = users(:jane) |
16 | 15 |
@checker.save! |
17 | 16 |
end |
@@ -21,21 +20,6 @@ describe Agents::BasecampAgent do |
||
21 | 20 |
@checker.should be_valid |
22 | 21 |
end |
23 | 22 |
|
24 |
- it "should require the basecamp username" do |
|
25 |
- @checker.options['username'] = nil |
|
26 |
- @checker.should_not be_valid |
|
27 |
- end |
|
28 |
- |
|
29 |
- it "should require the basecamp password" do |
|
30 |
- @checker.options['password'] = nil |
|
31 |
- @checker.should_not be_valid |
|
32 |
- end |
|
33 |
- |
|
34 |
- it "should require the basecamp user_id" do |
|
35 |
- @checker.options['user_id'] = nil |
|
36 |
- @checker.should_not be_valid |
|
37 |
- end |
|
38 |
- |
|
39 | 23 |
it "should require the basecamp project_id" do |
40 | 24 |
@checker.options['project_id'] = nil |
41 | 25 |
@checker.should_not be_valid |
@@ -45,7 +29,7 @@ describe Agents::BasecampAgent do |
||
45 | 29 |
|
46 | 30 |
describe "helpers" do |
47 | 31 |
it "should generate a correct request options hash" do |
48 |
- @checker.send(:request_options).should == {:basic_auth=>{:username=>"user", :password=>"pass"}, :headers => {"User-Agent" => "Huginn (https://github.com/cantino/huginn)"}} |
|
32 |
+ @checker.send(:request_options).should == {:headers => {"User-Agent" => "Huginn (https://github.com/cantino/huginn)", "Authorization" => 'Bearer "1234token"'}} |
|
49 | 33 |
end |
50 | 34 |
|
51 | 35 |
it "should generate the currect request url" do |
@@ -13,6 +13,7 @@ describe Agents::TwitterPublishAgent do |
||
13 | 13 |
} |
14 | 14 |
|
15 | 15 |
@checker = Agents::TwitterPublishAgent.new(:name => "HuginnBot", :options => @opts) |
16 |
+ @checker.service = services(:generic) |
|
16 | 17 |
@checker.user = users(:bob) |
17 | 18 |
@checker.save! |
18 | 19 |
|
@@ -13,6 +13,7 @@ describe Agents::TwitterStreamAgent do |
||
13 | 13 |
} |
14 | 14 |
|
15 | 15 |
@agent = Agents::TwitterStreamAgent.new(:name => "HuginnBot", :options => @opts) |
16 |
+ @agent.service = services(:generic) |
|
16 | 17 |
@agent.user = users(:bob) |
17 | 18 |
@agent.save! |
18 | 19 |
end |
@@ -15,6 +15,7 @@ describe Agents::TwitterUserAgent do |
||
15 | 15 |
} |
16 | 16 |
|
17 | 17 |
@checker = Agents::TwitterUserAgent.new(:name => "tectonic", :options => @opts) |
18 |
+ @checker.service = services(:generic) |
|
18 | 19 |
@checker.user = users(:bob) |
19 | 20 |
@checker.save! |
20 | 21 |
end |
@@ -0,0 +1,29 @@ |
||
1 |
+require 'spec_helper' |
|
2 |
+ |
|
3 |
+module Agents |
|
4 |
+ class OauthableTestAgent < Agent |
|
5 |
+ include Oauthable |
|
6 |
+ end |
|
7 |
+end |
|
8 |
+ |
|
9 |
+shared_examples_for Oauthable do |
|
10 |
+ before(:each) do |
|
11 |
+ @agent = described_class.new(:name => "somename") |
|
12 |
+ @agent.user = users(:jane) |
|
13 |
+ end |
|
14 |
+ |
|
15 |
+ it "should be oauthable" do |
|
16 |
+ @agent.oauthable?.should == true |
|
17 |
+ end |
|
18 |
+ |
|
19 |
+ describe "valid_services" do |
|
20 |
+ it "should return all available services without specifying valid_oauth_providers" do |
|
21 |
+ @agent = Agents::OauthableTestAgent.new |
|
22 |
+ @agent.valid_services(users(:bob)).collect(&:id).sort.should == [services(:generic), services(:global)].collect(&:id).sort |
|
23 |
+ end |
|
24 |
+ |
|
25 |
+ it "should filter the services based on the agent defaults" do |
|
26 |
+ @agent.valid_services(users(:bob)).to_a.should == Service.where(provider: @agent.valid_oauth_providers) |
|
27 |
+ end |
|
28 |
+ end |
|
29 |
+end |
@@ -0,0 +1,100 @@ |
||
1 |
+require 'spec_helper' |
|
2 |
+ |
|
3 |
+describe Service do |
|
4 |
+ before(:each) do |
|
5 |
+ @user = users(:bob) |
|
6 |
+ end |
|
7 |
+ |
|
8 |
+ it "should toggle the global flag" do |
|
9 |
+ @service = services(:generic) |
|
10 |
+ @service.global.should == false |
|
11 |
+ @service.toggle_availability! |
|
12 |
+ @service.global.should == true |
|
13 |
+ @service.toggle_availability! |
|
14 |
+ @service.global.should == false |
|
15 |
+ end |
|
16 |
+ |
|
17 |
+ describe "preparing for a request" do |
|
18 |
+ before(:each) do |
|
19 |
+ @service = services(:generic) |
|
20 |
+ end |
|
21 |
+ |
|
22 |
+ it "should not update the token if the token never expires" do |
|
23 |
+ @service.expires_at = nil |
|
24 |
+ @service.prepare_request.should == nil |
|
25 |
+ end |
|
26 |
+ |
|
27 |
+ it "should not update the token if the token is still valid" do |
|
28 |
+ @service.expires_at = Time.now + 1.hour |
|
29 |
+ @service.prepare_request.should == nil |
|
30 |
+ end |
|
31 |
+ |
|
32 |
+ it "should call refresh_token! if the token expired" do |
|
33 |
+ stub(@service).refresh_token! { @service } |
|
34 |
+ @service.expires_at = Time.now - 1.hour |
|
35 |
+ @service.prepare_request.should == @service |
|
36 |
+ end |
|
37 |
+ end |
|
38 |
+ |
|
39 |
+ describe "updating the access token" do |
|
40 |
+ before(:each) do |
|
41 |
+ @service = services(:generic) |
|
42 |
+ end |
|
43 |
+ |
|
44 |
+ it "should return the correct endpoint" do |
|
45 |
+ @service.provider = '37signals' |
|
46 |
+ @service.send(:endpoint).to_s.should == "https://launchpad.37signals.com/authorization/token" |
|
47 |
+ end |
|
48 |
+ |
|
49 |
+ it "should update the token" do |
|
50 |
+ stub_request(:post, "https://launchpad.37signals.com/authorization/token?client_id=TESTKEY&client_secret=TESTSECRET&refresh_token=refreshtokentest&type=refresh"). |
|
51 |
+ to_return(:status => 200, :body => '{"expires_in":1209600,"access_token": "NEWTOKEN"}', :headers => {}) |
|
52 |
+ @service.provider = '37signals' |
|
53 |
+ ENV['37SIGNALS_OAUTH_KEY'] = 'TESTKEY' |
|
54 |
+ ENV['37SIGNALS_OAUTH_SECRET'] = 'TESTSECRET' |
|
55 |
+ @service.refresh_token = 'refreshtokentest' |
|
56 |
+ @service.refresh_token! |
|
57 |
+ @service.token.should == 'NEWTOKEN' |
|
58 |
+ end |
|
59 |
+ end |
|
60 |
+ |
|
61 |
+ describe "creating services via omniauth" do |
|
62 |
+ it "should work with twitter services" do |
|
63 |
+ twitter = JSON.parse(File.read(Rails.root.join('spec/data_fixtures/services/twitter.json'))) |
|
64 |
+ expect { |
|
65 |
+ service = @user.services.initialize_or_update_via_omniauth(twitter) |
|
66 |
+ service.save! |
|
67 |
+ }.to change { @user.services.count }.by(1) |
|
68 |
+ service = @user.services.first |
|
69 |
+ service.name.should == 'johnqpublic' |
|
70 |
+ service.provider.should == 'twitter' |
|
71 |
+ service.token.should == 'a1b2c3d4...' |
|
72 |
+ service.secret.should == 'abcdef1234' |
|
73 |
+ end |
|
74 |
+ it "should work with 37signals services" do |
|
75 |
+ signals = JSON.parse(File.read(Rails.root.join('spec/data_fixtures/services/37signals.json'))) |
|
76 |
+ expect { |
|
77 |
+ service = @user.services.initialize_or_update_via_omniauth(signals) |
|
78 |
+ service.save! |
|
79 |
+ }.to change { @user.services.count }.by(1) |
|
80 |
+ service = @user.services.first |
|
81 |
+ service.provider.should == '37signals' |
|
82 |
+ service.name.should == 'Dominik Sander' |
|
83 |
+ service.token.should == 'abcde' |
|
84 |
+ service.refresh_token.should == 'fghrefresh' |
|
85 |
+ service.options[:user_id].should == 12345 |
|
86 |
+ service.expires_at = Time.at(1401554352) |
|
87 |
+ end |
|
88 |
+ it "should work with github services" do |
|
89 |
+ signals = JSON.parse(File.read(Rails.root.join('spec/data_fixtures/services/github.json'))) |
|
90 |
+ expect { |
|
91 |
+ service = @user.services.initialize_or_update_via_omniauth(signals) |
|
92 |
+ service.save! |
|
93 |
+ }.to change { @user.services.count }.by(1) |
|
94 |
+ service = @user.services.first |
|
95 |
+ service.provider.should == 'github' |
|
96 |
+ service.name.should == 'dsander' |
|
97 |
+ service.token.should == 'agithubtoken' |
|
98 |
+ end |
|
99 |
+ end |
|
100 |
+end |
@@ -21,6 +21,8 @@ WebMock.disable_net_connect! |
||
21 | 21 |
# in spec/support/ and its subdirectories. |
22 | 22 |
Dir[Rails.root.join("spec/support/**/*.rb")].each {|f| require f} |
23 | 23 |
|
24 |
+ActiveRecord::Migration.maintain_test_schema! |
|
25 |
+ |
|
24 | 26 |
RSpec.configure do |config| |
25 | 27 |
config.mock_with :rr |
26 | 28 |
|