Commit bdcd57837a1768cd8f0d9fb2a43aee52de2df383
Exists in
master
and in
1 other branch
Merge pull request #192 from twinslash/github
add github authentication
Showing
21 changed files
with
224 additions
and
7 deletions
Show diff stats
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
... | ... | @@ -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/routes.rb
1.94 KB
... | ... | @@ -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 | ... | ... |
... | ... | @@ -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 | ... | ... |
... | ... | @@ -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 | ... | ... |