2019-05-03 06:17:27 +08:00
# frozen_string_literal: true
2014-02-25 11:30:49 +08:00
class SingleSignOn
2018-08-30 07:57:53 +08:00
2018-12-07 23:01:44 +08:00
class ParseError < RuntimeError ; end
2018-08-30 07:57:53 +08: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-04 01:53:14 +08:00
logout
2018-08-30 07:57:53 +08:00
name
nonce
profile_background_url
remove_groups
require_activation
return_sso_url
suppress_welcome_message
title
username
website
2020-04-28 14:06:35 +08:00
location
2018-08-30 07:57:53 +08:00
}
2014-02-25 11:30:49 +08:00
FIXNUMS = [ ]
2018-08-30 07:57:53 +08:00
BOOLS = % i {
admin
avatar_force_update
locale_force_update
2020-02-04 01:53:14 +08:00
logout
2018-08-30 07:57:53 +08:00
moderator
require_activation
suppress_welcome_message
}
2019-03-19 14:33:20 +08: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-25 11:30:49 +08:00
attr_accessor ( * ACCESSORS )
2017-11-02 19:33:35 +08:00
attr_writer :sso_secret , :sso_url
2014-02-25 11:30:49 +08: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
2018-12-19 17:22:10 +08:00
sso . sso_secret = sso_secret if sso_secret
2014-02-25 11:30:49 +08:00
parsed = Rack :: Utils . parse_query ( payload )
2018-10-15 13:03:53 +08:00
decoded = Base64 . decode64 ( parsed [ " sso " ] )
decoded_hash = Rack :: Utils . parse_query ( decoded )
2014-02-25 11:30:49 +08:00
if sso . sign ( parsed [ " sso " ] ) != parsed [ " sig " ]
2014-12-30 06:23:21 +08:00
diags = " \n \n sso: #{ parsed [ " sso " ] } \n \n sig: #{ parsed [ " sig " ] } \n \n expected sig: #{ sso . sign ( parsed [ " sso " ] ) } "
2014-12-30 06:28:44 +08:00
if parsed [ " sso " ] =~ / [^a-zA-Z0-9= \ r \ n \/ +] /m
2018-12-07 23:01:44 +08: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-30 06:23:21 +08:00
else
2018-12-07 23:01:44 +08:00
raise ParseError , " Bad signature for payload #{ diags } "
2014-12-30 06:23:21 +08:00
end
2014-02-25 11:30:49 +08:00
end
ACCESSORS . each do | k |
val = decoded_hash [ k . to_s ]
val = val . to_i if FIXNUMS . include? k
2014-11-27 09:39:00 +08:00
if BOOLS . include? k
val = [ " true " , " false " ] . include? ( val ) ? val == " true " : nil
end
2019-05-07 09:27:05 +08:00
sso . public_send ( " #{ k } = " , val )
2014-02-25 11:30:49 +08:00
end
2014-04-22 11:52:13 +08:00
decoded_hash . each do | k , v |
2017-03-27 22:21:38 +08:00
if field = k [ / ^custom \ .(.+)$ / , 1 ]
2014-04-22 11:52:13 +08:00
sso . custom_fields [ field ] = v
end
end
2014-02-25 11:30:49 +08:00
sso
end
2016-04-08 09:20:01 +08:00
def diagnostics
2019-05-07 10:22:37 +08:00
SingleSignOn :: ACCESSORS . map { | a | " #{ a } : #{ public_send ( a ) } " } . join ( " \n " )
2016-04-08 09:20:01 +08:00
end
2014-04-22 11:52:13 +08: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 17:22:10 +08:00
def sign ( payload , secret = nil )
secret = secret || sso_secret
2018-10-15 13:03:53 +08:00
OpenSSL :: HMAC . hexdigest ( " sha256 " , secret , payload )
2014-02-25 11:30:49 +08:00
end
def to_url ( base_url = nil )
2014-03-20 05:14:09 +08:00
base = " #{ base_url || sso_url } "
" #{ base } #{ base . include? ( '?' ) ? '&' : '?' } #{ payload } "
2014-02-25 11:30:49 +08:00
end
2018-12-19 17:22:10 +08:00
def payload ( secret = nil )
2017-10-18 01:41:52 +08:00
payload = Base64 . strict_encode64 ( unsigned_payload )
2018-12-19 17:22:10 +08:00
" sso= #{ CGI :: escape ( payload ) } &sig= #{ sign ( payload , secret ) } "
2014-02-25 11:30:49 +08:00
end
def unsigned_payload
payload = { }
2017-03-27 22:21:38 +08:00
2014-02-25 11:30:49 +08:00
ACCESSORS . each do | k |
2019-05-07 10:05:58 +08:00
next if ( val = public_send ( k ) ) == nil
payload [ k ] = val
2014-02-25 11:30:49 +08:00
end
2017-03-27 22:21:38 +08:00
@custom_fields & . each do | k , v |
payload [ " custom. #{ k } " ] = v . to_s
2014-04-22 11:52:13 +08:00
end
2014-02-25 11:30:49 +08:00
Rack :: Utils . build_query ( payload )
end
end