Class: DiscourseApi::SingleSignOn

Inherits:
Object
  • Object
show all
Defined in:
lib/discourse_api/single_sign_on.rb

Defined Under Namespace

Classes: MissingConfigError, ParseError

Constant Summary collapse

ACCESSORS =
%i[
  add_groups
  admin
  avatar_force_update
  avatar_url
  bio
  card_background_url
  confirmed_2fa
  email
  external_id
  groups
  locale
  locale_force_update
  moderator
  name
  no_2fa_methods
  nonce
  profile_background_url
  remove_groups
  require_2fa
  require_activation
  return_sso_url
  suppress_welcome_message
  title
  username
]
FIXNUMS =
[]
BOOLS =
%i[
  admin
  avatar_force_update
  confirmed_2fa
  locale_force_update
  moderator
  no_2fa_methods
  require_2fa
  require_activation
  suppress_welcome_message
]
ARRAYS =
[:groups]

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#custom_fieldsObject



144
145
146
# File 'lib/discourse_api/single_sign_on.rb', line 144

def custom_fields
  @custom_fields ||= {}
end

#sso_secretObject



136
137
138
# File 'lib/discourse_api/single_sign_on.rb', line 136

def sso_secret
  @sso_secret || self.class.sso_secret
end

#sso_urlObject



140
141
142
# File 'lib/discourse_api/single_sign_on.rb', line 140

def sso_url
  @sso_url || self.class.sso_url
end

Class Method Details

.parse(payload, sso_secret = nil) ⇒ Object



96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
# File 'lib/discourse_api/single_sign_on.rb', line 96

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"]
    diags =
      "\n\nsso: #{parsed["sso"]}\n\nsig: #{parsed["sig"]}\n\nexpected sig: #{sso.sign(parsed["sso"])}"
    if parsed["sso"] =~ %r{[^a-zA-Z0-9=\r\n/+]}m
      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}"
    else
      raise ParseError, "Bad signature for payload #{diags}"
    end
  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
    val = %w[true false].include?(val) ? val == "true" : nil if BOOLS.include? k
    val = val.split(",") if ARRAYS.include?(k) && !val.nil?
    sso.send("#{k}=", val)
  end

  decoded_hash.each do |k, v|
    if field = k[/^custom\.(.+)$/, 1]
      sso.custom_fields[field] = v
    end
  end

  sso
end

.parse_hash(payload) ⇒ Object



67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
# File 'lib/discourse_api/single_sign_on.rb', line 67

def self.parse_hash(payload)
  sso = new

  sso.sso_secret = payload.delete(:sso_secret)
  sso.sso_url = payload.delete(:sso_url)

  ACCESSORS.each do |k|
    val = payload[k]

    val = val.to_i if FIXNUMS.include? k
    val = %w[true false].include?(val) ? val == "true" : nil if BOOLS.include? k
    val = val.split(",") if ARRAYS.include?(k) && !val.nil?
    sso.send("#{k}=", val)
  end

  # Set custom_fields
  sso.custom_fields = payload[:custom_fields]

  # Add custom_fields (old format)
  payload.each do |k, v|
    if field = k[/^custom\.(.+)$/, 1]
      # Maintain adding of .custom bug
      sso.custom_fields["custom.#{field}"] = v
    end
  end

  sso
end

.sso_secretObject

Raises:



59
60
61
# File 'lib/discourse_api/single_sign_on.rb', line 59

def self.sso_secret
  raise MissingConfigError, "sso_secret not implemented on class, be sure to set it on instance"
end

.sso_urlObject

Raises:



63
64
65
# File 'lib/discourse_api/single_sign_on.rb', line 63

def self.sso_url
  raise MissingConfigError, "sso_url not implemented on class, be sure to set it on instance"
end

Instance Method Details

#diagnosticsObject



132
133
134
# File 'lib/discourse_api/single_sign_on.rb', line 132

def diagnostics
  DiscourseApi::SingleSignOn::ACCESSORS.map { |a| "#{a}: #{send(a)}" }.join("\n")
end

#payloadObject



157
158
159
160
# File 'lib/discourse_api/single_sign_on.rb', line 157

def payload
  payload = Base64.strict_encode64(unsigned_payload)
  "sso=#{CGI.escape(payload)}&sig=#{sign(payload)}"
end

#sign(payload) ⇒ Object



148
149
150
# File 'lib/discourse_api/single_sign_on.rb', line 148

def sign(payload)
  OpenSSL::HMAC.hexdigest("sha256", sso_secret, payload)
end

#to_url(base_url = nil) ⇒ Object



152
153
154
155
# File 'lib/discourse_api/single_sign_on.rb', line 152

def to_url(base_url = nil)
  base = "#{base_url || sso_url}"
  "#{base}#{base.include?("?") ? "&" : "?"}#{payload}"
end

#unsigned_payloadObject



162
163
164
165
166
167
168
169
170
171
172
173
# File 'lib/discourse_api/single_sign_on.rb', line 162

def unsigned_payload
  payload = {}

  ACCESSORS.each do |k|
    next if (val = send k) == nil
    payload[k] = val
  end

  @custom_fields.each { |k, v| payload["custom.#{k}"] = v.to_s } if @custom_fields

  Rack::Utils.build_query(payload)
end