mirror of
https://github.com/codeninjasllc/discourse.git
synced 2024-11-23 23:58:31 -05:00
FEATURE: implement SSO provider on Discourse so Auth can be farmed to it
FEATURE: pass return_sso_url to SSO endpoints, for easier return
This commit is contained in:
parent
12e587c9b3
commit
c10e3df012
8 changed files with 74 additions and 9 deletions
|
@ -1,9 +1,10 @@
|
|||
require_dependency 'rate_limiter'
|
||||
require_dependency 'single_sign_on'
|
||||
|
||||
class SessionController < ApplicationController
|
||||
|
||||
skip_before_filter :redirect_to_login_if_required
|
||||
skip_before_filter :check_xhr, only: ['sso', 'sso_login', 'become']
|
||||
skip_before_filter :check_xhr, only: ['sso', 'sso_login', 'become', 'sso_provider']
|
||||
|
||||
def csrf
|
||||
render json: {csrf: form_authenticity_token }
|
||||
|
@ -17,6 +18,25 @@ class SessionController < ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
def sso_provider(payload=nil)
|
||||
payload ||= request.query_string
|
||||
if SiteSetting.enable_sso_provider
|
||||
sso = SingleSignOn.parse(payload, SiteSetting.sso_secret)
|
||||
if current_user
|
||||
sso.name = current_user.name
|
||||
sso.username = current_user.username
|
||||
sso.email = current_user.email
|
||||
sso.external_id = current_user.id.to_s
|
||||
redirect_to sso.to_url(sso.return_sso_url)
|
||||
else
|
||||
session[:sso_payload] = request.query_string
|
||||
redirect_to '/login'
|
||||
end
|
||||
else
|
||||
render nothing: true, status: 404
|
||||
end
|
||||
end
|
||||
|
||||
# For use in development mode only when login options could be limited or disabled.
|
||||
# NEVER allow this to work in production.
|
||||
def become
|
||||
|
@ -83,6 +103,7 @@ class SessionController < ApplicationController
|
|||
login = params[:login].strip
|
||||
login = login[1..-1] if login[0] == "@"
|
||||
|
||||
|
||||
if user = User.find_by_username_or_email(login)
|
||||
|
||||
# If their password is correct
|
||||
|
@ -200,7 +221,12 @@ class SessionController < ApplicationController
|
|||
|
||||
def login(user)
|
||||
log_on_user(user)
|
||||
|
||||
if payload = session.delete(:sso_payload)
|
||||
sso_provider(payload)
|
||||
else
|
||||
render_serialized(user, UserSerializer)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -14,6 +14,7 @@ class DiscourseSingleSignOn < SingleSignOn
|
|||
sso = new
|
||||
sso.nonce = SecureRandom.hex
|
||||
sso.register_nonce(return_path)
|
||||
sso.return_sso_url = Discourse.base_url + "/session/sso_login"
|
||||
sso.to_url
|
||||
end
|
||||
|
||||
|
|
|
@ -787,6 +787,7 @@ en:
|
|||
block_common_passwords: "Don't allow passwords that are in the 10,000 most common passwords."
|
||||
|
||||
enable_sso: "Enable single sign on via an external site (Note: disables invites)"
|
||||
enable_sso_provider: "Implement Discourse SSO protocol at the /session/sso_provider endpoint, requires sso_secret to be set"
|
||||
sso_url: "URL of single sign on endpoint"
|
||||
sso_secret: "Secret string used to encrypt/decrypt SSO information, be sure it is 10 chars or longer"
|
||||
sso_overrides_email: "Overrides local email with external site email from SSO payload (WARNING: discrepancies can occur due to normalization of local emails)"
|
||||
|
|
|
@ -197,6 +197,7 @@ Discourse::Application.routes.draw do
|
|||
|
||||
get "session/sso" => "session#sso"
|
||||
get "session/sso_login" => "session#sso_login"
|
||||
get "session/sso_provider" => "session#sso_provider"
|
||||
get "session/current" => "session#current"
|
||||
get "session/csrf" => "session#csrf"
|
||||
get "composer-messages" => "composer_messages#index"
|
||||
|
|
|
@ -221,6 +221,7 @@ login:
|
|||
enable_sso:
|
||||
client: true
|
||||
default: false
|
||||
enable_sso_provider: false
|
||||
sso_url: ''
|
||||
sso_secret: ''
|
||||
sso_overrides_email: false
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
class SingleSignOn
|
||||
ACCESSORS = [:nonce, :name, :username, :email, :avatar_url, :avatar_force_update,
|
||||
:about_me, :external_id]
|
||||
:about_me, :external_id, :return_sso_url]
|
||||
FIXNUMS = []
|
||||
NONCE_EXPIRY_TIME = 10.minutes
|
||||
|
||||
|
|
|
@ -26,9 +26,9 @@ describe SessionController do
|
|||
@sso_url = "http://somesite.com/discourse_sso"
|
||||
@sso_secret = "shjkfdhsfkjh"
|
||||
|
||||
SiteSetting.stubs("enable_sso").returns(true)
|
||||
SiteSetting.stubs("sso_url").returns(@sso_url)
|
||||
SiteSetting.stubs("sso_secret").returns(@sso_secret)
|
||||
SiteSetting.enable_sso = true
|
||||
SiteSetting.sso_url = @sso_url
|
||||
SiteSetting.sso_secret = @sso_secret
|
||||
|
||||
# We have 2 options, either fabricate an admin or don't
|
||||
# send welcome messages
|
||||
|
@ -97,6 +97,7 @@ describe SessionController do
|
|||
it 'allows login to existing account with valid nonce' do
|
||||
sso = get_sso('/hello/world')
|
||||
sso.external_id = '997'
|
||||
sso.sso_url = "http://somewhere.over.com/sso_login"
|
||||
|
||||
user = Fabricate(:user)
|
||||
user.create_single_sign_on_record(external_id: '997', last_payload: '')
|
||||
|
@ -116,6 +117,40 @@ describe SessionController do
|
|||
response.code.should == '500'
|
||||
end
|
||||
|
||||
it 'can act as an SSO provider' do
|
||||
SiteSetting.enable_sso_provider = true
|
||||
SiteSetting.enable_sso = false
|
||||
SiteSetting.enable_local_logins = true
|
||||
SiteSetting.sso_secret = "topsecret"
|
||||
|
||||
sso = SingleSignOn.new
|
||||
sso.nonce = "mynonce"
|
||||
sso.sso_secret = SiteSetting.sso_secret
|
||||
sso.return_sso_url = "http://somewhere.over.rainbow/sso"
|
||||
|
||||
get :sso_provider, Rack::Utils.parse_query(sso.payload)
|
||||
|
||||
response.should redirect_to("/login")
|
||||
|
||||
user = Fabricate(:user, password: "frogs", active: true)
|
||||
EmailToken.update_all(confirmed: true)
|
||||
|
||||
xhr :post, :create, login: user.username, password: "frogs", format: :json
|
||||
|
||||
location = response.header["Location"]
|
||||
location.should =~ /^http:\/\/somewhere.over.rainbow\/sso/
|
||||
|
||||
payload = location.split("?")[1]
|
||||
|
||||
sso2 = SingleSignOn.parse(payload, "topsecret")
|
||||
|
||||
sso2.email.should == user.email
|
||||
sso2.name.should == user.name
|
||||
sso2.username.should == user.username
|
||||
sso2.external_id == user.id.to_s
|
||||
|
||||
end
|
||||
|
||||
describe 'local attribute override from SSO payload' do
|
||||
before do
|
||||
SiteSetting.stubs("sso_overrides_email").returns(true)
|
||||
|
|
|
@ -5,9 +5,9 @@ describe DiscourseSingleSignOn do
|
|||
@sso_url = "http://somesite.com/discourse_sso"
|
||||
@sso_secret = "shjkfdhsfkjh"
|
||||
|
||||
SiteSetting.stubs("enable_sso").returns(true)
|
||||
SiteSetting.stubs("sso_url").returns(@sso_url)
|
||||
SiteSetting.stubs("sso_secret").returns(@sso_secret)
|
||||
SiteSetting.enable_sso = true
|
||||
SiteSetting.sso_url = @sso_url
|
||||
SiteSetting.sso_secret = @sso_secret
|
||||
end
|
||||
|
||||
def make_sso
|
||||
|
|
Loading…
Reference in a new issue