Merge pull request #558 from knu/refactor-omniauth

Refactor OmniAuth integration.

Akinori MUSHA 10 年之前
父節點
當前提交
7fc9fbb2d6

+ 4 - 4
app/concerns/twitter_concern.rb

@@ -5,9 +5,9 @@ module TwitterConcern
5 5
     include Oauthable
6 6
 
7 7
     validate :validate_twitter_options
8
-    valid_oauth_providers 'twitter'
8
+    valid_oauth_providers :twitter
9 9
 
10
-    gem_dependency_check { defined?(Twitter) && has_oauth_configuration_for?('twitter') }
10
+    gem_dependency_check { defined?(Twitter) && Devise.omniauth_providers.include?(:twitter) }
11 11
   end
12 12
 
13 13
   def validate_twitter_options
@@ -20,11 +20,11 @@ module TwitterConcern
20 20
   end
21 21
 
22 22
   def twitter_consumer_key
23
-    ENV['TWITTER_OAUTH_KEY']
23
+    (config = Devise.omniauth_configs[:twitter]) && config.strategy.consumer_key
24 24
   end
25 25
 
26 26
   def twitter_consumer_secret
27
-    ENV['TWITTER_OAUTH_SECRET']
27
+    (config = Devise.omniauth_configs[:twitter]) && config.strategy.consumer_secret
28 28
   end
29 29
 
30 30
   def twitter_oauth_token

+ 2 - 2
app/controllers/application_controller.rb

@@ -27,7 +27,7 @@ class ApplicationController < ActionController::Base
27 27
   private
28 28
 
29 29
   def twitter_oauth_check
30
-    if ENV['TWITTER_OAUTH_KEY'].blank? || ENV['TWITTER_OAUTH_SECRET'].blank?
30
+    unless Devise.omniauth_providers.include?(:twitter)
31 31
       if @twitter_agent = current_user.agents.where("type like 'Agents::Twitter%'").first
32 32
         @twitter_oauth_key    = @twitter_agent.options['consumer_key'].presence || @twitter_agent.credential('twitter_consumer_key')
33 33
         @twitter_oauth_secret = @twitter_agent.options['consumer_secret'].presence || @twitter_agent.credential('twitter_consumer_secret')
@@ -36,7 +36,7 @@ class ApplicationController < ActionController::Base
36 36
   end
37 37
 
38 38
   def basecamp_auth_check
39
-    if ENV['THIRTY_SEVEN_SIGNALS_OAUTH_KEY'].blank? || ENV['THIRTY_SEVEN_SIGNALS_OAUTH_SECRET'].blank?
39
+    unless Devise.omniauth_providers.include?(:'37signals')
40 40
       @basecamp_agent = current_user.agents.where(type: 'Agents::BasecampAgent').first
41 41
     end
42 42
   end

+ 9 - 0
app/helpers/application_helper.rb

@@ -40,4 +40,13 @@ module ApplicationHelper
40 40
       link_to 'No', agent_path(agent, tab: (agent.recent_error_logs? ? 'logs' : 'details')), class: 'label label-danger'
41 41
     end
42 42
   end
43
+
44
+  def icon_for_service(service)
45
+    case service.to_sym
46
+    when :twitter, :github
47
+      "<i class='fa fa-#{service}'></i>".html_safe
48
+    else
49
+      "<i class='fa fa-lock'></i>".html_safe
50
+    end
51
+  end
43 52
 end

+ 1 - 1
app/models/agents/basecamp_agent.rb

@@ -3,7 +3,7 @@ module Agents
3 3
     cannot_receive_events!
4 4
 
5 5
     include Oauthable
6
-    valid_oauth_providers '37signals'
6
+    valid_oauth_providers :'37signals'
7 7
 
8 8
     description <<-MD
9 9
       The BasecampAgent checks a Basecamp project for new Events

+ 6 - 12
app/models/service.rb

@@ -1,6 +1,4 @@
1 1
 class Service < ActiveRecord::Base
2
-  PROVIDER_TO_ENV_MAP = {'37signals' => 'THIRTY_SEVEN_SIGNALS'}
3
-
4 2
   attr_accessible :provider, :name, :token, :secret, :refresh_token, :expires_at, :global, :options, :uid
5 3
 
6 4
   serialize :options, Hash
@@ -51,23 +49,19 @@ class Service < ActiveRecord::Base
51 49
     URI.join(client_options['site'], client_options['token_url'])
52 50
   end
53 51
 
54
-  def provider_to_env
55
-    PROVIDER_TO_ENV_MAP[provider].presence || provider.upcase
56
-  end
57
-
58 52
   def oauth_key
59
-    ENV["#{provider_to_env}_OAUTH_KEY"]
53
+    (config = Devise.omniauth_configs[provider.to_sym]) && config.args[0]
60 54
   end
61 55
 
62 56
   def oauth_secret
63
-    ENV["#{provider_to_env}_OAUTH_SECRET"]
57
+    (config = Devise.omniauth_configs[provider.to_sym]) && config.args[1]
64 58
   end
65 59
 
66 60
   def self.provider_specific_options(omniauth)
67
-    case omniauth['provider']
68
-      when 'twitter', 'github'
61
+    case omniauth['provider'].to_sym
62
+      when :twitter, :github
69 63
         { name: omniauth['info']['nickname'] }
70
-      when '37signals'
64
+      when :'37signals'
71 65
         { user_id: omniauth['extra']['accounts'][0]['id'], name: omniauth['info']['name'] }
72 66
       else
73 67
         { name: omniauth['info']['nickname'] }
@@ -86,4 +80,4 @@ class Service < ActiveRecord::Base
86 80
                                 options: options
87 81
     end
88 82
   end
89
-end
83
+end

+ 2 - 4
app/models/user.rb

@@ -1,10 +1,8 @@
1 1
 # Huginn is designed to be a multi-User system.  Users have many Agents (and Events created by those Agents).
2 2
 class User < ActiveRecord::Base
3
-  # Include default devise modules. Others available are:
4
-  # :token_authenticatable, :confirmable,
5
-  # :lockable, :timeoutable and :omniauthable
6 3
   devise :database_authenticatable, :registerable,
7
-         :recoverable, :rememberable, :trackable, :validatable, :lockable
4
+         :recoverable, :rememberable, :trackable, :validatable, :lockable,
5
+         :omniauthable
8 6
 
9 7
   INVITATION_CODES = [ENV['INVITATION_CODE'] || 'try-huginn']
10 8
 

+ 3 - 9
app/views/services/index.html.erb

@@ -11,15 +11,9 @@
11 11
         <%= link_to 'wiki', 'https://github.com/cantino/huginn/wiki/Configuring-OAuth-applications', target: :_blank %>
12 12
         for guidance.
13 13
       </p>
14
-      <% if has_oauth_configuration_for?('twitter') %>
15
-        <p><%= link_to "/auth/twitter", class: 'btn btn-default btn-auth btn-auth-twitter' do %><i class='fa fa-twitter'></i><span>Authenticate with Twitter</span><% end %></p>
16
-      <% end %>
17
-      <% if has_oauth_configuration_for?('37signals') %>
18
-        <p><%= link_to "/auth/37signals", class: 'btn btn-default btn-auth btn-auth-37signals' do %><i class='fa fa-lock'></i><span>Authenticate with 37Signals (Basecamp)</span><% end %></p>
19
-      <% end -%>
20
-      <% if has_oauth_configuration_for?('github') %>
21
-        <p><%= link_to "/auth/github", class: 'btn btn-default btn-auth btn-auth-github' do %><i class='fa fa-github'></i><span>Authenticate with Github</span><% end %></p>
22
-      <% end -%>
14
+      <%- Devise.omniauth_providers.each { |provider| -%>
15
+        <p><%= link_to user_omniauth_authorize_path(provider), class: "btn btn-default btn-auth btn-auth-#{provider}" do %><%= icon_for_service(provider) %><span>Authenticate with <%= t("devise.omniauth_providers.#{provider}") %></span><% end %></p>
16
+      <%- } -%>
23 17
       <hr>
24 18
 
25 19
       <div class='table-responsive'>

+ 2 - 0
config/application.rb

@@ -4,6 +4,8 @@ require 'rails/all'
4 4
 
5 5
 Bundler.require(:default, Rails.env)
6 6
 
7
+Dotenv.overload File.expand_path('../../spec/env.test', __FILE__) if Rails.env.test?
8
+
7 9
 module Huginn
8 10
   class Application < Rails::Application
9 11
     # Settings in config/environments/* take precedence over those specified here.

+ 19 - 1
config/initializers/devise.rb

@@ -213,6 +213,23 @@ Devise.setup do |config|
213 213
   # Add a new OmniAuth provider. Check the wiki for more information on setting
214 214
   # up on your models and hooks.
215 215
   # config.omniauth :github, 'APP_ID', 'APP_SECRET', :scope => 'user,public_repo'
216
+  if defined?(OmniAuth::Strategies::Twitter) &&
217
+     (key = ENV["TWITTER_OAUTH_KEY"]).present? &&
218
+     (secret = ENV["TWITTER_OAUTH_SECRET"]).present?
219
+    config.omniauth :twitter, key, secret, authorize_params: {force_login: 'true', use_authorize: 'true'}
220
+  end
221
+
222
+  if defined?(OmniAuth::Strategies::ThirtySevenSignals) &&
223
+     (key = ENV["THIRTY_SEVEN_SIGNALS_OAUTH_KEY"]).present? &&
224
+     (secret = ENV["THIRTY_SEVEN_SIGNALS_OAUTH_SECRET"]).present?
225
+    config.omniauth :'37signals', key, secret
226
+  end
227
+
228
+  if defined?(OmniAuth::Strategies::GitHub) &&
229
+     (key = ENV["GITHUB_OAUTH_KEY"]).present? &&
230
+     (secret = ENV["GITHUB_OAUTH_SECRET"]).present?
231
+    config.omniauth :github, key, secret
232
+  end
216 233
 
217 234
   # ==> Warden configuration
218 235
   # If you want to use other strategies, that are not supported by Devise, or
@@ -236,4 +253,5 @@ Devise.setup do |config|
236 253
   # When using omniauth, Devise cannot automatically set Omniauth path,
237 254
   # so you need to do it manually. For the users scope, it would be:
238 255
   # config.omniauth_path_prefix = "/my_engine/users/auth"
239
-end
256
+  config.omniauth_path_prefix = "/auth"
257
+end

+ 0 - 35
config/initializers/omniauth.rb

@@ -1,35 +0,0 @@
1
-OMNIAUTH_PROVIDERS = {}.tap { |providers|
2
-  if defined?(OmniAuth::Strategies::Twitter) &&
3
-     (key = ENV["TWITTER_OAUTH_KEY"]).present? &&
4
-     (secret = ENV["TWITTER_OAUTH_SECRET"]).present?
5
-    providers['twitter'] = {
6
-      omniauth_params: [key, secret, authorize_params: {force_login: 'true', use_authorize: 'true'}]
7
-    }
8
-  end
9
-
10
-  if defined?(OmniAuth::Strategies::ThirtySevenSignals) &&
11
-     (key = ENV["THIRTY_SEVEN_SIGNALS_OAUTH_KEY"]).present? &&
12
-     (secret = ENV["THIRTY_SEVEN_SIGNALS_OAUTH_SECRET"]).present?
13
-    providers['37signals'] = {
14
-      omniauth_params: [key, secret]
15
-    }
16
-  end
17
-
18
-  if defined?(OmniAuth::Strategies::GitHub) &&
19
-     (key = ENV["GITHUB_OAUTH_KEY"]).present? &&
20
-     (secret = ENV["GITHUB_OAUTH_SECRET"]).present?
21
-    providers['github'] = {
22
-      omniauth_params: [key, secret]
23
-    }
24
-  end
25
-}
26
-
27
-def has_oauth_configuration_for?(provider)
28
-  OMNIAUTH_PROVIDERS.key?(provider.to_s)
29
-end
30
-
31
-Rails.application.config.middleware.use OmniAuth::Builder do
32
-  OMNIAUTH_PROVIDERS.each { |name, config|
33
-    provider name, *config[:omniauth_params]
34
-  }
35
-end

+ 4 - 0
config/locales/devise.en.yml

@@ -49,6 +49,10 @@ en:
49 49
     omniauth_callbacks:
50 50
       success: 'Successfully authenticated from %{kind} account.'
51 51
       failure: 'Could not authenticate you from %{kind} because "%{reason}".'
52
+    omniauth_providers:
53
+      twitter: 'Twitter'
54
+      github: 'GitHub'
55
+      37signals: '37Signals (Basecamp)'
52 56
     mailer:
53 57
       confirmation_instructions:
54 58
         subject: 'Confirmation instructions'

+ 2 - 1
config/routes.rb

@@ -66,8 +66,9 @@ Huginn::Application.routes.draw do
66 66
   post  "/users/:user_id/webhooks/:agent_id/:secret" => "web_requests#handle_request" # legacy
67 67
   post  "/users/:user_id/update_location/:secret" => "web_requests#update_location" # legacy
68 68
 
69
+  match '/auth/:provider/callback', to: 'services#callback',
70
+        via: [:get, :post] #, constraints: { provider: Regexp.union(Devise.omniauth_providers.map(&:to_s)) }
69 71
   devise_for :users, :sign_out_via => [ :post, :delete ]
70
-  get '/auth/:provider/callback', to: 'services#callback'
71 72
 
72 73
   get "/about" => "home#about"
73 74
   root :to => "home#index"

+ 146 - 0
spec/helpers/application_helper_spec.rb

@@ -0,0 +1,146 @@
1
+require 'spec_helper'
2
+
3
+describe ApplicationHelper do
4
+  describe '#nav_link' do
5
+    it 'returns a nav link' do
6
+      stub(self).current_page?('/things') { false }
7
+      nav = nav_link('Things', '/things')
8
+      a = Nokogiri(nav).at('li:not(.active) > a[href="/things"]')
9
+      expect(a.text.strip).to eq('Things')
10
+    end
11
+
12
+    it 'returns a nav link with a glyphicon' do
13
+      stub(self).current_page?('/things') { false }
14
+      nav = nav_link('Things', '/things', glyphicon: 'help')
15
+      expect(nav).to be_html_safe
16
+      a = Nokogiri(nav).at('li:not(.active) > a[href="/things"]')
17
+      expect(a.at('span.glyphicon.glyphicon-help')).to be_a Nokogiri::XML::Element
18
+      expect(a.text.strip).to eq('Things')
19
+    end
20
+
21
+    it 'returns an active nav link' do
22
+      stub(self).current_page?('/things') { true }
23
+      nav = nav_link('Things', '/things')
24
+      expect(nav).to be_html_safe
25
+      a = Nokogiri(nav).at('li.active > a[href="/things"]')
26
+      expect(a).to be_a Nokogiri::XML::Element
27
+      expect(a.text.strip).to eq('Things')
28
+    end
29
+
30
+    describe 'with block' do
31
+      it 'returns a nav link with menu' do
32
+        stub(self).current_page?('/things') { false }
33
+        stub(self).current_page?('/things/stuff') { false }
34
+        nav = nav_link('Things', '/things') { nav_link('Stuff', '/things/stuff') }
35
+        expect(nav).to be_html_safe
36
+        a0 = Nokogiri(nav).at('li.dropdown.dropdown-hover:not(.active) > a[href="/things"]')
37
+        expect(a0).to be_a Nokogiri::XML::Element
38
+        expect(a0.text.strip).to eq('Things')
39
+        a1 = Nokogiri(nav).at('li.dropdown.dropdown-hover:not(.active) > li:not(.active) > a[href="/things/stuff"]')
40
+        expect(a1).to be_a Nokogiri::XML::Element
41
+        expect(a1.text.strip).to eq('Stuff')
42
+      end
43
+
44
+      it 'returns an active nav link with menu' do
45
+        stub(self).current_page?('/things') { true }
46
+        stub(self).current_page?('/things/stuff') { false }
47
+        nav = nav_link('Things', '/things') { nav_link('Stuff', '/things/stuff') }
48
+        expect(nav).to be_html_safe
49
+        a0 = Nokogiri(nav).at('li.dropdown.dropdown-hover.active > a[href="/things"]')
50
+        expect(a0).to be_a Nokogiri::XML::Element
51
+        expect(a0.text.strip).to eq('Things')
52
+        a1 = Nokogiri(nav).at('li.dropdown.dropdown-hover.active > li:not(.active) > a[href="/things/stuff"]')
53
+        expect(a1).to be_a Nokogiri::XML::Element
54
+        expect(a1.text.strip).to eq('Stuff')
55
+      end
56
+
57
+      it 'returns an active nav link with menu when on a child page' do
58
+        stub(self).current_page?('/things') { false }
59
+        stub(self).current_page?('/things/stuff') { true }
60
+        nav = nav_link('Things', '/things') { nav_link('Stuff', '/things/stuff') }
61
+        expect(nav).to be_html_safe
62
+        a0 = Nokogiri(nav).at('li.dropdown.dropdown-hover.active > a[href="/things"]')
63
+        expect(a0).to be_a Nokogiri::XML::Element
64
+        expect(a0.text.strip).to eq('Things')
65
+        a1 = Nokogiri(nav).at('li.dropdown.dropdown-hover.active > li:not(.active) > a[href="/things/stuff"]')
66
+        expect(a1).to be_a Nokogiri::XML::Element
67
+        expect(a1.text.strip).to eq('Stuff')
68
+      end
69
+    end
70
+  end
71
+
72
+  describe '#yes_no' do
73
+    it 'returns a label "Yes" if any truthy value is given' do
74
+      [true, Object.new].each { |value|
75
+        label = yes_no(value)
76
+        expect(label).to be_html_safe
77
+        expect(Nokogiri(label).text).to eq 'Yes'
78
+      }
79
+    end
80
+
81
+    it 'returns a label "No" if any falsy value is given' do
82
+      [false, nil].each { |value|
83
+        label = yes_no(value)
84
+        expect(label).to be_html_safe
85
+        expect(Nokogiri(label).text).to eq 'No'
86
+      }
87
+    end
88
+  end
89
+
90
+  describe '#working' do
91
+    before do
92
+      @agent = agents(:jane_website_agent)
93
+    end
94
+
95
+    it 'returns a label "Disabled" if a given agent is disabled' do
96
+      stub(@agent).disabled? { true }
97
+      label = working(@agent)
98
+      expect(label).to be_html_safe
99
+      expect(Nokogiri(label).text).to eq 'Disabled'
100
+    end
101
+
102
+    it 'returns a label "Missing Gems" if a given agent has dependencies missing' do
103
+      stub(@agent).dependencies_missing? { true }
104
+      label = working(@agent)
105
+      expect(label).to be_html_safe
106
+      expect(Nokogiri(label).text).to eq 'Missing Gems'
107
+    end
108
+
109
+    it 'returns a label "Yes" if a given agent is working' do
110
+      stub(@agent).working? { true }
111
+      label = working(@agent)
112
+      expect(label).to be_html_safe
113
+      expect(Nokogiri(label).text).to eq 'Yes'
114
+    end
115
+
116
+    it 'returns a label "No" if a given agent is not working' do
117
+      stub(@agent).working? { false }
118
+      label = working(@agent)
119
+      expect(label).to be_html_safe
120
+      expect(Nokogiri(label).text).to eq 'No'
121
+    end
122
+  end
123
+
124
+  describe '#icon_for_service' do
125
+    it 'returns a correct icon tag for Twitter' do
126
+      icon = icon_for_service(:twitter)
127
+      expect(icon).to be_html_safe
128
+      elem = Nokogiri(icon).at('i.fa.fa-twitter')
129
+      expect(elem).to be_a Nokogiri::XML::Element
130
+    end
131
+
132
+    it 'returns a correct icon tag for GitHub' do
133
+      icon = icon_for_service(:github)
134
+      expect(icon).to be_html_safe
135
+      elem = Nokogiri(icon).at('i.fa.fa-github')
136
+      expect(elem).to be_a Nokogiri::XML::Element
137
+    end
138
+
139
+    it 'returns a correct icon tag for other services' do
140
+      icon = icon_for_service(:'37signals')
141
+      expect(icon).to be_html_safe
142
+      elem = Nokogiri(icon).at('i.fa.fa-lock')
143
+      expect(elem).to be_a Nokogiri::XML::Element
144
+    end
145
+  end
146
+end

+ 0 - 4
spec/spec_helper.rb

@@ -8,10 +8,6 @@ else
8 8
   Coveralls.wear!('rails')
9 9
 end
10 10
 
11
-# Required ENV variables that are normally set in .env are setup here for the test environment.
12
-require 'dotenv'
13
-Dotenv.overload File.join(File.dirname(__FILE__), "env.test")
14
-
15 11
 require File.expand_path("../../config/environment", __FILE__)
16 12
 require 'rspec/rails'
17 13
 require 'rspec/autorun'