Commit bdcd57837a1768cd8f0d9fb2a43aee52de2df383

Authored by Nathan Broadbent
2 parents c5387bf1 3fe07eef
Exists in master and in 1 other branch production

Merge pull request #192 from twinslash/github

add github authentication
Gemfile
... ... @@ -7,14 +7,21 @@ gem 'mongoid', '~> 2.4.10'
7 7  
8 8 gem 'haml'
9 9 gem 'htmlentities', "~> 4.3.0"
  10 +
10 11 gem 'devise', '~> 1.5.3'
  12 +
  13 +gem 'omniauth-github'
  14 +gem 'oa-core'
  15 +
11 16 gem 'lighthouse-api'
12 17 gem 'oruen_redmine_client', :require => 'redmine_client'
13 18 gem 'mongoid_rails_migrations'
14 19 gem 'useragent', '~> 0.3.1'
15 20 gem 'pivotal-tracker'
16 21 gem 'ruby-fogbugz', :require => 'fogbugz'
  22 +
17 23 gem 'octokit', '~> 1.0.0'
  24 +
18 25 gem 'inherited_resources'
19 26 gem 'SystemTimer', :platform => :ruby_18
20 27 gem 'hoptoad_notifier', "~> 2.4"
... ... @@ -44,6 +51,8 @@ group :development, :test do
44 51 end
45 52  
46 53 group :test do
  54 + gem 'capybara'
  55 + gem 'launchy'
47 56 gem 'rspec', '~> 2.6'
48 57 gem 'database_cleaner', '~> 0.6.0'
49 58 gem 'email_spec'
... ...
Gemfile.lock
... ... @@ -39,6 +39,15 @@ GEM
39 39 bson (1.3.1)
40 40 bson_ext (1.3.1)
41 41 builder (3.0.0)
  42 + capybara (1.1.2)
  43 + mime-types (>= 1.16)
  44 + nokogiri (>= 1.3.3)
  45 + rack (>= 1.0.0)
  46 + rack-test (>= 0.5.4)
  47 + selenium-webdriver (~> 2.0)
  48 + xpath (~> 0.1.4)
  49 + childprocess (0.3.2)
  50 + ffi (~> 1.0.6)
42 51 columnize (0.3.6)
43 52 crack (0.3.1)
44 53 css_parser (1.2.6)
... ... @@ -70,8 +79,9 @@ GEM
70 79 addressable (~> 2.2)
71 80 multipart-post (~> 1.1)
72 81 rack (~> 1.1)
73   - faraday_middleware (0.8.6)
  82 + faraday_middleware (0.8.7)
74 83 faraday (>= 0.7.4, < 0.9)
  84 + ffi (1.0.11)
75 85 haml (3.1.5)
76 86 happymapper (0.4.0)
77 87 libxml-ruby (~> 2.0)
... ... @@ -93,7 +103,11 @@ GEM
93 103 activesupport (>= 3.0.0)
94 104 railties (>= 3.0.0)
95 105 kgio (2.7.4)
  106 + launchy (2.1.0)
  107 + addressable (~> 2.2.6)
96 108 libv8 (3.3.10.4)
  109 + libwebsocket (0.1.3)
  110 + addressable
97 111 libxml-ruby (2.3.2)
98 112 lighthouse-api (2.0)
99 113 activeresource (>= 3.0.0)
... ... @@ -119,13 +133,26 @@ GEM
119 133 multi_json (1.1.0)
120 134 multipart-post (1.1.5)
121 135 nokogiri (1.5.0)
  136 + oa-core (0.3.2)
  137 + oauth2 (0.5.2)
  138 + faraday (~> 0.7)
  139 + multi_json (~> 1.0)
122 140 octokit (1.0.0)
123 141 addressable (~> 2.2)
124 142 faraday (~> 0.7)
125 143 faraday_middleware (~> 0.8)
126 144 hashie (~> 1.2)
127 145 multi_json (~> 1.0)
128   - orm_adapter (0.0.5)
  146 + omniauth (1.0.3)
  147 + hashie (~> 1.2)
  148 + rack
  149 + omniauth-github (1.0.1)
  150 + omniauth (~> 1.0)
  151 + omniauth-oauth2 (~> 1.0)
  152 + omniauth-oauth2 (1.0.0)
  153 + oauth2 (~> 0.5.0)
  154 + omniauth (~> 1.0)
  155 + orm_adapter (0.0.7)
129 156 oruen_redmine_client (0.0.1)
130 157 activeresource (>= 2.3.0)
131 158 pivotal-tracker (0.5.4)
... ... @@ -196,6 +223,13 @@ GEM
196 223 linecache (>= 0.3)
197 224 ruby-fogbugz (0.1.1)
198 225 crack
  226 + rubyzip (0.9.8)
  227 + selenium-webdriver (2.21.2)
  228 + childprocess (>= 0.2.5)
  229 + ffi (~> 1.0)
  230 + libwebsocket (~> 0.1.3)
  231 + multi_json (~> 1.0)
  232 + rubyzip
199 233 sprockets (2.1.3)
200 234 hike (~> 1.2)
201 235 rack (~> 1.0)
... ... @@ -225,6 +259,8 @@ GEM
225 259 webmock (1.8.6)
226 260 addressable (>= 2.2.7)
227 261 crack (>= 0.1.7)
  262 + xpath (0.1.4)
  263 + nokogiri (~> 1.3)
228 264 yajl-ruby (1.1.0)
229 265  
230 266 PLATFORMS
... ... @@ -235,6 +271,7 @@ DEPENDENCIES
235 271 actionmailer_inline_css (~> 1.3.0)
236 272 bson (= 1.3.1)
237 273 bson_ext (= 1.3.1)
  274 + capybara
238 275 database_cleaner (~> 0.6.0)
239 276 debugger
240 277 devise (~> 1.5.3)
... ... @@ -246,12 +283,15 @@ DEPENDENCIES
246 283 htmlentities (~> 4.3.0)
247 284 inherited_resources
248 285 kaminari
  286 + launchy
249 287 lighthouse-api
250 288 mongo (= 1.3.1)
251 289 mongoid (~> 2.4.10)
252 290 mongoid_rails_migrations
253 291 nokogiri
  292 + oa-core
254 293 octokit (~> 1.0.0)
  294 + omniauth-github
255 295 oruen_redmine_client
256 296 pivotal-tracker
257 297 rack-ssl-enforcer
... ...
app/assets/javascripts/errbit.js
... ... @@ -12,6 +12,8 @@ $(function() {
12 12  
13 13 toggleProblemsCheckboxes();
14 14  
  15 + bindRequiredPasswordMarks();
  16 +
15 17 $('#watcher_name').live("click", function() {
16 18 $(this).closest('form').find('.show').removeClass('show');
17 19 $('#app_watchers_attributes_0_user_id').addClass('show');
... ... @@ -97,6 +99,24 @@ $(function() {
97 99 });
98 100 }
99 101  
  102 + function bindRequiredPasswordMarks() {
  103 + $('#user_github_login').keyup(function(event) {
  104 + toggleRequiredPasswordMarks(this)
  105 + });
  106 + }
  107 +
  108 + function toggleRequiredPasswordMarks(input) {
  109 + if($(input).val() == "") {
  110 + $('#user_password').parent().attr('class', 'required')
  111 + $('#user_password_confirmation').parent().attr('class', 'required')
  112 + } else {
  113 + $('#user_password').parent().attr('class', '')
  114 + $('#user_password_confirmation').parent().attr('class', '')
  115 + }
  116 + }
  117 +
  118 + toggleRequiredPasswordMarks();
  119 +
100 120 function hide_external_backtrace() {
101 121 $('tr.toggle_external_backtrace').hide();
102 122 $('td.backtrace_separator').show();
... ... @@ -109,5 +129,6 @@ $(function() {
109 129 $('td.backtrace_separator span').live('click', show_external_backtrace);
110 130 // Hide external backtrace on page load
111 131 hide_external_backtrace();
  132 +
112 133 init();
113 134 });
... ...
app/assets/stylesheets/errbit.css
... ... @@ -851,3 +851,5 @@ table.errs tr td.message .inline_comment em.commenter {
851 851  
852 852 .current.asc:after { content: ' ↑'; }
853 853 .current.desc:after { content: ' ↓'; }
  854 +
  855 +span.github a { background: url(../images/github.png) no-repeat 5px 5px; }
... ...
app/controllers/users/omniauth_callbacks_controller.rb 0 → 100644
... ... @@ -0,0 +1,12 @@
  1 +class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
  2 + def github
  3 + @user = User.find_for_github_oauth(request.env["omniauth.auth"])
  4 +
  5 + if @user
  6 + flash[:notice] = I18n.t "devise.omniauth_callbacks.success", :kind => "Github"
  7 + sign_in_and_redirect @user, :event => :authentication
  8 + else
  9 + redirect_to new_user_session_path
  10 + end
  11 + end
  12 +end
... ...
app/models/user.rb
... ... @@ -6,6 +6,7 @@ class User
6 6 devise *Errbit::Config.devise_modules
7 7  
8 8 field :email
  9 + field :github_login
9 10 field :name
10 11 field :admin, :type => Boolean, :default => false
11 12 field :per_page, :type => Fixnum, :default => PER_PAGE
... ... @@ -15,6 +16,7 @@ class User
15 16 before_save :ensure_authentication_token
16 17  
17 18 validates_presence_of :name
  19 + validates_uniqueness_of :github_login, :allow_nil => true
18 20  
19 21 attr_protected :admin
20 22  
... ... @@ -37,6 +39,16 @@ class User
37 39 apps.all.include?(app)
38 40 end
39 41  
  42 + def self.find_for_github_oauth(omniauth_env)
  43 + data = omniauth_env.extra.raw_info
  44 +
  45 + User.where(:github_login => data.login).first
  46 + end
  47 +
  48 + def password_required?
  49 + github_login.present? ? false : super
  50 + end
  51 +
40 52 protected
41 53  
42 54 def destroy_watchers
... ...
app/views/devise/sessions/new.html.haml
1 1 - content_for :title, 'Sign in'
2 2 - auth_key = Devise.authentication_keys.first
3 3  
  4 +- if Errbit::Config.github_authentication
  5 + - content_for :action_bar do
  6 + %div.action-bar
  7 + %span.github= link_to "Sign in with GitHub", user_omniauth_authorize_path(:github), :style => 'background: url(/images/github.png) no-repeat 5px 5px'
  8 +
4 9 = form_for(resource, :as => resource_name, :url => session_path(resource_name)) do |f|
5 10 .required
6 11 = f.label auth_key
... ...
app/views/users/_fields.html.haml
... ... @@ -13,6 +13,10 @@
13 13 = f.label :email
14 14 = f.text_field :email
15 15  
  16 +- if Errbit::Config.github_authentication
  17 + = f.label :github_login
  18 + = f.text_field :github_login
  19 +
16 20 .required
17 21 = f.label 'Entries per page'
18 22 = f.select :per_page, [10, 20, 30, 50, 75, 100]
... ...
app/views/users/show.html.haml
... ... @@ -12,6 +12,10 @@
12 12 %tr
13 13 %th Username
14 14 %td.main= @user.username
  15 + - if Errbit::Config.github_authentication && @user.github_login.present?
  16 + %tr
  17 + %th GitHub
  18 + %td.main= link_to @user.github_login, "https://github.com/#{@user.github_login}"
15 19 %tr
16 20 %th Token
17 21 %td= @user.authentication_token
... ...
config/config.example.yml
... ... @@ -49,6 +49,13 @@ deployment:
49 49 user: deploy
50 50 deploy_to: /var/www/apps/errbit
51 51  
  52 +# Github OAuth configuration
  53 +# If you want to use authtiaction through Github you should provide Client ID and Secret keys.
  54 +# You can register a new application here https://github.com/settings/applications
  55 +github_authentication: false
  56 +github_client_id: 'GITHUB_CLIENT_ID'
  57 +github_secret: 'GITHUB_SECRET'
  58 +
52 59 # Configure SMTP settings. If you are running Errbit on Heroku,
53 60 # sendgrid will be configured by default.
54 61 # ------------------------------------------------------------------------
... ...
config/initializers/_load_config.rb
... ... @@ -14,6 +14,10 @@ unless defined?(Errbit::Config)
14 14 Errbit::Config.user_has_username = ENV['ERRBIT_USER_HAS_USERNAME']
15 15 Errbit::Config.allow_comments_with_issue_tracker = ENV['ERRBIT_ALLOW_COMMENTS_WITH_ISSUE_TRACKER']
16 16  
  17 + Errbit::Config.github_authentication = ENV['GITHUB_AUTHENTICATION']
  18 + Errbit::Config.github_client_id = ENV['GITHUB_CLIENT_ID']
  19 + Errbit::Config.github_secret = ENV['GITHUB_SECRET']
  20 +
17 21 Errbit::Config.smtp_settings = {
18 22 :address => "smtp.sendgrid.net",
19 23 :port => "25",
... ... @@ -42,7 +46,7 @@ unless defined?(Errbit::Config)
42 46 # Set default devise modules
43 47 Errbit::Config.devise_modules = [:database_authenticatable,
44 48 :recoverable, :rememberable, :trackable,
45   - :validatable, :token_authenticatable]
  49 + :validatable, :token_authenticatable, :omniauthable]
46 50 end
47 51  
48 52 # Set default settings from config.example.yml if key is missing from config.yml
... ...
config/initializers/devise.rb
... ... @@ -118,6 +118,10 @@ Devise.setup do |config|
118 118 # In case of sign_out_all_scopes set to true any logout action will sign out all active scopes.
119 119 # config.sign_out_all_scopes = false
120 120  
  121 + if Errbit::Config.github_authentication || Rails.env.test?
  122 + config.omniauth :github, Errbit::Config.github_client_id, Errbit::Config.github_secret
  123 + end
  124 +
121 125 # ==> Navigation configuration
122 126 # Lists the formats that should be treated as navigational. Formats like
123 127 # :html, should redirect to the sign in page when the user does not have
... ...
config/initializers/omniauth.rb 0 → 100644
config/routes.rb
1 1 Errbit::Application.routes.draw do
2 2  
3   - devise_for :users
  3 + devise_for :users, :controllers => { :omniauth_callbacks => "users/omniauth_callbacks" }
4 4  
5 5 # Hoptoad Notifier Routes
6 6 match '/notifier_api/v2/notices' => 'notices#create'
... ...
public/images/github.png 0 → 100644

1.94 KB

spec/acceptance/acceptance_helper.rb 0 → 100644
... ... @@ -0,0 +1,18 @@
  1 +require 'spec_helper'
  2 +require 'capybara/rspec'
  3 +
  4 +OmniAuth.config.test_mode = true
  5 +
  6 +RSpec.configure do |config|
  7 + config.before(:each) do
  8 + OmniAuth.config.mock_auth[:github] = Hashie::Mash.new(
  9 + 'provider' => 'github',
  10 + 'uid' => '1763',
  11 + 'extra' => {
  12 + 'raw_info' => {
  13 + 'login' => 'nashby'
  14 + }
  15 + }
  16 + )
  17 + end
  18 +end
... ...
spec/acceptance/login_spec.rb 0 → 100644
... ... @@ -0,0 +1,14 @@
  1 +require 'acceptance/acceptance_helper'
  2 +
  3 +feature 'Log in' do
  4 + background do
  5 + Errbit::Config.stub(:github_authentication) { true }
  6 + Fabricate(:user, :github_login => 'nashby')
  7 + end
  8 +
  9 + scenario 'log in via GitHub' do
  10 + visit '/'
  11 + click_link 'Sign in with GitHub'
  12 + page.should have_content 'Successfully authorized from Github account'
  13 + end
  14 +end
... ...
spec/controllers/users_controller_spec.rb
... ... @@ -78,6 +78,11 @@ describe UsersController do
78 78 put :update, :id => @user.to_param, :user => {:time_zone => "Warsaw"}
79 79 @user.reload.time_zone.should == "Warsaw"
80 80 end
  81 +
  82 + it "should be able to set github_login option" do
  83 + put :update, :id => @user.to_param, :user => {:github_login => "awesome_name"}
  84 + @user.reload.github_login.should == "awesome_name"
  85 + end
81 86 end
82 87  
83 88 context "when the update is unsuccessful" do
... ... @@ -226,7 +231,6 @@ describe UsersController do
226 231 request.flash[:success].should include('no longer part of your team')
227 232 end
228 233 end
229   -
230 234 end
231   -end
232 235  
  236 +end
... ...
spec/models/user_spec.rb
... ... @@ -8,6 +8,36 @@ describe User do
8 8 user.should_not be_valid
9 9 user.errors[:name].should include("can't be blank")
10 10 end
  11 +
  12 + it 'requires password without github login' do
  13 + user = Fabricate.build(:user, :password => nil)
  14 + user.should_not be_valid
  15 + user.errors[:password].should include("can't be blank")
  16 + end
  17 +
  18 + it "doesn't require password with github login" do
  19 + user = Fabricate.build(:user, :password => nil, :github_login => 'nashby')
  20 + user.should be_valid
  21 + end
  22 +
  23 + it 'requires uniq github login' do
  24 + user1 = Fabricate(:user, :github_login => 'nashby')
  25 + user1.should be_valid
  26 +
  27 + user2 = Fabricate.build(:user, :github_login => 'nashby')
  28 + user2.save
  29 + user2.should_not be_valid
  30 + user2.errors[:github_login].should include("is already taken")
  31 + end
  32 + end
  33 +
  34 + describe '.find_for_github_oauth' do
  35 + let(:auth_hash) { Hashie::Mash.new(:provider => 'github', :extra => { :raw_info => { :login => 'nashby' } }) }
  36 +
  37 + it 'finds user by github login' do
  38 + user = Fabricate(:user, :github_login => 'nashby')
  39 + User.find_for_github_oauth(auth_hash).should == user
  40 + end
11 41 end
12 42  
13 43 context 'Watchers' do
... ...
spec/spec_helper.rb
... ... @@ -17,7 +17,6 @@ end
17 17 RSpec.configure do |config|
18 18 config.mock_with :rspec
19 19 config.include Devise::TestHelpers, :type => :controller
20   -
21 20 config.filter_run :focused => true
22 21 config.run_all_when_everything_filtered = true
23 22 config.alias_example_to :fit, :focused => true
... ...
spec/views/users/show.html.haml_spec.rb 0 → 100644
... ... @@ -0,0 +1,28 @@
  1 +require 'spec_helper'
  2 +
  3 +describe 'users/show.html.haml' do
  4 + let(:user) do
  5 + user = stub_model(User, :created_at => Time.now)
  6 + end
  7 +
  8 + context 'with github auth' do
  9 + before do
  10 + Errbit::Config.stub(:github_authentication) { true }
  11 + end
  12 +
  13 + it 'shows github login' do
  14 + user.github_login = 'test_user'
  15 + assign :user, user
  16 + render
  17 + rendered.should match(/GitHub/)
  18 + rendered.should match(/test_user/)
  19 + end
  20 +
  21 + it 'does not show github if blank' do
  22 + user.github_login = ' '
  23 + assign :user, user
  24 + render
  25 + rendered.should_not match(/GitHub/)
  26 + end
  27 + end
  28 +end
... ...