2014-02-24 22:30:49 -05:00
class SingleSignOn
2018-08-29 19:57:53 -04:00
ACCESSORS = % i {
add_groups
admin moderator
avatar_force_update
avatar_url
bio
card_background_url
email
external_id
groups
locale
locale_force_update
name
nonce
profile_background_url
remove_groups
require_activation
return_sso_url
suppress_welcome_message
title
username
website
}
2014-02-24 22:30:49 -05:00
FIXNUMS = [ ]
2018-08-29 19:57:53 -04:00
BOOLS = % i {
admin
avatar_force_update
locale_force_update
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
parsed = Rack :: Utils . parse_query ( payload )
2018-10-15 01:03:53 -04:00
decoded = Base64 . decode64 ( parsed [ " sso " ] )
decoded_hash = Rack :: Utils . parse_query ( decoded )
return_sso_url = decoded_hash [ 'return_sso_url' ]
sso . sso_secret = sso_secret || ( provider_secret ( return_sso_url ) if return_sso_url )
2014-02-24 22:30:49 -05:00
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
2018-10-15 01:03:53 -04:00
def self . provider_secret ( return_sso_url )
2018-10-24 15:23:18 -04:00
provider_secrets = SiteSetting . sso_provider_secrets . split ( / [| \ n] / )
2018-10-15 01:03:53 -04:00
provider_secrets_hash = Hash [ * provider_secrets ]
return_url_host = URI . parse ( return_sso_url ) . host
2018-10-15 06:57:45 -04:00
# moves wildcard domains to the end of hash
sorted_secrets = provider_secrets_hash . sort_by { | k , _ | k } . reverse . to_h
2018-10-15 01:03:53 -04:00
2018-10-15 06:57:45 -04:00
secret = sorted_secrets . select do | domain , _ |
2018-10-15 01:03:53 -04:00
WildcardDomainChecker . check_domain ( domain , return_url_host )
end
secret . present? ? secret . values . first : nil
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
2018-10-15 01:03:53 -04:00
def sign ( payload , provider_secret = nil )
secret = provider_secret || sso_secret
OpenSSL :: HMAC . hexdigest ( " sha256 " , 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
2018-10-15 01:03:53 -04:00
def payload ( provider_secret = nil )
2017-10-17 13:41:52 -04:00
payload = Base64 . strict_encode64 ( unsigned_payload )
2018-10-15 01:03:53 -04:00
" sso= #{ CGI :: escape ( payload ) } &sig= #{ sign ( payload , provider_secret ) } "
2014-02-24 22:30:49 -05:00
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