Class: DiscourseApi::SingleSignOn

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

Constant Summary collapse

ACCESSORS =
[
  :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 =
[
  :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



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

def custom_fields
  @custom_fields ||= {}
end

#sso_secretObject



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

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

#sso_urlObject



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

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

Class Method Details

.parse(payload, sso_secret = nil) ⇒ Object



93
94
95
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
# File 'lib/discourse_api/single_sign_on.rb', line 93

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"] =~ /[^a-zA-Z0-9=\r\n\/+]/m
      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}"
    else
      raise RuntimeError, "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
    if BOOLS.include? k
      val = ["true", "false"].include?(val) ? val == "true" : nil
    end
    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



62
63
64
65
66
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
# File 'lib/discourse_api/single_sign_on.rb', line 62

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
    if BOOLS.include? k
      val = ["true", "false"].include?(val) ? val == "true" : nil
    end
    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:

  • (RuntimeError)


54
55
56
# File 'lib/discourse_api/single_sign_on.rb', line 54

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

.sso_urlObject

Raises:

  • (RuntimeError)


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

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

Instance Method Details

#diagnosticsObject



129
130
131
# File 'lib/discourse_api/single_sign_on.rb', line 129

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

#payloadObject



154
155
156
157
# File 'lib/discourse_api/single_sign_on.rb', line 154

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

#sign(payload) ⇒ Object



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

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

#to_url(base_url = nil) ⇒ Object



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

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

#unsigned_payloadObject



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

def unsigned_payload
  payload = {}

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

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

  Rack::Utils.build_query(payload)
end