2014-02-24 22:30:49 -05:00
class SingleSignOn
2015-05-19 12:16:02 -04:00
ACCESSORS = [ :nonce , :name , :username , :email , :avatar_url , :avatar_force_update , :require_activation ,
2017-01-31 19:42:27 -05:00
:bio , :external_id , :return_sso_url , :admin , :moderator , :suppress_welcome_message , :title ,
2018-06-19 20:30:23 -04:00
:add_groups , :remove_groups , :groups , :profile_background_url , :card_background_url , :website ]
2014-02-24 22:30:49 -05:00
FIXNUMS = [ ]
2015-05-21 09:41:36 -04:00
BOOLS = [ :avatar_force_update , :admin , :moderator , :require_activation , :suppress_welcome_message ]
2014-02-24 22:30:49 -05:00
NONCE_EXPIRY_TIME = 10 . minutes
attr_accessor ( * ACCESSORS )
2017-11-02 07:33:35 -04:00
attr_writer :sso_secret , :sso_url
2014-02-24 22:30:49 -05:00
def self . sso_secret
raise RuntimeError , " sso_secret not implemented on class, be sure to set it on instance "
end
def self . sso_url
raise RuntimeError , " sso_url not implemented on class, be sure to set it on instance "
end
def self . parse ( payload , sso_secret = nil )
sso = new
sso . sso_secret = sso_secret if sso_secret
parsed = Rack :: Utils . parse_query ( payload )
if sso . sign ( parsed [ " sso " ] ) != parsed [ " sig " ]
2014-12-29 17:23:21 -05:00
diags = " \n \n sso: #{ parsed [ " sso " ] } \n \n sig: #{ parsed [ " sig " ] } \n \n expected sig: #{ sso . sign ( parsed [ " sso " ] ) } "
2014-12-29 17:28:44 -05:00
if parsed [ " sso " ] =~ / [^a-zA-Z0-9= \ r \ n \/ +] /m
2014-12-29 18:45:33 -05:00
raise RuntimeError , " The SSO field should be Base64 encoded, using only A-Z, a-z, 0-9, +, /, and = characters. Your input contains characters we don't understand as Base64, see http://en.wikipedia.org/wiki/Base64 #{ diags } "
2014-12-29 17:23:21 -05:00
else
raise RuntimeError , " Bad signature for payload #{ diags } "
end
2014-02-24 22:30:49 -05:00
end
decoded = Base64 . decode64 ( parsed [ " sso " ] )
decoded_hash = Rack :: Utils . parse_query ( decoded )
ACCESSORS . each do | k |
val = decoded_hash [ k . to_s ]
val = val . to_i if FIXNUMS . include? k
2014-11-26 20:39:00 -05:00
if BOOLS . include? k
val = [ " true " , " false " ] . include? ( val ) ? val == " true " : nil
end
2014-02-24 22:30:49 -05:00
sso . send ( " #{ k } = " , val )
end
2014-04-21 23:52:13 -04:00
2017-07-27 21:20:09 -04:00
decoded_hash . each do | k , v |
2017-03-27 10:21:38 -04:00
if field = k [ / ^custom \ .(.+)$ / , 1 ]
2014-04-21 23:52:13 -04:00
sso . custom_fields [ field ] = v
end
end
2014-02-24 22:30:49 -05:00
sso
end
2016-04-07 21:20:01 -04:00
def diagnostics
2017-03-27 10:21:38 -04:00
SingleSignOn :: ACCESSORS . map { | a | " #{ a } : #{ send ( a ) } " } . join ( " \n " )
2016-04-07 21:20:01 -04:00
end
2014-04-21 23:52:13 -04:00
def sso_secret
@sso_secret || self . class . sso_secret
end
def sso_url
@sso_url || self . class . sso_url
end
def custom_fields
@custom_fields || = { }
end
2014-02-24 22:30:49 -05:00
def sign ( payload )
2014-02-25 17:44:41 -05:00
OpenSSL :: HMAC . hexdigest ( " sha256 " , sso_secret , payload )
2014-02-24 22:30:49 -05:00
end
2017-07-27 21:20:09 -04:00
def to_url ( base_url = nil )
2014-03-19 17:14:09 -04:00
base = " #{ base_url || sso_url } "
" #{ base } #{ base . include? ( '?' ) ? '&' : '?' } #{ payload } "
2014-02-24 22:30:49 -05:00
end
def payload
2017-10-17 13:41:52 -04:00
payload = Base64 . strict_encode64 ( unsigned_payload )
2014-02-24 22:30:49 -05:00
" sso= #{ CGI :: escape ( payload ) } &sig= #{ sign ( payload ) } "
end
def unsigned_payload
payload = { }
2017-03-27 10:21:38 -04:00
2014-02-24 22:30:49 -05:00
ACCESSORS . each do | k |
2017-07-27 21:20:09 -04:00
next if ( val = send k ) == nil
2014-02-24 22:30:49 -05:00
payload [ k ] = val
end
2017-03-27 10:21:38 -04:00
@custom_fields & . each do | k , v |
payload [ " custom. #{ k } " ] = v . to_s
2014-04-21 23:52:13 -04:00
end
2014-02-24 22:30:49 -05:00
Rack :: Utils . build_query ( payload )
end
end