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