Merge pull request #558 from knu/refactor-omniauth

Refactor OmniAuth integration.
This commit is contained in:
Akinori MUSHA 2014-09-30 18:41:32 +09:00
commit 7fc9fbb2d6
14 changed files with 200 additions and 73 deletions

View file

@ -5,9 +5,9 @@ module TwitterConcern
include Oauthable
validate :validate_twitter_options
valid_oauth_providers 'twitter'
valid_oauth_providers :twitter
gem_dependency_check { defined?(Twitter) && has_oauth_configuration_for?('twitter') }
gem_dependency_check { defined?(Twitter) && Devise.omniauth_providers.include?(:twitter) }
end
def validate_twitter_options
@ -20,11 +20,11 @@ module TwitterConcern
end
def twitter_consumer_key
ENV['TWITTER_OAUTH_KEY']
(config = Devise.omniauth_configs[:twitter]) && config.strategy.consumer_key
end
def twitter_consumer_secret
ENV['TWITTER_OAUTH_SECRET']
(config = Devise.omniauth_configs[:twitter]) && config.strategy.consumer_secret
end
def twitter_oauth_token

View file

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

View file

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

View file

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

View file

@ -1,6 +1,4 @@
class Service < ActiveRecord::Base
PROVIDER_TO_ENV_MAP = {'37signals' => 'THIRTY_SEVEN_SIGNALS'}
attr_accessible :provider, :name, :token, :secret, :refresh_token, :expires_at, :global, :options, :uid
serialize :options, Hash
@ -51,23 +49,19 @@ class Service < ActiveRecord::Base
URI.join(client_options['site'], client_options['token_url'])
end
def provider_to_env
PROVIDER_TO_ENV_MAP[provider].presence || provider.upcase
end
def oauth_key
ENV["#{provider_to_env}_OAUTH_KEY"]
(config = Devise.omniauth_configs[provider.to_sym]) && config.args[0]
end
def oauth_secret
ENV["#{provider_to_env}_OAUTH_SECRET"]
(config = Devise.omniauth_configs[provider.to_sym]) && config.args[1]
end
def self.provider_specific_options(omniauth)
case omniauth['provider']
when 'twitter', 'github'
case omniauth['provider'].to_sym
when :twitter, :github
{ name: omniauth['info']['nickname'] }
when '37signals'
when :'37signals'
{ user_id: omniauth['extra']['accounts'][0]['id'], name: omniauth['info']['name'] }
else
{ name: omniauth['info']['nickname'] }
@ -86,4 +80,4 @@ class Service < ActiveRecord::Base
options: options
end
end
end
end

View file

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

View file

@ -11,15 +11,9 @@
<%= link_to 'wiki', 'https://github.com/cantino/huginn/wiki/Configuring-OAuth-applications', target: :_blank %>
for guidance.
</p>
<% if has_oauth_configuration_for?('twitter') %>
<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>
<% end %>
<% if has_oauth_configuration_for?('37signals') %>
<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>
<% end -%>
<% if has_oauth_configuration_for?('github') %>
<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>
<% end -%>
<%- Devise.omniauth_providers.each { |provider| -%>
<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>
<%- } -%>
<hr>
<div class='table-responsive'>

View file

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

View file

@ -213,6 +213,23 @@ Devise.setup do |config|
# Add a new OmniAuth provider. Check the wiki for more information on setting
# up on your models and hooks.
# config.omniauth :github, 'APP_ID', 'APP_SECRET', :scope => 'user,public_repo'
if defined?(OmniAuth::Strategies::Twitter) &&
(key = ENV["TWITTER_OAUTH_KEY"]).present? &&
(secret = ENV["TWITTER_OAUTH_SECRET"]).present?
config.omniauth :twitter, key, secret, authorize_params: {force_login: 'true', use_authorize: 'true'}
end
if defined?(OmniAuth::Strategies::ThirtySevenSignals) &&
(key = ENV["THIRTY_SEVEN_SIGNALS_OAUTH_KEY"]).present? &&
(secret = ENV["THIRTY_SEVEN_SIGNALS_OAUTH_SECRET"]).present?
config.omniauth :'37signals', key, secret
end
if defined?(OmniAuth::Strategies::GitHub) &&
(key = ENV["GITHUB_OAUTH_KEY"]).present? &&
(secret = ENV["GITHUB_OAUTH_SECRET"]).present?
config.omniauth :github, key, secret
end
# ==> Warden configuration
# If you want to use other strategies, that are not supported by Devise, or
@ -236,4 +253,5 @@ Devise.setup do |config|
# When using omniauth, Devise cannot automatically set Omniauth path,
# so you need to do it manually. For the users scope, it would be:
# config.omniauth_path_prefix = "/my_engine/users/auth"
end
config.omniauth_path_prefix = "/auth"
end

View file

@ -1,35 +0,0 @@
OMNIAUTH_PROVIDERS = {}.tap { |providers|
if defined?(OmniAuth::Strategies::Twitter) &&
(key = ENV["TWITTER_OAUTH_KEY"]).present? &&
(secret = ENV["TWITTER_OAUTH_SECRET"]).present?
providers['twitter'] = {
omniauth_params: [key, secret, authorize_params: {force_login: 'true', use_authorize: 'true'}]
}
end
if defined?(OmniAuth::Strategies::ThirtySevenSignals) &&
(key = ENV["THIRTY_SEVEN_SIGNALS_OAUTH_KEY"]).present? &&
(secret = ENV["THIRTY_SEVEN_SIGNALS_OAUTH_SECRET"]).present?
providers['37signals'] = {
omniauth_params: [key, secret]
}
end
if defined?(OmniAuth::Strategies::GitHub) &&
(key = ENV["GITHUB_OAUTH_KEY"]).present? &&
(secret = ENV["GITHUB_OAUTH_SECRET"]).present?
providers['github'] = {
omniauth_params: [key, secret]
}
end
}
def has_oauth_configuration_for?(provider)
OMNIAUTH_PROVIDERS.key?(provider.to_s)
end
Rails.application.config.middleware.use OmniAuth::Builder do
OMNIAUTH_PROVIDERS.each { |name, config|
provider name, *config[:omniauth_params]
}
end

View file

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

View file

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

View file

@ -0,0 +1,146 @@
require 'spec_helper'
describe ApplicationHelper do
describe '#nav_link' do
it 'returns a nav link' do
stub(self).current_page?('/things') { false }
nav = nav_link('Things', '/things')
a = Nokogiri(nav).at('li:not(.active) > a[href="/things"]')
expect(a.text.strip).to eq('Things')
end
it 'returns a nav link with a glyphicon' do
stub(self).current_page?('/things') { false }
nav = nav_link('Things', '/things', glyphicon: 'help')
expect(nav).to be_html_safe
a = Nokogiri(nav).at('li:not(.active) > a[href="/things"]')
expect(a.at('span.glyphicon.glyphicon-help')).to be_a Nokogiri::XML::Element
expect(a.text.strip).to eq('Things')
end
it 'returns an active nav link' do
stub(self).current_page?('/things') { true }
nav = nav_link('Things', '/things')
expect(nav).to be_html_safe
a = Nokogiri(nav).at('li.active > a[href="/things"]')
expect(a).to be_a Nokogiri::XML::Element
expect(a.text.strip).to eq('Things')
end
describe 'with block' do
it 'returns a nav link with menu' do
stub(self).current_page?('/things') { false }
stub(self).current_page?('/things/stuff') { false }
nav = nav_link('Things', '/things') { nav_link('Stuff', '/things/stuff') }
expect(nav).to be_html_safe
a0 = Nokogiri(nav).at('li.dropdown.dropdown-hover:not(.active) > a[href="/things"]')
expect(a0).to be_a Nokogiri::XML::Element
expect(a0.text.strip).to eq('Things')
a1 = Nokogiri(nav).at('li.dropdown.dropdown-hover:not(.active) > li:not(.active) > a[href="/things/stuff"]')
expect(a1).to be_a Nokogiri::XML::Element
expect(a1.text.strip).to eq('Stuff')
end
it 'returns an active nav link with menu' do
stub(self).current_page?('/things') { true }
stub(self).current_page?('/things/stuff') { false }
nav = nav_link('Things', '/things') { nav_link('Stuff', '/things/stuff') }
expect(nav).to be_html_safe
a0 = Nokogiri(nav).at('li.dropdown.dropdown-hover.active > a[href="/things"]')
expect(a0).to be_a Nokogiri::XML::Element
expect(a0.text.strip).to eq('Things')
a1 = Nokogiri(nav).at('li.dropdown.dropdown-hover.active > li:not(.active) > a[href="/things/stuff"]')
expect(a1).to be_a Nokogiri::XML::Element
expect(a1.text.strip).to eq('Stuff')
end
it 'returns an active nav link with menu when on a child page' do
stub(self).current_page?('/things') { false }
stub(self).current_page?('/things/stuff') { true }
nav = nav_link('Things', '/things') { nav_link('Stuff', '/things/stuff') }
expect(nav).to be_html_safe
a0 = Nokogiri(nav).at('li.dropdown.dropdown-hover.active > a[href="/things"]')
expect(a0).to be_a Nokogiri::XML::Element
expect(a0.text.strip).to eq('Things')
a1 = Nokogiri(nav).at('li.dropdown.dropdown-hover.active > li:not(.active) > a[href="/things/stuff"]')
expect(a1).to be_a Nokogiri::XML::Element
expect(a1.text.strip).to eq('Stuff')
end
end
end
describe '#yes_no' do
it 'returns a label "Yes" if any truthy value is given' do
[true, Object.new].each { |value|
label = yes_no(value)
expect(label).to be_html_safe
expect(Nokogiri(label).text).to eq 'Yes'
}
end
it 'returns a label "No" if any falsy value is given' do
[false, nil].each { |value|
label = yes_no(value)
expect(label).to be_html_safe
expect(Nokogiri(label).text).to eq 'No'
}
end
end
describe '#working' do
before do
@agent = agents(:jane_website_agent)
end
it 'returns a label "Disabled" if a given agent is disabled' do
stub(@agent).disabled? { true }
label = working(@agent)
expect(label).to be_html_safe
expect(Nokogiri(label).text).to eq 'Disabled'
end
it 'returns a label "Missing Gems" if a given agent has dependencies missing' do
stub(@agent).dependencies_missing? { true }
label = working(@agent)
expect(label).to be_html_safe
expect(Nokogiri(label).text).to eq 'Missing Gems'
end
it 'returns a label "Yes" if a given agent is working' do
stub(@agent).working? { true }
label = working(@agent)
expect(label).to be_html_safe
expect(Nokogiri(label).text).to eq 'Yes'
end
it 'returns a label "No" if a given agent is not working' do
stub(@agent).working? { false }
label = working(@agent)
expect(label).to be_html_safe
expect(Nokogiri(label).text).to eq 'No'
end
end
describe '#icon_for_service' do
it 'returns a correct icon tag for Twitter' do
icon = icon_for_service(:twitter)
expect(icon).to be_html_safe
elem = Nokogiri(icon).at('i.fa.fa-twitter')
expect(elem).to be_a Nokogiri::XML::Element
end
it 'returns a correct icon tag for GitHub' do
icon = icon_for_service(:github)
expect(icon).to be_html_safe
elem = Nokogiri(icon).at('i.fa.fa-github')
expect(elem).to be_a Nokogiri::XML::Element
end
it 'returns a correct icon tag for other services' do
icon = icon_for_service(:'37signals')
expect(icon).to be_html_safe
elem = Nokogiri(icon).at('i.fa.fa-lock')
expect(elem).to be_a Nokogiri::XML::Element
end
end
end

View file

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