Skip to content

Commit

Permalink
Switched to OmniAuth for authentication.
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
gaelian committed Apr 16, 2015
1 parent b793d48 commit ec85aef
Show file tree
Hide file tree
Showing 21 changed files with 325 additions and 127 deletions.
4 changes: 3 additions & 1 deletion Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,9 @@ gem 'lesstile', '~> 1.1.0'
gem 'formtastic'
gem 'will_paginate', '~> 3.0.2'
gem 'exception_notification', '~> 2.5.2'
gem 'open_id_authentication'
gem 'omniauth'
gem 'omniauth-google-oauth2'
gem 'omniauth-openid'

# Bundle gems for the local environment. Make sure to
# put test-only gems in this group so their generators
Expand Down
32 changes: 29 additions & 3 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -59,15 +59,19 @@ GEM
actionmailer (>= 3.0.4)
factory_girl (4.2.0)
activesupport (>= 3.0.0)
faraday (0.9.1)
multipart-post (>= 1.2, < 3)
formtastic (2.2.1)
actionpack (>= 3.0)
gherkin (2.12.0)
multi_json (~> 1.3)
hashie (3.4.1)
hike (1.2.3)
i18n (0.6.4)
jquery-rails (3.0.4)
railties (>= 3.0, < 5.0)
thor (>= 0.14, < 2.0)
jwt (1.4.1)
launchy (2.3.0)
addressable (~> 2.3)
lesstile (1.1.0)
Expand All @@ -78,9 +82,29 @@ GEM
minitest (4.7.5)
multi_json (1.7.7)
multi_test (0.0.2)
multi_xml (0.5.5)
multipart-post (2.0.0)
nokogiri (1.5.10)
open_id_authentication (1.1.0)
rack-openid (~> 1.3)
oauth2 (1.0.0)
faraday (>= 0.8, < 0.10)
jwt (~> 1.0)
multi_json (~> 1.3)
multi_xml (~> 0.5)
rack (~> 1.2)
omniauth (1.2.2)
hashie (>= 1.2, < 4)
rack (~> 1.0)
omniauth-google-oauth2 (0.2.6)
omniauth (> 1.0)
omniauth-oauth2 (~> 1.1)
omniauth-oauth2 (1.2.0)
faraday (>= 0.8, < 0.10)
multi_json (~> 1.3)
oauth2 (~> 1.0)
omniauth (~> 1.2)
omniauth-openid (1.0.1)
omniauth (~> 1.0)
rack-openid (~> 1.3.1)
polyglot (0.3.3)
rack (1.5.2)
rack-openid (1.3.1)
Expand Down Expand Up @@ -167,7 +191,9 @@ DEPENDENCIES
jruby-openssl
lesstile (~> 1.1.0)
nokogiri (~> 1.5.0)
open_id_authentication
omniauth
omniauth-google-oauth2
omniauth-openid
rack-openid
rails (~> 4.0.0)
rspec
Expand Down
3 changes: 2 additions & 1 deletion app/assets/javascripts/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,5 @@ jQuery.ajaxSetup({
// jQuery extensions
jQuery.prototype.any = function(callback) {
return (this.filter(callback).length > 0)
}
}

72 changes: 50 additions & 22 deletions app/controllers/admin/sessions_controller.rb
Original file line number Diff line number Diff line change
@@ -1,40 +1,42 @@
class Admin::SessionsController < ApplicationController
skip_before_filter :verify_authenticity_token, :only => :create
before_filter :verify_authenticity_token_unless_openid, :only => :create
before_filter :verify_authenticity_token_unless_using_open_id, :only => :create

layout 'login'

def show
if using_open_id?
create
else
redirect_to :action => 'new'
end
redirect_to :action => 'new'
end

def new
flash.now[:error] = params[:message] if params[:message] # OmniAuth error message.
end

def create
return successful_login if allow_login_bypass? && params[:bypass_login]

if params[:openid_url].blank? && !request.env[Rack::OpenID::RESPONSE]
flash.now[:error] = "You must provide an OpenID URL"
render :action => 'new'
else
authenticate_with_open_id(params[:openid_url]) do |result, identity_url|
if result.successful?
if enki_config.author_open_ids.include?(URI.parse(identity_url))
return successful_login
else
flash.now[:error] = "You are not authorized"
end
return successful_login if allow_login_bypass? && params[:bypass_login] == '1'

if request.env['omniauth.auth'].present?
case request.env['omniauth.auth'][:provider]
when OMNIAUTH_GOOGLE_OAUTH2_STRATEGY
if enki_config.author_google_oauth2_email == request.env['omniauth.auth'][:info][:email]
save_auth_details(request.env['omniauth.auth'])
return successful_login
else
return show_not_authorized
end
when OMNIAUTH_OPEN_ID_ADMIN_STRATEGY
if enki_config.author_open_ids.include?(URI.parse(request.env['omniauth.auth'][:uid]))
save_auth_details(request.env['omniauth.auth'])
return successful_login
else
flash.now[:error] = result.message
return show_not_authorized
end
render :action => 'new'
else
raise ArgumentError, "The value returned from request.env['omniauth.auth'][:provider] is unknown."
end
end

show_not_authorized
end

def destroy
Expand All @@ -44,6 +46,21 @@ def destroy

protected

def show_not_authorized
flash.now[:error] = 'You are not authorized'
render :action => 'new'
end

def save_auth_details(auth_response)
OmniAuthDetails.create(
:provider => auth_response[:provider],
:uid => auth_response[:uid],
:info => auth_response[:info],
:credentials => auth_response[:credentials],
:extra => auth_response[:extra]
)
end

def successful_login
session[:logged_in] = true
redirect_to(admin_root_path)
Expand All @@ -53,9 +70,20 @@ def allow_login_bypass?
%w(development test).include?(Rails.env)
end

def verify_authenticity_token_unless_openid
def verify_authenticity_token_unless_using_open_id
verify_authenticity_token unless using_open_id?
end

def using_open_id?
if request.env['omniauth.auth'].present?
if request.env['omniauth.auth'][:provider] == OMNIAUTH_GOOGLE_OAUTH2_STRATEGY ||
request.env['omniauth.auth'][:provider] == OMNIAUTH_OPEN_ID_ADMIN_STRATEGY
return true
end
end

return false
end

helper_method :allow_login_bypass?
end
17 changes: 17 additions & 0 deletions app/controllers/application_controller.rb
Original file line number Diff line number Diff line change
@@ -1,10 +1,27 @@
class ApplicationController < ActionController::Base
protect_from_forgery

OMNIAUTH_GOOGLE_OAUTH2_STRATEGY = 'google_oauth2'
OMNIAUTH_OPEN_ID_ADMIN_STRATEGY = 'open_id_admin'
OMNIAUTH_OPEN_ID_COMMENT_STRATEGY = 'open_id_comment'

protected

def enki_config
@@enki_config = Enki::Config.default
end

# Used for OmniAuth routing.
def auth_path(provider, query_string_params = '')
path = "/auth/#{provider.to_s}"

if !query_string_params.blank?
return path + "?#{query_string_params}"
end

path
end

helper_method :enki_config
helper_method :auth_path
end
87 changes: 43 additions & 44 deletions app/controllers/comments_controller.rb
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
class CommentsController < ApplicationController
skip_before_filter :verify_authenticity_token, :only => :create
before_filter :verify_authenticity_token_unless_openid, :only => :create
before_filter :verify_authenticity_token_unless_using_openid, :only => :create

include UrlHelper
OPEN_ID_ERRORS = {
:missing => "Sorry, the OpenID server couldn't be found",
:canceled => "OpenID verification was canceled",
:failed => "Sorry, the OpenID verification failed" }

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

Expand All @@ -26,53 +22,44 @@ def new

# TODO: Spec OpenID with cucumber and rack-my-id
def create
@comment = Comment.new((session[:pending_comment] || comment_params || {}).
reject {|key, value| !Comment.protected_attribute?(key) })
@comment = Comment.new((session[:pending_comment] || comment_params || {}).
reject { |key, value| !Comment.protected_attribute?(key) })

@comment.post = @post

session[:pending_comment] = nil

if @comment.requires_openid_authentication?
session[:pending_comment] = comment_params
authenticate_with_open_id(@comment.author,
:optional => [:nickname, :fullname, :email]
) do |result, identity_url, registration|
if result.status == :successful
@comment.post = @post

@comment.author_url = @comment.author
@comment.author = (
registration["fullname"] ||
registration["nickname"] ||
@comment.author_url
).to_s
@comment.author_email = (
registration["email"] ||
@comment.author_url
).to_s

@comment.openid_error = ""
session[:pending_comment] = nil
else
@comment.openid_error = OPEN_ID_ERRORS[ result.status ]
end
end
else
if !@comment.requires_openid_authentication?
@comment.blank_openid_fields
end

# #authenticate_with_open_id may have already provided a response
unless response.headers[Rack::OpenID::AUTHENTICATE_HEADER]
if @comment.save
redirect_to post_path(@post)
else
render :template => 'posts/show'
save_comment_or_show_error
else
if request.env['omniauth.auth'].nil? && params[:message].blank? # Begin auth.
session[:pending_comment] = comment_params
session[:post_id] = @post.id
redirect_to auth_path(:open_id_comment, "openid_url=#{@comment.author}")
elsif !request.env['omniauth.auth'].nil? # Process success response.
@comment.author_url = request.env['omniauth.auth'][:uid]
@comment.author = request.env['omniauth.auth'][:info][:name]
@comment.author_email = request.env['omniauth.auth'][:info][:email] || ''
@comment.openid_error = ''
save_comment_or_show_error
else # Process error response.
@comment.openid_error = params[:message]
save_comment_or_show_error
end
end
end

private

def save_comment_or_show_error
if @comment.save
session[:pending_comment] = nil
session[:post_id] = nil
redirect_to post_path(@post)
else
render :template => 'posts/show'
end
end

def comment_params
params.require(:comment).permit(:author, :body)
end
Expand All @@ -83,9 +70,21 @@ def find_post
@post = Post.find_by_permalink(*[:year, :month, :day, :slug].map {|x|
params[x]
})

rescue ActiveRecord::RecordNotFound
@post = Post.find(session[:post_id])
end

def verify_authenticity_token_unless_openid
def verify_authenticity_token_unless_using_openid
verify_authenticity_token unless using_open_id?
end

def using_open_id?
if !request.env['omniauth.auth'].nil? &&
request.env['omniauth.auth'][:provider] == OMNIAUTH_OPEN_ID_COMMENT_STRATEGY
return true
end

return false
end
end
5 changes: 5 additions & 0 deletions app/models/omni_auth_details.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
class OmniAuthDetails < ActiveRecord::Base
serialize :info, Hash
serialize :credentials, Hash
serialize :extra, Hash
end
20 changes: 13 additions & 7 deletions app/views/admin/sessions/new.html.erb
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
<h1><%= link_to(enki_config[:title], '/') %></h1>
<% if flash[:error] %><p><%= flash[:error] %></p><% end %>
<%= form_tag(admin_session_path) do -%>
<h2>Stop! Who are you?</h2>
<p><%= text_field_tag 'openid_url' %></p>
<% if allow_login_bypass? -%>
<p><%= check_box_tag 'bypass_login' %> <label for="bypass_login">Bypass credentials check</label></p>
<% end -%>
<p><%= submit_tag("Login with OpenID") %></p>
<h2>Stop! Who are you?</h2>
<% if allow_login_bypass? -%>
<%= form_tag(admin_session_path) do -%>
<%= hidden_field_tag 'bypass_login', '1' -%>
<p><%= submit_tag('Bypass credentials check') %></p>
<%- end %>
<%- end %>
<%= form_tag(auth_path(:google_oauth2)) do -%>
<p><%= submit_tag('Login with Google OpenID Connect') %></p>
<% end -%>
<%= form_tag(auth_path(:open_id_admin)) do -%>
<p><%= text_field_tag 'openid_url', nil, placeholder: 'Enter your OpenID URL' %></p>
<p><%= submit_tag('Login with OpenID') %></p>
<% end -%>
10 changes: 10 additions & 0 deletions app/views/layouts/login.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,16 @@
<meta charset="utf-8">
<title><%= enki_config[:title] %> - Admin Login</title>
<%= stylesheet_link_tag 'login' %>
<%= javascript_tag do -%>
// Why is this code here? After a failed OpenID login, second and subsequent tries will cause Rails to throw an
// ActionDispatch::Cookies::CookieOverflow exception when hitting the /auth/open_id_admin path (this path is
// dynamically set by OmniAuth) due to the large amount of stuff passed in from the query string. So let's
// automagically say goodbye to the query string, no one needs it here anyway.
// to the query string.
if (window.location.href.indexOf('/auth/open_id_admin/callback?') > -1) {
window.history.pushState(null, 'Look ma! No query string!', '/auth/open_id_admin/callback');
}
<% end -%>
</head>
<body>
<div id="page">
Expand Down
1 change: 1 addition & 0 deletions config/.gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
database.yml
defensio.yml
google_oauth2.yml
Loading

0 comments on commit ec85aef

Please sign in to comment.