Add Discourse SSO to allow login into Discourse via Foodsoft

This commit is contained in:
Patrick Gansterer 2017-09-24 17:27:23 +02:00
parent b5e5d7d246
commit 01950b48a1
8 changed files with 110 additions and 54 deletions

View file

@ -1,50 +1,26 @@
class DiscourseController < ApplicationController
before_filter -> { require_plugin_enabled FoodsoftDiscourse }
skip_before_filter :authenticate
def initiate
discourse_url = FoodsoftConfig[:discourse_url]
protected
nonce = SecureRandom.hex()
return_sso_url = url_for(action: :callback, only_path: false)
payload = "nonce=#{nonce}&return_sso_url=#{return_sso_url}"
base64_payload = Base64.encode64 payload
sso = URI.escape base64_payload
def valid_signature?
return false if params[:sso].blank? || params[:sig].blank?
get_hmac_hex_string(params[:sso]) == params[:sig]
end
def redirect_to_with_payload(url, payload)
base64_payload = Base64.encode64 payload.to_query
sso = CGI::escape base64_payload
sig = get_hmac_hex_string base64_payload
session[:discourse_sso_nonce] = nonce
redirect_to "#{discourse_url}/session/sso_provider?sso=#{sso}&sig=#{sig}"
redirect_to "#{url}#{url.include?('?') ? '&' : '?'}sso=#{sso}&sig=#{sig}"
end
def callback
raise I18n.t('discourse.callback.invalid_signature') if get_hmac_hex_string(params[:sso]) != params[:sig]
info = Rack::Utils.parse_query(Base64.decode64(params[:sso]))
info.symbolize_keys!
raise I18n.t('discourse.callback.invalid_nonce') if info[:nonce] != session[:discourse_sso_nonce]
session[:discourse_sso_nonce] = nil
id = info[:external_id].to_i
user = User.find_or_initialize_by(id: id) do |user|
user.id = id
user.password = SecureRandom.random_bytes(25)
end
user.nick = info[:username]
user.email = info[:email]
user.first_name = info[:name]
user.last_name = ''
user.last_login = Time.now
user.save!
login_and_redirect_to_return_to user, :notice => I18n.t('discourse.callback.logged_in')
rescue => error
redirect_to login_url, :alert => error.to_s
def parse_payload
payload = Rack::Utils.parse_query Base64.decode64(params[:sso])
payload.symbolize_keys!
end
private
def get_hmac_hex_string payload
discourse_sso_secret = FoodsoftConfig[:discourse_sso_secret]
OpenSSL::HMAC.hexdigest 'sha256', discourse_sso_secret, payload

View file

@ -0,0 +1,43 @@
class DiscourseLoginController < DiscourseController
before_filter -> { require_config_disabled :discourse_sso }
skip_before_filter :authenticate
def initiate
discourse_url = FoodsoftConfig[:discourse_url]
nonce = SecureRandom.hex()
return_sso_url = url_for(action: :callback, only_path: false)
session[:discourse_sso_nonce] = nonce
redirect_to_with_payload "#{discourse_url}/session/sso_provider",
nonce: nonce,
return_sso_url: return_sso_url
end
def callback
raise I18n.t('discourse.callback.invalid_signature') unless valid_signature?
payload = parse_payload
raise I18n.t('discourse.callback.invalid_nonce') if payload[:nonce] != session[:discourse_sso_nonce]
session[:discourse_sso_nonce] = nil
id = payload[:external_id].to_i
user = User.find_or_initialize_by(id: id) do |user|
user.id = id
user.password = SecureRandom.random_bytes(25)
end
user.nick = payload[:username]
user.email = payload[:email]
user.first_name = payload[:name]
user.last_name = ''
user.last_login = Time.now
user.save!
login_and_redirect_to_return_to user, notice: I18n.t('discourse.callback.logged_in')
rescue => error
redirect_to login_url, alert: error.to_s
end
end

View file

@ -0,0 +1,25 @@
class DiscourseSsoController < DiscourseController
before_filter -> { require_config_enabled :discourse_sso }
def sso
raise I18n.t('discourse.sso.invalid_signature') unless valid_signature?
payload = parse_payload
nonce = payload[:nonce]
return_sso_url = payload[:return_sso_url] || "#{discourse_url}/session/sso_login"
raise I18n.t('discourse.sso.nonce_missing') if nonce.blank?
redirect_to_with_payload return_sso_url,
nonce: nonce,
email: current_user.email,
require_activation: true,
external_id: "#{FoodsoftConfig.scope}/#{current_user.id}",
username: current_user.nick,
name: current_user.name
rescue => error
redirect_to root_url, alert: error.to_s
end
end