Skip to content

Commit 22ef1ab

Browse files
committed
Allow for multiple authentication providers.
Add openid provider to its own file.
1 parent 67a556f commit 22ef1ab

File tree

4 files changed

+105
-79
lines changed

4 files changed

+105
-79
lines changed

‎barkeep_server.rb‎

Lines changed: 10 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,6 @@
66
require "methodchain"
77
require "nokogiri"
88
require "open-uri"
9-
require "openid"
10-
require "openid/extensions/ax"
11-
require "openid/store/filesystem"
129
require "pinion"
1310
require "pinion/sinatra_helpers"
1411
require "redcarpet"
@@ -25,6 +22,7 @@
2522
require "lib/git_diff_utils"
2623
require "lib/keyboard_shortcuts"
2724
require "lib/meta_repo"
25+
require "lib/openid_provider"
2826
require "lib/pretty_date"
2927
require "lib/script_environment"
3028
require "lib/stats"
@@ -39,17 +37,12 @@
3937
require "resque_jobs/deliver_review_request_emails.rb"
4038

4139
NODE_MODULES_BIN_PATH = "./node_modules/.bin"
42-
OPENID_AX_EMAIL_SCHEMA = "http://axschema.org/contact/email"
4340
UNAUTHENTICATED_ROUTES = ["/signin", "/signout", "/inspire", "/statusz", "/api/"]
4441
# NOTE(philc): Currently we let you see previews of individual commits and the code review stats without
45-
# being logged in, as a friendly UX. When we flesh out our auth model, we should intentionally make this
46-
# configurable.
42+
# being logged in, as a friendly UX. When we flesh out our authorization model, we should intentionally make
43+
# this configurable.
4744
UNAUTHENTICATED_PREVIEW_ROUTES = ["/commits/", "/stats"]
4845

49-
50-
# OPENID_PROVIDERS is a string env variable. It's a comma-separated list of OpenID providers.
51-
OPENID_PROVIDERS_ARRAY = OPENID_PROVIDERS.split(",")
52-
5346
class BarkeepServer < Sinatra::Base
5447
attr_accessor :current_user
5548

@@ -106,8 +99,6 @@ class BarkeepServer < Sinatra::Base
10699
# Session hijacking protection breaks Chrome sessions and offers little protection anyway
107100
set :protection, except: :session_hijacking
108101

109-
raise "You must have an OpenID provider defined in OPENID_PROVIDERS." if OPENID_PROVIDERS.empty?
110-
111102
configure :development do
112103
STDOUT.sync = true # Flush any output right away when running via Foreman.
113104
enable :logging
@@ -145,6 +136,11 @@ class BarkeepServer < Sinatra::Base
145136
GitDiffUtils.setup(RedisManager.redis_instance)
146137
end
147138

139+
# Register the authentication provider specified by AUTHENTICATION_PROTOCOL.
140+
AUTHENTICATION_PROVIDERS = {"openid" => OpenidProvider }
141+
authentication_provider = AUTHENTICATION_PROVIDERS[AUTHENTICATION_PROTOCOL]
142+
register authentication_provider
143+
148144
helpers do
149145
def current_page_if_url(text) request.url.include?(text) ? "currentPage" : "" end
150146

@@ -200,9 +196,7 @@ def ensure_required_params(*required_params)
200196

201197
# Save url to return to it after login completes.
202198
session[:login_started_url] = request.url
203-
redirect(OPENID_PROVIDERS_ARRAY.size == 1 ?
204-
get_openid_login_redirect(OPENID_PROVIDERS_ARRAY.first) :
205-
"/signin/select_openid_provider")
199+
redirect authentication_provider::signin_url(request, session)
206200
end
207201
end
208202

@@ -213,49 +207,7 @@ def ensure_required_params(*required_params)
213207
get "/signin" do
214208
session.clear
215209
session[:login_started_url] = request.referrer
216-
redirect(OPENID_PROVIDERS_ARRAY.size == 1 ?
217-
get_openid_login_redirect(OPENID_PROVIDERS_ARRAY.first) :
218-
"/signin/select_openid_provider")
219-
end
220-
221-
get "/signin/select_openid_provider" do
222-
erb :select_openid_provider, :locals => { :openid_providers => OPENID_PROVIDERS_ARRAY }
223-
end
224-
225-
# Users navigate to here from select_openid_provider.
226-
# - provider_id: an integer indicating which provider from OPENID_PROVIDERS_ARRAY to use for authentication.
227-
get "/signin/signin_using_openid_provider" do
228-
provider = OPENID_PROVIDERS_ARRAY[params[:provider_id].to_i]
229-
halt 400, "OpenID provider not found." unless provider
230-
redirect get_openid_login_redirect(provider)
231-
end
232-
233-
# Handle login complete from openid provider.
234-
get "/signin/complete" do
235-
@openid_consumer ||= OpenID::Consumer.new(session,
236-
OpenID::Store::Filesystem.new(File.join(File.dirname(__FILE__), "/tmp/openid")))
237-
openid_response = @openid_consumer.complete(params, request.url)
238-
case openid_response.status
239-
when OpenID::Consumer::FAILURE
240-
"Sorry, we could not authenticate you with this identifier. #{openid_response.display_identifier}"
241-
when OpenID::Consumer::SETUP_NEEDED then "Immediate request failed - Setup Needed"
242-
when OpenID::Consumer::CANCEL then "Login cancelled."
243-
when OpenID::Consumer::SUCCESS
244-
ax_resp = OpenID::AX::FetchResponse.from_success_response(openid_response)
245-
email = ax_resp["http://axschema.org/contact/email"][0]
246-
if defined?(PERMITTED_USERS) && !PERMITTED_USERS.empty?
247-
unless PERMITTED_USERS.split(",").map(&:strip).include?(email)
248-
halt 401, "Your email #{email} is not authorized to login to Barkeep."
249-
end
250-
end
251-
session[:email] = email
252-
unless User.find(:email => email)
253-
# If there are no admin users yet, make the first user to log in the first admin.
254-
permission = User.find(:permission => "admin").nil? ? "admin" : "normal"
255-
User.new(:email => email, :name => email, :permission => permission).save
256-
end
257-
redirect session[:login_started_url] || "/"
258-
end
210+
redirect authentication_provider::signin_url(request, session)
259211
end
260212

261213
get "/signout" do
@@ -561,26 +513,6 @@ def ensure_required_params(*required_params)
561513

562514
def logged_in?() current_user && !current_user.demo? end
563515

564-
# Construct redirect url to google openid.
565-
def get_openid_login_redirect(openid_provider_url)
566-
@openid_consumer ||= OpenID::Consumer.new(session,
567-
OpenID::Store::Filesystem.new(File.join(File.dirname(__FILE__), "/tmp/openid")))
568-
begin
569-
service = OpenID::OpenIDServiceEndpoint.from_op_endpoint_url(openid_provider_url)
570-
oidreq = @openid_consumer.begin_without_discovery(service, false)
571-
rescue OpenID::DiscoveryFailure => why
572-
"Could not contact #{OPENID_DISCOVERY_ENDPOINT}. #{why}"
573-
else
574-
ax_request = OpenID::AX::FetchRequest.new
575-
# Information we require from the OpenID provider.
576-
required_fields = ["http://axschema.org/contact/email"]
577-
required_fields.each { |field| ax_request.add(OpenID::AX::AttrInfo.new(field, nil, true)) }
578-
oidreq.add_extension(ax_request)
579-
host = "#{request.scheme}://#{request.host_with_port}"
580-
oidreq.redirect_url(host, "#{host}/signin/complete")
581-
end
582-
end
583-
584516
def create_comment(repo_name, sha, filename, line_number_string, text)
585517
commit = MetaRepo.instance.db_commit(repo_name, sha)
586518
raise "No such commit." unless commit
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"web":{"auth_uri":"https://accounts.google.com/o/oauth2/auth","client_secret":"sDBoi9tf0BX_jyhryXKB5HE8","token_uri":"https://accounts.google.com/o/oauth2/token","client_email":"344820527148-b0tdodatsbejl8oo952in70gaep1d87g@developer.gserviceaccount.com","redirect_uris":["http://localhost:8040/oauth2callback"],"client_x509_cert_url":"https://www.googleapis.com/robot/v1/metadata/x509/344820527148-b0tdodatsbejl8oo952in70gaep1d87g@developer.gserviceaccount.com","client_id":"344820527148-b0tdodatsbejl8oo952in70gaep1d87g.apps.googleusercontent.com","auth_provider_x509_cert_url":"https://www.googleapis.com/oauth2/v1/certs","javascript_origins":["http://localhost:8040"]}}

‎environment.rb‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,17 @@
2222
REDIS_DB = 0
2323
REDIS_DB_FOR_RESQUE = 1
2424

25+
# Choose authentication protocol, currently only 'openid' is supported.
26+
AUTHENTICATION_PROTOCOL = "openid"
27+
2528
# A comma-separate list of OpenID provider URLs for signing in your users.
2629
# If you provide more than one, users will receive a UI allowing to pick which service to use to authenticate.
2730
# Besides Google, another popular OpenID endpoint is https://me.yahoo.com
2831
OPENID_PROVIDERS = "https://www.google.com/accounts/o8/ud"
2932

3033
# This is the read-only demo mode which is used in the Barkeep demo linked from getbarkeep.com.
3134
# Most production deployments will not want to enable the demo mode, but we want it while developing.
32-
ENABLE_READONLY_DEMO_MODE = true
35+
# ENABLE_READONLY_DEMO_MODE = true
3336

3437
# If specified, this will be used as the session secret in development mode.
3538
# This prevents the session being cleared when sinatra reloads changes.

‎lib/openid_provider.rb‎

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
require "bundler/setup"
2+
require "environment"
3+
require "openid"
4+
require "openid/extensions/ax"
5+
require "openid/store/filesystem"
6+
7+
# Openid authentication provider. This Modukle is registered with sinatra if AUTHENTICATION_PROVIDERS is set
8+
# to 'openid', so authentication routes can be hosted here.
9+
module OpenidProvider
10+
OPENID_AX_EMAIL_SCHEMA = "http://axschema.org/contact/email"
11+
12+
# OPENID_PROVIDERS is a string env variable. It's a comma-separated list of OpenID providers.
13+
OPENID_PROVIDERS_ARRAY = OPENID_PROVIDERS.split(",")
14+
15+
# Construct redirect url to google openid.
16+
def self.get_openid_login_redirect(openid_provider_url, request, session)
17+
@openid_consumer ||= OpenID::Consumer.new(session,
18+
OpenID::Store::Filesystem.new(File.join(File.dirname(__FILE__), "/tmp/openid")))
19+
begin
20+
service = OpenID::OpenIDServiceEndpoint.from_op_endpoint_url(openid_provider_url)
21+
oidreq = @openid_consumer.begin_without_discovery(service, false)
22+
rescue OpenID::DiscoveryFailure => why
23+
"Could not contact #{OPENID_DISCOVERY_ENDPOINT}. #{why}"
24+
else
25+
ax_request = OpenID::AX::FetchRequest.new
26+
# Information we require from the OpenID provider.
27+
required_fields = ["http://axschema.org/contact/email"]
28+
required_fields.each { |field| ax_request.add(OpenID::AX::AttrInfo.new(field, nil, true)) }
29+
oidreq.add_extension(ax_request)
30+
host = "#{request.scheme}://#{request.host_with_port}"
31+
oidreq.redirect_url(host, "#{host}/signin/complete")
32+
end
33+
end
34+
35+
# Entry point for user signin. The user will be redirected to this url to start authentication. This
36+
# provider is expected set session[:login_started_url] with the user's email set inside session[:email] when
37+
# signin is complete, or show an error if signin fails.
38+
def self.signin_url(request, session)
39+
if OPENID_PROVIDERS_ARRAY.size == 1
40+
get_openid_login_redirect(OPENID_PROVIDERS_ARRAY.first, request, session)
41+
else
42+
"/signin/select_openid_provider"
43+
end
44+
end
45+
46+
# Routes for authentication are defined here, and will be processed by Sinatra when this module is
47+
# registered.
48+
def self.registered(app)
49+
# Users navigate to here from select_openid_provider.
50+
# - provider_id: an integer indicating which provider from OPENID_PROVIDERS_ARRAY to use for authentication.
51+
app.get "/signin/signin_using_openid_provider" do
52+
provider = OPENID_PROVIDERS_ARRAY[params[:provider_id].to_i]
53+
halt 400, "OpenID provider not found." unless provider
54+
redirect OpenidProvider.get_openid_login_redirect(provider, request, session)
55+
end
56+
57+
app.get "/signin/select_openid_provider" do
58+
erb :select_openid_provider, :locals => { :openid_providers => OPENID_PROVIDERS_ARRAY }
59+
end
60+
61+
# Handle login complete from openid provider.
62+
app.get "/signin/complete" do
63+
@openid_consumer ||= OpenID::Consumer.new(session,
64+
OpenID::Store::Filesystem.new(
65+
File.join(File.dirname(__FILE__), "/tmp/openid")))
66+
openid_response = @openid_consumer.complete(params, request.url)
67+
case openid_response.status
68+
when OpenID::Consumer::FAILURE
69+
"Sorry, we could not authenticate you with this identifier. #{openid_response.display_identifier}"
70+
when OpenID::Consumer::SETUP_NEEDED then "Immediate request failed - Setup Needed"
71+
when OpenID::Consumer::CANCEL then "Login cancelled."
72+
when OpenID::Consumer::SUCCESS
73+
ax_resp = OpenID::AX::FetchResponse.from_success_response(openid_response)
74+
email = ax_resp["http://axschema.org/contact/email"][0]
75+
if defined?(PERMITTED_USERS) && !PERMITTED_USERS.empty?
76+
unless PERMITTED_USERS.split(",").map(&:strip).include?(email)
77+
halt 401, "Your email #{email} is not authorized to login to Barkeep."
78+
end
79+
end
80+
session[:email] = email
81+
unless User.find(:email => email)
82+
# If there are no admin users yet, make the first user to log in the first admin.
83+
permission = User.find(:permission => "admin").nil? ? "admin" : "normal"
84+
User.new(:email => email, :name => email, :permission => permission).save
85+
end
86+
redirect session[:login_started_url] || "/"
87+
end
88+
end
89+
end
90+
end

0 commit comments

Comments
 (0)