Class: Gigya::Connection

Inherits:
Object
  • Object
show all
Defined in:
lib/gigya/connection.rb

Constant Summary collapse

GIGYA_BASE_URL =
"gigya.com"

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(opts = {}) ⇒ Connection

Returns a new instance of Connection.



223
224
225
226
# File 'lib/gigya/connection.rb', line 223

def initialize(opts = {})
	@opts = opts
	@cached_data = {}
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(name, *args, &block) ⇒ Object



373
374
375
376
377
378
379
380
381
382
# File 'lib/gigya/connection.rb', line 373

def method_missing(name, *args, &block)
	if args.size == 0
		gdi = GigyaDynamicImplementation.new
		gdi.connection = self
		gdi.area = name
		return gdi
	else
		super
	end
end

Instance Attribute Details

#jwt_skip_validationObject

Returns the value of attribute jwt_skip_validation.



155
156
157
# File 'lib/gigya/connection.rb', line 155

def jwt_skip_validation
  @jwt_skip_validation
end

#whitelisted_api_keysObject

Returns the value of attribute whitelisted_api_keys.



156
157
158
# File 'lib/gigya/connection.rb', line 156

def whitelisted_api_keys
  @whitelisted_api_keys
end

Class Method Details

.build_rsa_key(modulus, exponent) ⇒ Object

Builds an OpenSSL RSA key from a given modulus and exponent. This is because Gigya likes to give us keys like this. stackoverflow.com/questions/46121275/ruby-rsa-from-exponent-and-modulus-strings



210
211
212
213
214
215
216
217
218
219
220
221
# File 'lib/gigya/connection.rb', line 210

def self.build_rsa_key(modulus, exponent)
	mod_num = OpenSSL::BN.new(Base64.decode64(strange_munge(modulus)), 2)
	exp_num = OpenSSL::BN.new(Base64.decode64(exponent), 2)
	k = OpenSSL::PKey::RSA.new
	if k.respond_to? :set_key
		k.set_key(mod_num, exp_num, 0)
	else
		k.n = mod_num
		k.e = exp_num
	end
	return k
end

.encode(x) ⇒ Object

The regular URI.encode doesn’t do “+”s, so this is a shortcut



184
185
186
# File 'lib/gigya/connection.rb', line 184

def self.encode(x)
	URI.encode_www_form_component(x)
end

.reformat_jwt(jwt_token) ⇒ Object

According to developers.gigya.com/display/GD/How+To+Validate+A+Gigya+id_token Gigya JWTs are not in the standard format, but must be munged first



201
202
203
204
205
# File 'lib/gigya/connection.rb', line 201

def self.reformat_jwt(jwt_token)
	signable_piece = jwt_token.split(".")[0..1].join(".")
	signature = strange_munge(jwt_token.split(".")[2])
	return [signable_piece, signature].join(".")
end

.shared_connectionObject



159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
# File 'lib/gigya/connection.rb', line 159

def self.shared_connection
	@@connection ||= begin
		conn = self.new(
			:datacenter => ENV["GIGYA_DATACENTER"] || "us1",
			:api_key => ENV["GIGYA_API_KEY"],
			:user_key => ENV["GIGYA_USER_KEY"],
			:user_secret => ENV["GIGYA_USER_SECRET"],
			:debug_connection => ENV["GIGYA_DEBUG_CONNECTION"] == "1"
		)

		whitelist = ENV["GIGYA_WHITELISTED_API_KEYS"]
		conn.whitelisted_api_keys = whitelist.split(",") unless whitelist.blank?

		conn.jwt_skip_validation = false
		conn
	end

	return @@connection
end

.shared_connection=(conn) ⇒ Object



179
180
181
# File 'lib/gigya/connection.rb', line 179

def self.shared_connection=(conn)
	@@connection = conn
end

.strange_munge(x) ⇒ Object

See here for the reasons for the strange reasons for this strange function: developers.gigya.com/display/GD/How+To+Validate+A+Gigya+id_token Seems to apply to some, but not all, pieces of Base64 encoded things.



191
192
193
# File 'lib/gigya/connection.rb', line 191

def self.strange_munge(x)
	x.gsub("-", "+").gsub("_", "/")
end

.strange_unmunge(x) ⇒ Object



195
196
197
# File 'lib/gigya/connection.rb', line 195

def self.strange_unmunge(x)
	x.gsub("+", "-").gsub("/", "_")
end

Instance Method Details

#api_call(http_method, area, function, params = nil, opts = nil) ⇒ Object



321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
# File 'lib/gigya/connection.rb', line 321

def api_call(http_method, area, function, params = nil, opts = nil)
	params ||= {}
	opts ||= {}
	opts = @opts.merge(opts)

	base_url = "https://#{area}.#{opts[:datacenter]}.#{GIGYA_BASE_URL}/#{area}.#{function}"

	params[:apiKey] = opts[:api_key]
	unless opts[:authenticate_app] == false
		params[:secret] = opts[:user_secret]
		params[:userKey] = opts[:user_key] unless opts[:user_key].blank?
	end

	if opts[:session] != nil
		if opts[:session][:user_id] != nil
			unless opts[:ignore_user_id] 
				params[:UID] = opts[:session][:user_id]
			end
		end
	end

	if opts[:debug_connection] 
		# FIXME - what to do with logging
		puts "DEBUG CONNECTION SEND: #{http_method} #{base_url} // #{params.inspect}"
	end
	http_response = nil
	response = begin
		http_response = http_method == "GET" ? http_driver.get(base_url, :query => params) : http_driver.post(base_url, :body => params)
		JSON.parse(http_response.body)
	rescue
		{"errorCode" => 600, "errorMessage" => "Unknown error", "errorDetail" => "Unable to communicate with authentication server", :http => http_response.inspect}
	end

	if opts[:debug_connection]
		# FIXME - what to do with logging
		puts "DEBUG CONNECTION RECEIVE: #{response.inspect}"
	end

	if opts[:throw_on_error]
		if response["statusCode"].to_i >= 400 || response["errorCode"].to_i > 0
			error_msg = "#{response["errorMessage"]}: #{response["errorDetails"]}"
			raise GigyaApiException.new(error_msg, response)
		end
	end

	return response
end

#api_get(area, function, params = nil, opts = nil) ⇒ Object



304
305
306
# File 'lib/gigya/connection.rb', line 304

def api_get(area, function, params = nil, opts = nil)
	api_call("GET", area, function, params, opts)
end

#api_keyObject



291
292
293
# File 'lib/gigya/connection.rb', line 291

def api_key
	@opts[:api_key]
end

#api_post(area, function, params = nil, opts = nil) ⇒ Object



308
309
310
# File 'lib/gigya/connection.rb', line 308

def api_post(area, function, params = nil, opts = nil)
	api_call("POST", area, function, params, opts)
end

#build_test_jwt(uid = nil, data_options = {}, expiration = nil, gigya_munge = false) ⇒ Object



228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
# File 'lib/gigya/connection.rb', line 228

def build_test_jwt(uid = nil, data_options = {}, expiration = nil, gigya_munge = false)
	uid = SecureRandom.uuid if uid.nil?
	data_options = (data_options || {}).dup
	data_options["sub"] = uid 
	data_options["apiKey"] ||= (@opts[:api_key] || "no_api_key")
	data_options["iss"] ||= "https://fidm.gigya.com/jwt/#{data_options["apiKey"]}/"
	data_options["iat"] ||= (Time.now - 10.seconds).to_i
	data_options["exp"] = (Time.now + expiration).to_i unless expiration.nil?
	data_options["exp"] ||= (Time.now + (60 * 60)).to_i
	data_options["firstName"] ||= "Jim#{rand(10000000)}"
	data_options["lastName"] ||= "Jimmersly#{rand(10000000)}"
	data_options["email"] ||= "example+#{uid}@example.com"
	
	jwt_str = JWT.encode(data_options, nil, 'none', {:typ => "JWT"})
	jwt_str = self.class.strange_unmunge(jwt_str) if gigya_munge

	return jwt_str
end

#connection_optionsObject



247
248
249
# File 'lib/gigya/connection.rb', line 247

def connection_options
	@opts
end

#download_latest_jwt_public_key(key_id = nil) ⇒ Object

NOTE - the key_id is here so that, in the future, we might be able

to download a specific key.  Right now, it is ignored and the
most recent one is obtained


254
255
256
257
258
259
260
261
262
# File 'lib/gigya/connection.rb', line 254

def download_latest_jwt_public_key(key_id = nil)
	keyinfo = api_get("accounts", "getJWTPublicKey")
	keyinfo_id = keyinfo["kid"]
	raise "Unsupported Key Type" if keyinfo["kty"] != "RSA"
	keyinfo_key = self.class.build_rsa_key(keyinfo["n"], keyinfo["e"])
	@cached_data["jwt_public_keys"] ||= {}
	@cached_data["jwt_public_keys"][keyinfo_id] = keyinfo_key
	return keyinfo_key
end

#http_driverObject

This allows substituting how HTTP calls are made (could be useful for testing)



313
314
315
# File 'lib/gigya/connection.rb', line 313

def http_driver
	@http_driver || HTTParty
end

#http_driver=(val) ⇒ Object



317
318
319
# File 'lib/gigya/connection.rb', line 317

def http_driver=(val)
	@http_driver = val
end

#login(username, password) ⇒ Object



295
296
297
298
299
300
301
302
# File 'lib/gigya/connection.rb', line 295

def (username, password)
	 = api_get("accounts", "login", {:loginID => username, :password => password, :targetEnv => "mobile"}, :throw_on_error => true)
	uid = ["UID"]
	session_token = ["sessionToken"]
	session_secret = ["sessionSecret"]
	conn = self.class.new(@opts.merge(:session => {:user_id => uid, :profile => ["profile"], :token => session_token, :secret => session_secret}))
	return conn
end

#lookup_user(uid) ⇒ Object



369
370
371
# File 'lib/gigya/connection.rb', line 369

def lookup_user(uid)
	Gigya::User.find(uid, :connection => self)
end

#validate_jwt(jwt_token, gigya_munge = false) ⇒ Object



264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
# File 'lib/gigya/connection.rb', line 264

def validate_jwt(jwt_token, gigya_munge = false)
	jwt_token = self.class.reformat_jwt(jwt_token) if gigya_munge

	user_jwt_info, signing_jwt_info = JWT.decode(jwt_token, nil, false)

	return user_jwt_info if jwt_skip_validation

	# If we have enumerated whitelisted API keys
	unless whitelisted_api_keys.nil?
		# Grab the API key encoded in the token
		jwt_api_key = user_jwt_info["apiKey"]

		# Our own API key is automatically valid
		if jwt_api_key != api_key
			# Make sure it is listed in the whitelisted keys
			raise "Invalid API Key" unless whitelisted_api_keys.include?(jwt_api_key)
		end
	end

	signing_key_id = signing_jwt_info["keyid"] || signing_jwt_info["kid"]
	@cached_data["jwt_public_keys"] ||= {}
	k = @cached_data["jwt_public_keys"][signing_key_id]
	k = download_latest_jwt_public_key(signing_key_id) if k == nil
	user_jwt_info, signing_jwt_info = JWT.decode(jwt_token, k, true, { :algorithm => signing_jwt_info["alg"] })
	return user_jwt_info
end