Class: Google::APIClient

Inherits:
Object
  • Object
show all
Includes:
Logging
Defined in:
lib/google/api_client.rb,
lib/google/api_client/batch.rb,
lib/google/api_client/media.rb,
lib/google/api_client/errors.rb,
lib/google/api_client/result.rb,
lib/google/api_client/charset.rb,
lib/google/api_client/logging.rb,
lib/google/api_client/railtie.rb,
lib/google/api_client/request.rb,
lib/google/api_client/service.rb,
lib/google/api_client/version.rb,
lib/google/api_client/reference.rb,
lib/google/api_client/auth/pkcs12.rb,
lib/google/api_client/environment.rb,
lib/google/api_client/auth/storage.rb,
lib/google/api_client/discovery/api.rb,
lib/google/api_client/service/batch.rb,
lib/google/api_client/auth/key_utils.rb,
lib/google/api_client/client_secrets.rb,
lib/google/api_client/service/result.rb,
lib/google/api_client/discovery/media.rb,
lib/google/api_client/service/request.rb,
lib/google/api_client/discovery/method.rb,
lib/google/api_client/discovery/schema.rb,
lib/google/api_client/service/resource.rb,
lib/google/api_client/auth/file_storage.rb,
lib/google/api_client/auth/jwt_asserter.rb,
lib/google/api_client/auth/installed_app.rb,
lib/google/api_client/discovery/resource.rb,
lib/google/api_client/service/stub_generator.rb,
lib/google/api_client/auth/storages/file_store.rb,
lib/google/api_client/auth/storages/redis_store.rb,
lib/google/api_client/service/simple_file_store.rb,
lib/google/api_client/auth/compute_service_account.rb

Overview

This class manages APIs communication.

Defined Under Namespace

Modules: ENV, KeyUtils, Logging, PKCS12, Schema, VERSION Classes: API, AuthorizationError, BatchError, BatchRequest, BatchedCallResponse, Charset, ClientError, ClientSecrets, ComputeServiceAccount, FileStorage, FileStore, InstalledAppFlow, InvalidIDTokenError, JWTAsserter, MediaUpload, Method, Railtie, RangedIO, RedirectError, RedisStore, Reference, Request, Resource, Result, ResumableUpload, ServerError, Service, Storage, TransmissionError, UploadIO, ValidationError

Class Attribute Summary collapse

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Logging

#logger

Constructor Details

#initialize(options = {}) ⇒ APIClient

Creates a new Google API client.

Parameters:

  • options (Hash) (defaults to: {})

    The configuration parameters for the client.

Options Hash (options):

  • :authorization (Symbol, #generate_authenticated_request) — default: :oauth_1

    The authorization mechanism used by the client. The following mechanisms are supported out-of-the-box: <ul>

    <li><code>:two_legged_oauth_1</code></li>
    <li><code>:oauth_1</code></li>
    <li><code>:oauth_2</code></li>
    <li><code>:google_app_default</code></li>
    

    </ul>

  • :auto_refresh_token (Boolean) — default: true

    The setting that controls whether or not the api client attempts to refresh authorization when a 401 is hit in #execute. If the token does not support it, this option is ignored.

  • :application_name (String)

    The name of the application using the client.

  • :scope (String | Array | nil)

    The scope(s) used when using google application default credentials

  • :application_version (String)

    The version number of the application using the client.

  • :user_agent (String) — default: "{app_name} google-api-ruby-client/{version} {os_name}/{os_version}"

    The user agent used by the client. Most developers will want to leave this value alone and use the ‘:application_name` option instead.

  • :host (String) — default: "www.googleapis.com"

    The API hostname used by the client. This rarely needs to be changed.

  • :port (String) — default: 443

    The port number used by the client. This rarely needs to be changed.

  • :discovery_path (String) — default: "/discovery/v1"

    The discovery base path. This rarely needs to be changed.

  • :ca_file (String)

    Optional set of root certificates to use when validating SSL connections. By default, a bundled set of trusted roots will be used.



87
88
89
90
91
92
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
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
# File 'lib/google/api_client.rb', line 87

def initialize(options={})
  logger.debug { "#{self.class} - Initializing client with options #{options}" }

  # Normalize key to String to allow indifferent access.
  options = options.inject({}) do |accu, (key, value)|
    accu[key.to_sym] = value
    accu
  end
  # Almost all API usage will have a host of 'www.googleapis.com'.
  self.host = options[:host] || 'www.googleapis.com'
  self.port = options[:port] || 443
  self.discovery_path = options[:discovery_path] || '/discovery/v1'

  # Most developers will want to leave this value alone and use the
  # application_name option.
  if options[:application_name]
    app_name = options[:application_name]
    app_version = options[:application_version]
    application_string = "#{app_name}/#{app_version || '0.0.0'}"
  else
    logger.warn { "#{self.class} - Please provide :application_name and :application_version when initializing the client" }
  end

  proxy = options[:proxy] || Object::ENV["http_proxy"]

  self.user_agent = options[:user_agent] || (
    "#{application_string} " +
    "google-api-ruby-client/#{Google::APIClient::VERSION::STRING} #{ENV::OS_VERSION}".strip + " (gzip)"
  ).strip
  # The writer method understands a few Symbols and will generate useful
  # default authentication mechanisms.
  self.authorization =
    options.key?(:authorization) ? options[:authorization] : :oauth_2
  if !options['scope'].nil? and self.authorization.respond_to?(:scope=)
    self.authorization.scope = options['scope']
  end
  self.auto_refresh_token = options.fetch(:auto_refresh_token) { true }
  self.key = options[:key]
  self.user_ip = options[:user_ip]
  self.retries = options.fetch(:retries) { 0 }
  self.expired_auth_retry = options.fetch(:expired_auth_retry) { true }
  @discovery_uris = {}
  @discovery_documents = {}
  @discovered_apis = {}
  ca_file = options[:ca_file] || File.expand_path('../../cacerts.pem', __FILE__)
  self.connection = Faraday.new do |faraday|
    faraday.request :gzip
    faraday.response :charset if options[:force_encoding]
    faraday.options.params_encoder = Faraday::FlatParamsEncoder
    faraday.ssl.ca_file = ca_file
    faraday.ssl.verify = true
    if faraday.respond_to?(:proxy=)
      # faraday >= 0.6.2
      faraday.proxy = proxy
    else
      # older versions of faraday
      faraday.proxy proxy
    end
    faraday.adapter Faraday.default_adapter
    if options[:faraday_option].is_a?(Hash)
      options[:faraday_option].each_pair do |option, value|
        faraday.options.send("#{option}=", value)
      end
    end
  end
  return self
end

Class Attribute Details

.loggerLogger

Logger for the API client

Returns:

  • (Logger)

    logger instance.



11
12
13
# File 'lib/google/api_client/logging.rb', line 11

def logger
  @logger
end

Instance Attribute Details

#authorization#generate_authenticated_request

Returns the authorization mechanism used by the client.

Returns:

  • (#generate_authenticated_request)

    The authorization mechanism.



159
160
161
# File 'lib/google/api_client.rb', line 159

def authorization
  @authorization
end

#auto_refresh_tokenBoolean

The setting that controls whether or not the api client attempts to refresh authorization when a 401 is hit in #execute.

Returns:

  • (Boolean)


226
227
228
# File 'lib/google/api_client.rb', line 226

def auto_refresh_token
  @auto_refresh_token
end

#connectionFaraday::Connection

Default Faraday/HTTP connection.

Returns:

  • (Faraday::Connection)


219
220
221
# File 'lib/google/api_client.rb', line 219

def connection
  @connection
end

#discovery_pathString

The base path used by the client for discovery.

Returns:

  • (String)

    The base path. Should almost always be ‘/discovery/v1’.



266
267
268
# File 'lib/google/api_client.rb', line 266

def discovery_path
  @discovery_path
end

#expired_auth_retryBoolean

Whether or not an expired auth token should be re-acquired (and the operation retried) regardless of retries setting

Returns:

  • (Boolean)

    Auto retry on auth expiry



280
281
282
# File 'lib/google/api_client.rb', line 280

def expired_auth_retry
  @expired_auth_retry
end

#hostString

The API hostname used by the client.

Returns:



252
253
254
# File 'lib/google/api_client.rb', line 252

def host
  @host
end

#keyString

The application’s API key issued by the API console.

Returns:

  • (String)

    The API key.



232
233
234
# File 'lib/google/api_client.rb', line 232

def key
  @key
end

#portString

The port number used by the client.

Returns:

  • (String)

    The port number. Should almost always be 443.



259
260
261
# File 'lib/google/api_client.rb', line 259

def port
  @port
end

#retriesFixNum

Number of times to retry on recoverable errors

Returns:

  • (FixNum)

    Number of retries



273
274
275
# File 'lib/google/api_client.rb', line 273

def retries
  @retries
end

#user_agentString

The user agent used by the client.

Returns:

  • (String)

    The user agent string used in the User-Agent header.



245
246
247
# File 'lib/google/api_client.rb', line 245

def user_agent
  @user_agent
end

#user_ipString

The IP address of the user this request is being performed on behalf of.

Returns:

  • (String)

    The user’s IP address.



238
239
240
# File 'lib/google/api_client.rb', line 238

def user_ip
  @user_ip
end

Instance Method Details

#directory_documentHash

Returns the parsed directory document.

Returns:

  • (Hash)

    The parsed JSON from the directory document.



353
354
355
356
357
358
359
360
361
362
# File 'lib/google/api_client.rb', line 353

def directory_document
  return @directory_document ||= (begin
    response = self.execute!(
      :http_method => :get,
      :uri => self.directory_uri,
      :authenticated => false
    )
    response.data
  end)
end

#directory_uriAddressable::URI

Returns the URI for the directory document.

Returns:

  • (Addressable::URI)

    The URI of the directory document.



286
287
288
# File 'lib/google/api_client.rb', line 286

def directory_uri
  return resolve_uri(self.discovery_path + '/apis')
end

#discovered_api(api, version = nil) ⇒ Google::APIClient::API

Returns the service object for a given service name and service version.

Parameters:

  • api (String, Symbol)

    The API name.

  • version (String) (defaults to: nil)

    The desired version of the API.

Returns:



410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
# File 'lib/google/api_client.rb', line 410

def discovered_api(api, version=nil)
  if !api.kind_of?(String) && !api.kind_of?(Symbol)
    raise TypeError,
      "Expected String or Symbol, got #{api.class}."
  end
  api = api.to_s
  version = version || 'v1'
  return @discovered_apis["#{api}:#{version}"] ||= begin
    document_base = self.discovery_uri(api, version)
    discovery_document = self.discovery_document(api, version)
    if document_base && discovery_document
      Google::APIClient::API.new(
        document_base,
        discovery_document
      )
    else
      nil
    end
  end
end

#discovered_apisArray

Returns all APIs published in the directory document.

Returns:

  • (Array)

    The list of available APIs.



387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
# File 'lib/google/api_client.rb', line 387

def discovered_apis
  @directory_apis ||= (begin
    document_base = self.directory_uri
    if self.directory_document && self.directory_document['items']
      self.directory_document['items'].map do |discovery_document|
        Google::APIClient::API.new(
          document_base,
          discovery_document
        )
      end
    else
      []
    end
  end)
end

#discovered_method(rpc_name, api, version = nil) ⇒ Google::APIClient::Method

Returns the method object for a given RPC name and service version.

Parameters:

  • rpc_name (String, Symbol)

    The RPC name of the desired method.

  • api (String, Symbol)

    The API the method is within.

  • version (String) (defaults to: nil)

    The desired version of the API.

Returns:



439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
# File 'lib/google/api_client.rb', line 439

def discovered_method(rpc_name, api, version=nil)
  if !rpc_name.kind_of?(String) && !rpc_name.kind_of?(Symbol)
    raise TypeError,
      "Expected String or Symbol, got #{rpc_name.class}."
  end
  rpc_name = rpc_name.to_s
  api = api.to_s
  version = version || 'v1'
  service = self.discovered_api(api, version)
  if service.to_h[rpc_name]
    return service.to_h[rpc_name]
  else
    return nil
  end
end

#discovery_document(api, version = nil) ⇒ Hash

Returns the parsed discovery document.

Parameters:

  • api (String, Symbol)

    The API name.

  • version (String) (defaults to: nil)

    The desired version of the API.

Returns:

  • (Hash)

    The parsed JSON from the discovery document.



370
371
372
373
374
375
376
377
378
379
380
381
# File 'lib/google/api_client.rb', line 370

def discovery_document(api, version=nil)
  api = api.to_s
  version = version || 'v1'
  return @discovery_documents["#{api}:#{version}"] ||= (begin
    response = self.execute!(
      :http_method => :get,
      :uri => self.discovery_uri(api, version),
      :authenticated => false
    )
    response.data
  end)
end

#discovery_uri(api, version = nil) ⇒ Addressable::URI

Returns the URI for the discovery document.

Parameters:

  • api (String, Symbol)

    The API name.

  • version (String) (defaults to: nil)

    The desired version of the API.

Returns:

  • (Addressable::URI)

    The URI of the discovery document.



311
312
313
314
315
316
317
318
319
320
321
# File 'lib/google/api_client.rb', line 311

def discovery_uri(api, version=nil)
  api = api.to_s
  version = version || 'v1'
  return @discovery_uris["#{api}:#{version}"] ||= (
    resolve_uri(
      self.discovery_path + '/apis/{api}/{version}/rest',
      'api' => api,
      'version' => version
    )
  )
end

#execute(*params) ⇒ Object

Same as Google::APIClient#execute!, but does not raise an exception for normal API errros.

See Also:



683
684
685
686
687
688
689
# File 'lib/google/api_client.rb', line 683

def execute(*params)
  begin
    return self.execute!(*params)
  rescue TransmissionError => e
    return e.result
  end
end

#execute!(*params) ⇒ Google::APIClient::Result

Executes a request, wrapping it in a Result object.

Examples:

result = client.execute(batch_request)
plus = client.discovered_api('plus')
result = client.execute(
  :api_method => plus.activities.list,
  :parameters => {'collection' => 'public', 'userId' => 'me'}
)

Parameters:

  • params (Google::APIClient::Request, Hash, Array)

    Either a Google::APIClient::Request, a Hash, or an Array.

    If a Google::APIClient::Request, no other parameters are expected.

    If a Hash, the below parameters are handled. If an Array, the parameters are assumed to be in the below order:

    • (Google::APIClient::Method) api_method: The method object or the RPC name of the method being executed.

    • (Hash, Array) parameters: The parameters to send to the method.

    • (String) body: The body of the request.

    • (Hash, Array) headers: The HTTP headers for the request.

    • (Hash) options: A set of options for the request, of which:

      • (#generate_authenticated_request) :authorization (default: true) - The authorization mechanism for the response. Used only if ‘:authenticated` is `true`.

      • (TrueClass, FalseClass) :authenticated (default: true) - ‘true` if the request must be signed or somehow authenticated, `false` otherwise.

      • (TrueClass, FalseClass) :gzip (default: true) - ‘true` if gzip enabled, `false` otherwise.

      • (FixNum) :retries - # of times to retry on recoverable errors

Returns:

See Also:



607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
# File 'lib/google/api_client.rb', line 607

def execute!(*params)
  if params.first.kind_of?(Google::APIClient::Request)
    request = params.shift
    options = params.shift || {}
  else
    # This block of code allows us to accept multiple parameter passing
    # styles, and maintaining some backwards compatibility.
    #
    # Note: I'm extremely tempted to deprecate this style of execute call.
    if params.last.respond_to?(:to_hash) && params.size == 1
      options = params.pop
    else
      options = {}
    end

    options[:api_method] = params.shift if params.size > 0
    options[:parameters] = params.shift if params.size > 0
    options[:body] = params.shift if params.size > 0
    options[:headers] = params.shift if params.size > 0
    options.update(params.shift) if params.size > 0
    request = self.generate_request(options)
  end

  request.headers['User-Agent'] ||= '' + self.user_agent unless self.user_agent.nil?
  request.headers['Accept-Encoding'] ||= 'gzip' unless options[:gzip] == false
  request.headers['Content-Type'] ||= ''
  request.parameters['key'] ||= self.key unless self.key.nil?
  request.parameters['userIp'] ||= self.user_ip unless self.user_ip.nil?

  connection = options[:connection] || self.connection
  request.authorization = options[:authorization] || self.authorization unless options[:authenticated] == false

  tries = 1 + (options[:retries] || self.retries)
  attempt = 0

  Retriable.retriable :tries => tries,
                      :on => [TransmissionError],
                      :on_retry => client_error_handler,
                      :interval => lambda {|attempts| (2 ** attempts) + rand} do
    attempt += 1

    # This 2nd level retriable only catches auth errors, and supports 1 retry, which allows
    # auth to be re-attempted without having to retry all sorts of other failures like
    # NotFound, etc
    Retriable.retriable :tries => ((expired_auth_retry || tries > 1) && attempt == 1) ? 2 : 1,
                        :on => [AuthorizationError],
                        :on_retry => authorization_error_handler(request.authorization) do
      result = request.send(connection, true)

      case result.status
        when 200...300
          result
        when 301, 302, 303, 307
          request = generate_request(request.to_hash.merge({
            :uri => result.headers['location'],
            :api_method => nil
          }))
          raise RedirectError.new(result.headers['location'], result)
        when 401
          raise AuthorizationError.new(result.error_message || 'Invalid/Expired Authentication', result)
        when 400, 402...500
          raise ClientError.new(result.error_message || "A client error has occurred", result)
        when 500...600
          raise ServerError.new(result.error_message || "A server error has occurred", result)
        else
          raise TransmissionError.new(result.error_message || "A transmission error has occurred", result)
      end
    end
  end
end

#generate_request(options = {}) ⇒ Google::APIClient::Reference

Generates a request.

Examples:

request = client.generate_request(
  :api_method => 'plus.activities.list',
  :parameters =>
    {'collection' => 'public', 'userId' => 'me'}
)

Parameters:

  • options (Hash) (defaults to: {})

    a customizable set of options

Options Hash (options):

  • :api_method (Google::APIClient::Method)

    The method object or the RPC name of the method being executed.

  • :parameters (Hash, Array)

    The parameters to send to the method.

  • :headers (Hash, Array)

    The HTTP headers for the request.

  • :body (String)

    The body of the request.

  • :version (String) — default: "v1"

    The service version. Only used if ‘api_method` is a `String`.

  • :authorization (#generate_authenticated_request)

    The authorization mechanism for the response. Used only if ‘:authenticated` is `true`.

  • :authenticated (TrueClass, FalseClass) — default: true

    ‘true` if the request must be signed or somehow authenticated, `false` otherwise.

Returns:



558
559
560
561
562
563
# File 'lib/google/api_client.rb', line 558

def generate_request(options={})
  options = {
    :api_client => self
  }.merge(options)
  return Google::APIClient::Request.new(options)
end

#preferred_version(api) ⇒ Google::APIClient::API

Note:

Warning: This method should be used with great care.

Returns the service object with the highest version number.

As APIs are updated, minor differences between versions may cause incompatibilities. Requesting a specific version will avoid this issue.

Parameters:

  • api (String, Symbol)

    The name of the service.

Returns:



465
466
467
468
469
470
471
472
473
474
# File 'lib/google/api_client.rb', line 465

def preferred_version(api)
  if !api.kind_of?(String) && !api.kind_of?(Symbol)
    raise TypeError,
      "Expected String or Symbol, got #{api.class}."
  end
  api = api.to_s
  return self.discovered_apis.detect do |a|
    a.name == api && a.preferred == true
  end
end

#register_discovery_document(api, version, discovery_document) ⇒ Google::APIClient::API

Manually registers a pre-loaded discovery document for a specific version of an API.

Parameters:

  • api (String, Symbol)

    The API name.

  • version (String)

    The desired version of the API.

  • discovery_document (String, StringIO)

    The contents of the discovery document.

Returns:



332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
# File 'lib/google/api_client.rb', line 332

def register_discovery_document(api, version, discovery_document)
  api = api.to_s
  version = version || 'v1'
  if discovery_document.kind_of?(StringIO)
    discovery_document.rewind
    discovery_document = discovery_document.string
  elsif discovery_document.respond_to?(:to_str)
    discovery_document = discovery_document.to_str
  else
    raise TypeError,
      "Expected String or StringIO, got #{discovery_document.class}."
  end
  @discovery_documents["#{api}:#{version}"] =
    MultiJson.load(discovery_document)
  discovered_api(api, version)
end

#register_discovery_uri(api, version, uri) ⇒ Google::APIClient::API

Manually registers a URI as a discovery document for a specific version of an API.

Parameters:

  • api (String, Symbol)

    The API name.

  • version (String)

    The desired version of the API.

  • uri (Addressable::URI)

    The URI of the discovery document.

Returns:



298
299
300
301
302
303
# File 'lib/google/api_client.rb', line 298

def register_discovery_uri(api, version, uri)
  api = api.to_s
  version = version || 'v1'
  @discovery_uris["#{api}:#{version}"] = uri
  discovered_api(api, version)
end

#verify_id_token!Object

Deprecated.

Use the google-id-token gem for verifying JWTs

Verifies an ID token against a server certificate. Used to ensure that an ID token supplied by an untrusted client-side mechanism is valid. Raises an error if the token is invalid or missing.



482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
# File 'lib/google/api_client.rb', line 482

def verify_id_token!
  require 'jwt'
  require 'openssl'
  @certificates ||= {}
  if !self.authorization.respond_to?(:id_token)
    raise ArgumentError, (
      "Current authorization mechanism does not support ID tokens: " +
      "#{self.authorization.class.to_s}"
    )
  elsif !self.authorization.id_token
    raise ArgumentError, (
      "Could not verify ID token, ID token missing. " +
      "Scopes were: #{self.authorization.scope.inspect}"
    )
  else
    check_cached_certs = lambda do
      valid = false
      for _key, cert in @certificates
        begin
          self.authorization.decoded_id_token(cert.public_key)
          valid = true
        rescue JWT::DecodeError, Signet::UnsafeOperationError
          # Expected exception. Ignore, ID token has not been validated.
        end
      end
      valid
    end
    if check_cached_certs.call()
      return true
    end
    response = self.execute!(
      :http_method => :get,
      :uri => 'https://www.googleapis.com/oauth2/v1/certs',
      :authenticated => false
    )
    @certificates.merge!(
      Hash[MultiJson.load(response.body).map do |key, cert|
        [key, OpenSSL::X509::Certificate.new(cert)]
      end]
    )
    if check_cached_certs.call()
      return true
    else
      raise InvalidIDTokenError,
        "Could not verify ID token against any available certificate."
    end
  end
  return nil
end