Skip to content

Commit ec85aef

Browse files
committed
Switched to OmniAuth for authentication.
The open_id_authentication gem is no longer used. This commit includes a migration that removes the two tables used by the open_id_authentication gem (i.e. the open_id_authentication_nonces and open_id_authentication_associations tables). Enki now supports Google OpenID Connect (OAuth 2.0 for Login) and OpenID 2.0 by default. But further OmniAuth strategies can be added if desired. Closes #97.
1 parent b793d48 commit ec85aef

21 files changed

+325
-127
lines changed

Gemfile

+3-1
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,9 @@ gem 'lesstile', '~> 1.1.0'
4141
gem 'formtastic'
4242
gem 'will_paginate', '~> 3.0.2'
4343
gem 'exception_notification', '~> 2.5.2'
44-
gem 'open_id_authentication'
44+
gem 'omniauth'
45+
gem 'omniauth-google-oauth2'
46+
gem 'omniauth-openid'
4547

4648
# Bundle gems for the local environment. Make sure to
4749
# put test-only gems in this group so their generators

Gemfile.lock

+29-3
Original file line numberDiff line numberDiff line change
@@ -59,15 +59,19 @@ GEM
5959
actionmailer (>= 3.0.4)
6060
factory_girl (4.2.0)
6161
activesupport (>= 3.0.0)
62+
faraday (0.9.1)
63+
multipart-post (>= 1.2, < 3)
6264
formtastic (2.2.1)
6365
actionpack (>= 3.0)
6466
gherkin (2.12.0)
6567
multi_json (~> 1.3)
68+
hashie (3.4.1)
6669
hike (1.2.3)
6770
i18n (0.6.4)
6871
jquery-rails (3.0.4)
6972
railties (>= 3.0, < 5.0)
7073
thor (>= 0.14, < 2.0)
74+
jwt (1.4.1)
7175
launchy (2.3.0)
7276
addressable (~> 2.3)
7377
lesstile (1.1.0)
@@ -78,9 +82,29 @@ GEM
7882
minitest (4.7.5)
7983
multi_json (1.7.7)
8084
multi_test (0.0.2)
85+
multi_xml (0.5.5)
86+
multipart-post (2.0.0)
8187
nokogiri (1.5.10)
82-
open_id_authentication (1.1.0)
83-
rack-openid (~> 1.3)
88+
oauth2 (1.0.0)
89+
faraday (>= 0.8, < 0.10)
90+
jwt (~> 1.0)
91+
multi_json (~> 1.3)
92+
multi_xml (~> 0.5)
93+
rack (~> 1.2)
94+
omniauth (1.2.2)
95+
hashie (>= 1.2, < 4)
96+
rack (~> 1.0)
97+
omniauth-google-oauth2 (0.2.6)
98+
omniauth (> 1.0)
99+
omniauth-oauth2 (~> 1.1)
100+
omniauth-oauth2 (1.2.0)
101+
faraday (>= 0.8, < 0.10)
102+
multi_json (~> 1.3)
103+
oauth2 (~> 1.0)
104+
omniauth (~> 1.2)
105+
omniauth-openid (1.0.1)
106+
omniauth (~> 1.0)
107+
rack-openid (~> 1.3.1)
84108
polyglot (0.3.3)
85109
rack (1.5.2)
86110
rack-openid (1.3.1)
@@ -167,7 +191,9 @@ DEPENDENCIES
167191
jruby-openssl
168192
lesstile (~> 1.1.0)
169193
nokogiri (~> 1.5.0)
170-
open_id_authentication
194+
omniauth
195+
omniauth-google-oauth2
196+
omniauth-openid
171197
rack-openid
172198
rails (~> 4.0.0)
173199
rspec

app/assets/javascripts/common.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,5 @@ jQuery.ajaxSetup({
2424
// jQuery extensions
2525
jQuery.prototype.any = function(callback) {
2626
return (this.filter(callback).length > 0)
27-
}
27+
}
28+
+50-22
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,42 @@
11
class Admin::SessionsController < ApplicationController
22
skip_before_filter :verify_authenticity_token, :only => :create
3-
before_filter :verify_authenticity_token_unless_openid, :only => :create
3+
before_filter :verify_authenticity_token_unless_using_open_id, :only => :create
44

55
layout 'login'
66

77
def show
8-
if using_open_id?
9-
create
10-
else
11-
redirect_to :action => 'new'
12-
end
8+
redirect_to :action => 'new'
139
end
1410

1511
def new
12+
flash.now[:error] = params[:message] if params[:message] # OmniAuth error message.
1613
end
1714

1815
def create
19-
return successful_login if allow_login_bypass? && params[:bypass_login]
20-
21-
if params[:openid_url].blank? && !request.env[Rack::OpenID::RESPONSE]
22-
flash.now[:error] = "You must provide an OpenID URL"
23-
render :action => 'new'
24-
else
25-
authenticate_with_open_id(params[:openid_url]) do |result, identity_url|
26-
if result.successful?
27-
if enki_config.author_open_ids.include?(URI.parse(identity_url))
28-
return successful_login
29-
else
30-
flash.now[:error] = "You are not authorized"
31-
end
16+
return successful_login if allow_login_bypass? && params[:bypass_login] == '1'
17+
18+
if request.env['omniauth.auth'].present?
19+
case request.env['omniauth.auth'][:provider]
20+
when OMNIAUTH_GOOGLE_OAUTH2_STRATEGY
21+
if enki_config.author_google_oauth2_email == request.env['omniauth.auth'][:info][:email]
22+
save_auth_details(request.env['omniauth.auth'])
23+
return successful_login
24+
else
25+
return show_not_authorized
26+
end
27+
when OMNIAUTH_OPEN_ID_ADMIN_STRATEGY
28+
if enki_config.author_open_ids.include?(URI.parse(request.env['omniauth.auth'][:uid]))
29+
save_auth_details(request.env['omniauth.auth'])
30+
return successful_login
3231
else
33-
flash.now[:error] = result.message
32+
return show_not_authorized
3433
end
35-
render :action => 'new'
34+
else
35+
raise ArgumentError, "The value returned from request.env['omniauth.auth'][:provider] is unknown."
3636
end
3737
end
38+
39+
show_not_authorized
3840
end
3941

4042
def destroy
@@ -44,6 +46,21 @@ def destroy
4446

4547
protected
4648

49+
def show_not_authorized
50+
flash.now[:error] = 'You are not authorized'
51+
render :action => 'new'
52+
end
53+
54+
def save_auth_details(auth_response)
55+
OmniAuthDetails.create(
56+
:provider => auth_response[:provider],
57+
:uid => auth_response[:uid],
58+
:info => auth_response[:info],
59+
:credentials => auth_response[:credentials],
60+
:extra => auth_response[:extra]
61+
)
62+
end
63+
4764
def successful_login
4865
session[:logged_in] = true
4966
redirect_to(admin_root_path)
@@ -53,9 +70,20 @@ def allow_login_bypass?
5370
%w(development test).include?(Rails.env)
5471
end
5572

56-
def verify_authenticity_token_unless_openid
73+
def verify_authenticity_token_unless_using_open_id
5774
verify_authenticity_token unless using_open_id?
5875
end
5976

77+
def using_open_id?
78+
if request.env['omniauth.auth'].present?
79+
if request.env['omniauth.auth'][:provider] == OMNIAUTH_GOOGLE_OAUTH2_STRATEGY ||
80+
request.env['omniauth.auth'][:provider] == OMNIAUTH_OPEN_ID_ADMIN_STRATEGY
81+
return true
82+
end
83+
end
84+
85+
return false
86+
end
87+
6088
helper_method :allow_login_bypass?
6189
end
+17
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,27 @@
11
class ApplicationController < ActionController::Base
22
protect_from_forgery
33

4+
OMNIAUTH_GOOGLE_OAUTH2_STRATEGY = 'google_oauth2'
5+
OMNIAUTH_OPEN_ID_ADMIN_STRATEGY = 'open_id_admin'
6+
OMNIAUTH_OPEN_ID_COMMENT_STRATEGY = 'open_id_comment'
7+
48
protected
59

610
def enki_config
711
@@enki_config = Enki::Config.default
812
end
13+
14+
# Used for OmniAuth routing.
15+
def auth_path(provider, query_string_params = '')
16+
path = "/auth/#{provider.to_s}"
17+
18+
if !query_string_params.blank?
19+
return path + "?#{query_string_params}"
20+
end
21+
22+
path
23+
end
24+
925
helper_method :enki_config
26+
helper_method :auth_path
1027
end
+43-44
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,8 @@
11
class CommentsController < ApplicationController
22
skip_before_filter :verify_authenticity_token, :only => :create
3-
before_filter :verify_authenticity_token_unless_openid, :only => :create
3+
before_filter :verify_authenticity_token_unless_using_openid, :only => :create
44

55
include UrlHelper
6-
OPEN_ID_ERRORS = {
7-
:missing => "Sorry, the OpenID server couldn't be found",
8-
:canceled => "OpenID verification was canceled",
9-
:failed => "Sorry, the OpenID verification failed" }
106

117
before_filter :find_post, :except => [:new]
128

@@ -26,53 +22,44 @@ def new
2622

2723
# TODO: Spec OpenID with cucumber and rack-my-id
2824
def create
29-
@comment = Comment.new((session[:pending_comment] || comment_params || {}).
30-
reject {|key, value| !Comment.protected_attribute?(key) })
25+
@comment = Comment.new((session[:pending_comment] || comment_params || {}).
26+
reject { |key, value| !Comment.protected_attribute?(key) })
27+
3128
@comment.post = @post
3229

33-
session[:pending_comment] = nil
34-
35-
if @comment.requires_openid_authentication?
36-
session[:pending_comment] = comment_params
37-
authenticate_with_open_id(@comment.author,
38-
:optional => [:nickname, :fullname, :email]
39-
) do |result, identity_url, registration|
40-
if result.status == :successful
41-
@comment.post = @post
42-
43-
@comment.author_url = @comment.author
44-
@comment.author = (
45-
registration["fullname"] ||
46-
registration["nickname"] ||
47-
@comment.author_url
48-
).to_s
49-
@comment.author_email = (
50-
registration["email"] ||
51-
@comment.author_url
52-
).to_s
53-
54-
@comment.openid_error = ""
55-
session[:pending_comment] = nil
56-
else
57-
@comment.openid_error = OPEN_ID_ERRORS[ result.status ]
58-
end
59-
end
60-
else
30+
if !@comment.requires_openid_authentication?
6131
@comment.blank_openid_fields
62-
end
63-
64-
# #authenticate_with_open_id may have already provided a response
65-
unless response.headers[Rack::OpenID::AUTHENTICATE_HEADER]
66-
if @comment.save
67-
redirect_to post_path(@post)
68-
else
69-
render :template => 'posts/show'
32+
save_comment_or_show_error
33+
else
34+
if request.env['omniauth.auth'].nil? && params[:message].blank? # Begin auth.
35+
session[:pending_comment] = comment_params
36+
session[:post_id] = @post.id
37+
redirect_to auth_path(:open_id_comment, "openid_url=#{@comment.author}")
38+
elsif !request.env['omniauth.auth'].nil? # Process success response.
39+
@comment.author_url = request.env['omniauth.auth'][:uid]
40+
@comment.author = request.env['omniauth.auth'][:info][:name]
41+
@comment.author_email = request.env['omniauth.auth'][:info][:email] || ''
42+
@comment.openid_error = ''
43+
save_comment_or_show_error
44+
else # Process error response.
45+
@comment.openid_error = params[:message]
46+
save_comment_or_show_error
7047
end
7148
end
7249
end
7350

7451
private
7552

53+
def save_comment_or_show_error
54+
if @comment.save
55+
session[:pending_comment] = nil
56+
session[:post_id] = nil
57+
redirect_to post_path(@post)
58+
else
59+
render :template => 'posts/show'
60+
end
61+
end
62+
7663
def comment_params
7764
params.require(:comment).permit(:author, :body)
7865
end
@@ -83,9 +70,21 @@ def find_post
8370
@post = Post.find_by_permalink(*[:year, :month, :day, :slug].map {|x|
8471
params[x]
8572
})
73+
74+
rescue ActiveRecord::RecordNotFound
75+
@post = Post.find(session[:post_id])
8676
end
8777

88-
def verify_authenticity_token_unless_openid
78+
def verify_authenticity_token_unless_using_openid
8979
verify_authenticity_token unless using_open_id?
9080
end
81+
82+
def using_open_id?
83+
if !request.env['omniauth.auth'].nil? &&
84+
request.env['omniauth.auth'][:provider] == OMNIAUTH_OPEN_ID_COMMENT_STRATEGY
85+
return true
86+
end
87+
88+
return false
89+
end
9190
end

app/models/omni_auth_details.rb

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
class OmniAuthDetails < ActiveRecord::Base
2+
serialize :info, Hash
3+
serialize :credentials, Hash
4+
serialize :extra, Hash
5+
end

app/views/admin/sessions/new.html.erb

+13-7
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
11
<h1><%= link_to(enki_config[:title], '/') %></h1>
22
<% if flash[:error] %><p><%= flash[:error] %></p><% end %>
3-
<%= form_tag(admin_session_path) do -%>
4-
<h2>Stop! Who are you?</h2>
5-
<p><%= text_field_tag 'openid_url' %></p>
6-
<% if allow_login_bypass? -%>
7-
<p><%= check_box_tag 'bypass_login' %> <label for="bypass_login">Bypass credentials check</label></p>
8-
<% end -%>
9-
<p><%= submit_tag("Login with OpenID") %></p>
3+
<h2>Stop! Who are you?</h2>
4+
<% if allow_login_bypass? -%>
5+
<%= form_tag(admin_session_path) do -%>
6+
<%= hidden_field_tag 'bypass_login', '1' -%>
7+
<p><%= submit_tag('Bypass credentials check') %></p>
8+
<%- end %>
9+
<%- end %>
10+
<%= form_tag(auth_path(:google_oauth2)) do -%>
11+
<p><%= submit_tag('Login with Google OpenID Connect') %></p>
12+
<% end -%>
13+
<%= form_tag(auth_path(:open_id_admin)) do -%>
14+
<p><%= text_field_tag 'openid_url', nil, placeholder: 'Enter your OpenID URL' %></p>
15+
<p><%= submit_tag('Login with OpenID') %></p>
1016
<% end -%>

app/views/layouts/login.html.erb

+10
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,16 @@
44
<meta charset="utf-8">
55
<title><%= enki_config[:title] %> - Admin Login</title>
66
<%= stylesheet_link_tag 'login' %>
7+
<%= javascript_tag do -%>
8+
// Why is this code here? After a failed OpenID login, second and subsequent tries will cause Rails to throw an
9+
// ActionDispatch::Cookies::CookieOverflow exception when hitting the /auth/open_id_admin path (this path is
10+
// dynamically set by OmniAuth) due to the large amount of stuff passed in from the query string. So let's
11+
// automagically say goodbye to the query string, no one needs it here anyway.
12+
// to the query string.
13+
if (window.location.href.indexOf('/auth/open_id_admin/callback?') > -1) {
14+
window.history.pushState(null, 'Look ma! No query string!', '/auth/open_id_admin/callback');
15+
}
16+
<% end -%>
717
</head>
818
<body>
919
<div id="page">

config/.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
database.yml
22
defensio.yml
3+
google_oauth2.yml

0 commit comments

Comments
 (0)