class Gigya::Connection
Constants
- GIGYA_BASE_URL
Attributes
Public Class Methods
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
# 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
The regular URI.encode doesn't do “+”s, so this is a shortcut
# File lib/gigya/connection.rb, line 184 def self.encode(x) URI.encode_www_form_component(x) end
# File lib/gigya/connection.rb, line 223 def initialize(opts = {}) @opts = opts @cached_data = {} end
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
# 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
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.
# File lib/gigya/connection.rb, line 191 def self.strange_munge(x) x.gsub("-", "+").gsub("_", "/") end
# File lib/gigya/connection.rb, line 195 def self.strange_unmunge(x) x.gsub("+", "-").gsub("/", "_") end
Public Instance Methods
# 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
# File lib/gigya/connection.rb, line 304 def api_get(area, function, params = nil, opts = nil) api_call("GET", area, function, params, opts) end
# File lib/gigya/connection.rb, line 291 def api_key @opts[:api_key] end
# File lib/gigya/connection.rb, line 308 def api_post(area, function, params = nil, opts = nil) api_call("POST", area, function, params, opts) end
# 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
# File lib/gigya/connection.rb, line 247 def connection_options @opts end
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
# 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
This allows substituting how HTTP calls are made (could be useful for testing)
# File lib/gigya/connection.rb, line 313 def http_driver @http_driver || HTTParty end
# File lib/gigya/connection.rb, line 317 def http_driver=(val) @http_driver = val end
# File lib/gigya/connection.rb, line 295 def login(username, password) user_info = api_get("accounts", "login", {:loginID => username, :password => password, :targetEnv => "mobile"}, :throw_on_error => true) uid = user_info["UID"] session_token = user_info["sessionToken"] session_secret = user_info["sessionSecret"] conn = self.class.new(@opts.merge(:session => {:user_id => uid, :profile => user_info["profile"], :token => session_token, :secret => session_secret})) return conn end
# File lib/gigya/connection.rb, line 369 def lookup_user(uid) Gigya::User.find(uid, :connection => self) end
# 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
# 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