2019-05-02 18:17:27 -04:00
# frozen_string_literal: true
2014-02-24 22:30:49 -05:00
class SingleSignOn
2018-08-29 19:57:53 -04:00
2018-12-07 10:01:44 -05:00
class ParseError < RuntimeError ; end
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
2020-02-03 12:53:14 -05:00
logout
2018-08-29 19:57:53 -04:00
name
nonce
profile_background_url
remove_groups
require_activation
return_sso_url
suppress_welcome_message
title
username
website
2020-04-28 02:06:35 -04:00
location
2018-08-29 19:57:53 -04:00
}
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
2020-02-03 12:53:14 -05:00
logout
2018-08-29 19:57:53 -04:00
moderator
require_activation
suppress_welcome_message
}
2019-03-19 02:33:20 -04:00
def self . nonce_expiry_time
@nonce_expiry_time || = 10 . minutes
end
def self . nonce_expiry_time = ( v )
@nonce_expiry_time = v
end
2014-02-24 22:30:49 -05:00
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
2021-02-18 05:35:10 -05:00
def self . parse ( payload , sso_secret = nil , ** init_kwargs )
sso = new ( ** init_kwargs )
2018-12-19 04:22:10 -05:00
sso . sso_secret = sso_secret if sso_secret
2014-02-24 22:30:49 -05:00
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 )
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
2018-12-07 10:01:44 -05:00
raise ParseError , " 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
2018-12-07 10:01:44 -05:00
raise ParseError , " Bad signature for payload #{ diags } "
2014-12-29 17:23:21 -05:00
end
2014-02-24 22:30:49 -05:00
end
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
2019-05-06 21:27:05 -04:00
sso . public_send ( " #{ k } = " , val )
2014-02-24 22:30:49 -05:00
end
2014-04-21 23:52:13 -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
2019-05-06 22:22:37 -04:00
SingleSignOn :: ACCESSORS . map { | a | " #{ a } : #{ public_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-12-19 04:22:10 -05:00
def sign ( payload , secret = nil )
secret = secret || sso_secret
2018-10-15 01:03:53 -04:00
OpenSSL :: HMAC . hexdigest ( " sha256 " , secret , payload )
2014-02-24 22:30:49 -05:00
end
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-12-19 04:22:10 -05:00
def payload ( secret = nil )
2017-10-17 13:41:52 -04:00
payload = Base64 . strict_encode64 ( unsigned_payload )
2018-12-19 04:22:10 -05:00
" sso= #{ CGI :: escape ( payload ) } &sig= #{ sign ( payload , 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 |
2019-05-06 22:05:58 -04:00
next if ( val = public_send ( k ) ) == nil
payload [ k ] = val
2014-02-24 22:30:49 -05:00
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