jomz-google-api-client 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. data/CHANGELOG.md +144 -0
  2. data/CONTRIBUTING.md +32 -0
  3. data/Gemfile +41 -0
  4. data/LICENSE +202 -0
  5. data/README.md +192 -0
  6. data/Rakefile +46 -0
  7. data/lib/cacerts.pem +2183 -0
  8. data/lib/compat/multi_json.rb +16 -0
  9. data/lib/google/api_client.rb +672 -0
  10. data/lib/google/api_client/auth/compute_service_account.rb +28 -0
  11. data/lib/google/api_client/auth/file_storage.rb +87 -0
  12. data/lib/google/api_client/auth/installed_app.rb +122 -0
  13. data/lib/google/api_client/auth/jwt_asserter.rb +126 -0
  14. data/lib/google/api_client/auth/key_utils.rb +93 -0
  15. data/lib/google/api_client/auth/pkcs12.rb +41 -0
  16. data/lib/google/api_client/batch.rb +323 -0
  17. data/lib/google/api_client/client_secrets.rb +176 -0
  18. data/lib/google/api_client/discovery.rb +19 -0
  19. data/lib/google/api_client/discovery/api.rb +300 -0
  20. data/lib/google/api_client/discovery/media.rb +77 -0
  21. data/lib/google/api_client/discovery/method.rb +363 -0
  22. data/lib/google/api_client/discovery/resource.rb +156 -0
  23. data/lib/google/api_client/discovery/schema.rb +121 -0
  24. data/lib/google/api_client/environment.rb +42 -0
  25. data/lib/google/api_client/errors.rb +60 -0
  26. data/lib/google/api_client/gzip.rb +28 -0
  27. data/lib/google/api_client/logging.rb +32 -0
  28. data/lib/google/api_client/media.rb +259 -0
  29. data/lib/google/api_client/railtie.rb +16 -0
  30. data/lib/google/api_client/reference.rb +27 -0
  31. data/lib/google/api_client/request.rb +351 -0
  32. data/lib/google/api_client/result.rb +253 -0
  33. data/lib/google/api_client/service.rb +233 -0
  34. data/lib/google/api_client/service/batch.rb +103 -0
  35. data/lib/google/api_client/service/request.rb +144 -0
  36. data/lib/google/api_client/service/resource.rb +40 -0
  37. data/lib/google/api_client/service/result.rb +162 -0
  38. data/lib/google/api_client/service/simple_file_store.rb +151 -0
  39. data/lib/google/api_client/service/stub_generator.rb +59 -0
  40. data/lib/google/api_client/service_account.rb +18 -0
  41. data/lib/google/api_client/version.rb +31 -0
  42. data/lib/google/inflection.rb +28 -0
  43. data/spec/fixtures/files/privatekey.p12 +0 -0
  44. data/spec/fixtures/files/sample.txt +33 -0
  45. data/spec/fixtures/files/secret.pem +19 -0
  46. data/spec/google/api_client/batch_spec.rb +249 -0
  47. data/spec/google/api_client/discovery_spec.rb +652 -0
  48. data/spec/google/api_client/gzip_spec.rb +86 -0
  49. data/spec/google/api_client/media_spec.rb +179 -0
  50. data/spec/google/api_client/request_spec.rb +30 -0
  51. data/spec/google/api_client/result_spec.rb +203 -0
  52. data/spec/google/api_client/service_account_spec.rb +164 -0
  53. data/spec/google/api_client/service_spec.rb +586 -0
  54. data/spec/google/api_client/simple_file_store_spec.rb +137 -0
  55. data/spec/google/api_client_spec.rb +253 -0
  56. data/spec/spec_helper.rb +56 -0
  57. data/tasks/gem.rake +97 -0
  58. data/tasks/git.rake +45 -0
  59. data/tasks/metrics.rake +22 -0
  60. data/tasks/spec.rake +57 -0
  61. data/tasks/wiki.rake +82 -0
  62. data/tasks/yard.rake +29 -0
  63. metadata +309 -0
@@ -0,0 +1,41 @@
1
+ # Copyright 2010 Google Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ require 'google/api_client/auth/key_utils'
16
+ module Google
17
+ class APIClient
18
+ ##
19
+ # Helper for loading keys from the PKCS12 files downloaded when
20
+ # setting up service accounts at the APIs Console.
21
+ #
22
+ module PKCS12
23
+ ##
24
+ # Loads a key from PKCS12 file, assuming a single private key
25
+ # is present.
26
+ #
27
+ # @param [String] keyfile
28
+ # Path of the PKCS12 file to load. If not a path to an actual file,
29
+ # assumes the string is the content of the file itself.
30
+ # @param [String] passphrase
31
+ # Passphrase for unlocking the private key
32
+ #
33
+ # @return [OpenSSL::PKey] The private key for signing assertions.
34
+ # @deprecated
35
+ # Use {Google::APIClient::KeyUtils} instead
36
+ def self.load_key(keyfile, passphrase)
37
+ KeyUtils.load_from_pkcs12(keyfile, passphrase)
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,323 @@
1
+ # Copyright 2012 Google Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ require 'addressable/uri'
16
+ require 'google/api_client/reference'
17
+ require 'uuidtools'
18
+
19
+ module Google
20
+ class APIClient
21
+
22
+ ##
23
+ # Helper class to contain a response to an individual batched call.
24
+ #
25
+ # @api private
26
+ class BatchedCallResponse
27
+ # @return [String] UUID of the call
28
+ attr_reader :call_id
29
+ # @return [Fixnum] HTTP status code
30
+ attr_accessor :status
31
+ # @return [Hash] HTTP response headers
32
+ attr_accessor :headers
33
+ # @return [String] HTTP response body
34
+ attr_accessor :body
35
+
36
+ ##
37
+ # Initialize the call response
38
+ #
39
+ # @param [String] call_id
40
+ # UUID of the original call
41
+ # @param [Fixnum] status
42
+ # HTTP status
43
+ # @param [Hash] headers
44
+ # HTTP response headers
45
+ # @param [#read, #to_str] body
46
+ # Response body
47
+ def initialize(call_id, status = nil, headers = nil, body = nil)
48
+ @call_id, @status, @headers, @body = call_id, status, headers, body
49
+ end
50
+ end
51
+
52
+ # Wraps multiple API calls into a single over-the-wire HTTP request.
53
+ #
54
+ # @example
55
+ #
56
+ # client = Google::APIClient.new
57
+ # urlshortener = client.discovered_api('urlshortener')
58
+ # batch = Google::APIClient::BatchRequest.new do |result|
59
+ # puts result.data
60
+ # end
61
+ #
62
+ # batch.add(:api_method => urlshortener.url.insert, :body_object => { 'longUrl' => 'http://example.com/foo' })
63
+ # batch.add(:api_method => urlshortener.url.insert, :body_object => { 'longUrl' => 'http://example.com/bar' })
64
+ #
65
+ # client.execute(batch)
66
+ #
67
+ class BatchRequest < Request
68
+ BATCH_BOUNDARY = "-----------RubyApiBatchRequest".freeze
69
+
70
+ # @api private
71
+ # @return [Array<(String,Google::APIClient::Request,Proc)] List of API calls in the batch
72
+ attr_reader :calls
73
+
74
+ ##
75
+ # Creates a new batch request.
76
+ #
77
+ # @param [Hash] options
78
+ # Set of options for this request.
79
+ # @param [Proc] block
80
+ # Callback for every call's response. Won't be called if a call defined
81
+ # a callback of its own.
82
+ #
83
+ # @return [Google::APIClient::BatchRequest]
84
+ # The constructed object.
85
+ #
86
+ # @yield [Google::APIClient::Result]
87
+ # block to be called when result ready
88
+ def initialize(options = {}, &block)
89
+ @calls = []
90
+ @global_callback = block if block_given?
91
+ @last_auto_id = 0
92
+
93
+ # TODO(sgomes): Use SecureRandom.uuid, drop UUIDTools when we drop 1.8
94
+ @base_id = UUIDTools::UUID.random_create.to_s
95
+
96
+ options[:uri] ||= 'https://www.googleapis.com/batch'
97
+ options[:http_method] ||= 'POST'
98
+
99
+ super options
100
+ end
101
+
102
+ ##
103
+ # Add a new call to the batch request.
104
+ # Each call must have its own call ID; if not provided, one will
105
+ # automatically be generated, avoiding collisions. If duplicate call IDs
106
+ # are provided, an error will be thrown.
107
+ #
108
+ # @param [Hash, Google::APIClient::Request] call
109
+ # the call to be added.
110
+ # @param [String] call_id
111
+ # the ID to be used for this call. Must be unique
112
+ # @param [Proc] block
113
+ # callback for this call's response.
114
+ #
115
+ # @return [Google::APIClient::BatchRequest]
116
+ # the BatchRequest, for chaining
117
+ #
118
+ # @yield [Google::APIClient::Result]
119
+ # block to be called when result ready
120
+ def add(call, call_id = nil, &block)
121
+ unless call.kind_of?(Google::APIClient::Reference)
122
+ call = Google::APIClient::Reference.new(call)
123
+ end
124
+ call_id ||= new_id
125
+ if @calls.assoc(call_id)
126
+ raise BatchError,
127
+ 'A call with this ID already exists: %s' % call_id
128
+ end
129
+ callback = block_given? ? block : @global_callback
130
+ @calls << [call_id, call, callback]
131
+ return self
132
+ end
133
+
134
+ ##
135
+ # Processes the HTTP response to the batch request, issuing callbacks.
136
+ #
137
+ # @api private
138
+ #
139
+ # @param [Faraday::Response] response
140
+ # the HTTP response.
141
+ def process_http_response(response)
142
+ content_type = find_header('Content-Type', response.headers)
143
+ boundary = /.*boundary=(.+)/.match(content_type)[1]
144
+ parts = response.body.split(/--#{Regexp.escape(boundary)}/)
145
+ parts = parts[1...-1]
146
+ parts.each do |part|
147
+ call_response = deserialize_call_response(part)
148
+ _, call, callback = @calls.assoc(call_response.call_id)
149
+ result = Google::APIClient::Result.new(call, call_response)
150
+ callback.call(result) if callback
151
+ end
152
+ Google::APIClient::Result.new(self, response)
153
+ end
154
+
155
+ ##
156
+ # Return the request body for the BatchRequest's HTTP request.
157
+ #
158
+ # @api private
159
+ #
160
+ # @return [String]
161
+ # the request body.
162
+ def to_http_request
163
+ if @calls.nil? || @calls.empty?
164
+ raise BatchError, 'Cannot make an empty batch request'
165
+ end
166
+ parts = @calls.map {|(call_id, call, callback)| serialize_call(call_id, call)}
167
+ build_multipart(parts, 'multipart/mixed', BATCH_BOUNDARY)
168
+ super
169
+ end
170
+
171
+
172
+ protected
173
+
174
+ ##
175
+ # Helper method to find a header from its name, regardless of case.
176
+ #
177
+ # @api private
178
+ #
179
+ # @param [String] name
180
+ # the name of the header to find.
181
+ # @param [Hash] headers
182
+ # the hash of headers and their values.
183
+ #
184
+ # @return [String]
185
+ # the value of the desired header.
186
+ def find_header(name, headers)
187
+ _, header = headers.detect do |h, v|
188
+ h.downcase == name.downcase
189
+ end
190
+ return header
191
+ end
192
+
193
+ ##
194
+ # Create a new call ID. Uses an auto-incrementing, conflict-avoiding ID.
195
+ #
196
+ # @api private
197
+ #
198
+ # @return [String]
199
+ # the new, unique ID.
200
+ def new_id
201
+ @last_auto_id += 1
202
+ while @calls.assoc(@last_auto_id)
203
+ @last_auto_id += 1
204
+ end
205
+ return @last_auto_id.to_s
206
+ end
207
+
208
+ ##
209
+ # Convert a Content-ID header value to an id. Presumes the Content-ID
210
+ # header conforms to the format that id_to_header() returns.
211
+ #
212
+ # @api private
213
+ #
214
+ # @param [String] header
215
+ # Content-ID header value.
216
+ #
217
+ # @return [String]
218
+ # The extracted ID value.
219
+ def header_to_id(header)
220
+ if !header.start_with?('<') || !header.end_with?('>') ||
221
+ !header.include?('+')
222
+ raise BatchError, 'Invalid value for Content-ID: "%s"' % header
223
+ end
224
+
225
+ base, call_id = header[1...-1].split('+')
226
+ return Addressable::URI.unencode(call_id)
227
+ end
228
+
229
+ ##
230
+ # Auxiliary method to split the headers from the body in an HTTP response.
231
+ #
232
+ # @api private
233
+ #
234
+ # @param [String] response
235
+ # the response to parse.
236
+ #
237
+ # @return [Array<Hash>, String]
238
+ # the headers and the body, separately.
239
+ def split_headers_and_body(response)
240
+ headers = {}
241
+ payload = response.lstrip
242
+ while payload
243
+ line, payload = payload.split("\n", 2)
244
+ line.sub!(/\s+\z/, '')
245
+ break if line.empty?
246
+ match = /\A([^:]+):\s*/.match(line)
247
+ if match
248
+ headers[match[1]] = match.post_match
249
+ else
250
+ raise BatchError, 'Invalid header line in response: %s' % line
251
+ end
252
+ end
253
+ return headers, payload
254
+ end
255
+
256
+ ##
257
+ # Convert a single batched response into a BatchedCallResponse object.
258
+ #
259
+ # @api private
260
+ #
261
+ # @param [String] call_response
262
+ # the request to deserialize.
263
+ #
264
+ # @return [Google::APIClient::BatchedCallResponse]
265
+ # the parsed and converted response.
266
+ def deserialize_call_response(call_response)
267
+ outer_headers, outer_body = split_headers_and_body(call_response)
268
+ status_line, payload = outer_body.split("\n", 2)
269
+ protocol, status, reason = status_line.split(' ', 3)
270
+
271
+ headers, body = split_headers_and_body(payload)
272
+ content_id = find_header('Content-ID', outer_headers)
273
+ call_id = header_to_id(content_id)
274
+ return BatchedCallResponse.new(call_id, status.to_i, headers, body)
275
+ end
276
+
277
+ ##
278
+ # Serialize a single batched call for assembling the multipart message
279
+ #
280
+ # @api private
281
+ #
282
+ # @param [Google::APIClient::Request] call
283
+ # the call to serialize.
284
+ #
285
+ # @return [Faraday::UploadIO]
286
+ # the serialized request
287
+ def serialize_call(call_id, call)
288
+ method, uri, headers, body = call.to_http_request
289
+ request = "#{method.to_s.upcase} #{Addressable::URI.parse(uri).request_uri} HTTP/1.1"
290
+ headers.each do |header, value|
291
+ request << "\r\n%s: %s" % [header, value]
292
+ end
293
+ if body
294
+ # TODO - CompositeIO if body is a stream
295
+ request << "\r\n\r\n"
296
+ if body.respond_to?(:read)
297
+ request << body.read
298
+ else
299
+ request << body.to_s
300
+ end
301
+ end
302
+ Faraday::UploadIO.new(StringIO.new(request), 'application/http', 'ruby-api-request', 'Content-ID' => id_to_header(call_id))
303
+ end
304
+
305
+ ##
306
+ # Convert an id to a Content-ID header value.
307
+ #
308
+ # @api private
309
+ #
310
+ # @param [String] call_id
311
+ # identifier of individual call.
312
+ #
313
+ # @return [String]
314
+ # A Content-ID header with the call_id encoded into it. A UUID is
315
+ # prepended to the value because Content-ID headers are supposed to be
316
+ # universally unique.
317
+ def id_to_header(call_id)
318
+ return '<%s+%s>' % [@base_id, Addressable::URI.encode(call_id)]
319
+ end
320
+
321
+ end
322
+ end
323
+ end
@@ -0,0 +1,176 @@
1
+ # Copyright 2010 Google Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+
16
+ require 'multi_json'
17
+ require 'compat/multi_json'
18
+
19
+
20
+ module Google
21
+ class APIClient
22
+ ##
23
+ # Manages the persistence of client configuration data and secrets. Format
24
+ # inspired by the Google API Python client.
25
+ #
26
+ # @see https://developers.google.com/api-client-library/python/guide/aaa_client_secrets
27
+ #
28
+ # @example
29
+ # {
30
+ # "web": {
31
+ # "client_id": "asdfjasdljfasdkjf",
32
+ # "client_secret": "1912308409123890",
33
+ # "redirect_uris": ["https://www.example.com/oauth2callback"],
34
+ # "auth_uri": "https://accounts.google.com/o/oauth2/auth",
35
+ # "token_uri": "https://accounts.google.com/o/oauth2/token"
36
+ # }
37
+ # }
38
+ #
39
+ # @example
40
+ # {
41
+ # "installed": {
42
+ # "client_id": "837647042410-75ifg...usercontent.com",
43
+ # "client_secret":"asdlkfjaskd",
44
+ # "redirect_uris": ["http://localhost", "urn:ietf:oauth:2.0:oob"],
45
+ # "auth_uri": "https://accounts.google.com/o/oauth2/auth",
46
+ # "token_uri": "https://accounts.google.com/o/oauth2/token"
47
+ # }
48
+ # }
49
+ class ClientSecrets
50
+
51
+ ##
52
+ # Reads client configuration from a file
53
+ #
54
+ # @param [String] filename
55
+ # Path to file to load
56
+ #
57
+ # @return [Google::APIClient::ClientSecrets]
58
+ # OAuth client settings
59
+ def self.load(filename=nil)
60
+ if filename && File.directory?(filename)
61
+ search_path = File.expand_path(filename)
62
+ filename = nil
63
+ end
64
+ while filename == nil
65
+ search_path ||= File.expand_path('.')
66
+ if File.exist?(File.join(search_path, 'client_secrets.json'))
67
+ filename = File.join(search_path, 'client_secrets.json')
68
+ elsif search_path == '/' || search_path =~ /[a-zA-Z]:[\/\\]/
69
+ raise ArgumentError,
70
+ 'No client_secrets.json filename supplied ' +
71
+ 'and/or could not be found in search path.'
72
+ else
73
+ search_path = File.expand_path(File.join(search_path, '..'))
74
+ end
75
+ end
76
+ data = File.open(filename, 'r') { |file| MultiJson.load(file.read) }
77
+ return self.new(data)
78
+ end
79
+
80
+ ##
81
+ # Intialize OAuth client settings.
82
+ #
83
+ # @param [Hash] options
84
+ # Parsed client secrets files
85
+ def initialize(options={})
86
+ # Client auth configuration
87
+ @flow = options[:flow] || options.keys.first.to_s || 'web'
88
+ fdata = options[@flow]
89
+ @client_id = fdata[:client_id] || fdata["client_id"]
90
+ @client_secret = fdata[:client_secret] || fdata["client_secret"]
91
+ @redirect_uris = fdata[:redirect_uris] || fdata["redirect_uris"]
92
+ @redirect_uris ||= [fdata[:redirect_uri]]
93
+ @javascript_origins = (
94
+ fdata[:javascript_origins] ||
95
+ fdata["javascript_origins"]
96
+ )
97
+ @javascript_origins ||= [fdata[:javascript_origin]]
98
+ @authorization_uri = fdata[:auth_uri] || fdata["auth_uri"]
99
+ @authorization_uri ||= fdata[:authorization_uri]
100
+ @token_credential_uri = fdata[:token_uri] || fdata["token_uri"]
101
+ @token_credential_uri ||= fdata[:token_credential_uri]
102
+
103
+ # Associated token info
104
+ @access_token = fdata[:access_token] || fdata["access_token"]
105
+ @refresh_token = fdata[:refresh_token] || fdata["refresh_token"]
106
+ @id_token = fdata[:id_token] || fdata["id_token"]
107
+ @expires_in = fdata[:expires_in] || fdata["expires_in"]
108
+ @expires_at = fdata[:expires_at] || fdata["expires_at"]
109
+ @issued_at = fdata[:issued_at] || fdata["issued_at"]
110
+ end
111
+
112
+ attr_reader(
113
+ :flow, :client_id, :client_secret, :redirect_uris, :javascript_origins,
114
+ :authorization_uri, :token_credential_uri, :access_token,
115
+ :refresh_token, :id_token, :expires_in, :expires_at, :issued_at
116
+ )
117
+
118
+ ##
119
+ # Serialize back to the original JSON form
120
+ #
121
+ # @return [String]
122
+ # JSON
123
+ def to_json
124
+ return MultiJson.dump({
125
+ self.flow => ({
126
+ 'client_id' => self.client_id,
127
+ 'client_secret' => self.client_secret,
128
+ 'redirect_uris' => self.redirect_uris,
129
+ 'javascript_origins' => self.javascript_origins,
130
+ 'auth_uri' => self.authorization_uri,
131
+ 'token_uri' => self.token_credential_uri,
132
+ 'access_token' => self.access_token,
133
+ 'refresh_token' => self.refresh_token,
134
+ 'id_token' => self.id_token,
135
+ 'expires_in' => self.expires_in,
136
+ 'expires_at' => self.expires_at,
137
+ 'issued_at' => self.issued_at
138
+ }).inject({}) do |accu, (k, v)|
139
+ # Prunes empty values from JSON output.
140
+ unless v == nil || (v.respond_to?(:empty?) && v.empty?)
141
+ accu[k] = v
142
+ end
143
+ accu
144
+ end
145
+ })
146
+ end
147
+
148
+ def to_authorization
149
+ gem 'signet', '>= 0.4.0'
150
+ require 'signet/oauth_2/client'
151
+ # NOTE: Do not rely on this default value, as it may change
152
+ new_authorization = Signet::OAuth2::Client.new
153
+ new_authorization.client_id = self.client_id
154
+ new_authorization.client_secret = self.client_secret
155
+ new_authorization.authorization_uri = (
156
+ self.authorization_uri ||
157
+ 'https://accounts.google.com/o/oauth2/auth'
158
+ )
159
+ new_authorization.token_credential_uri = (
160
+ self.token_credential_uri ||
161
+ 'https://accounts.google.com/o/oauth2/token'
162
+ )
163
+ new_authorization.redirect_uri = self.redirect_uris.first
164
+
165
+ # These are supported, but unlikely.
166
+ new_authorization.access_token = self.access_token
167
+ new_authorization.refresh_token = self.refresh_token
168
+ new_authorization.id_token = self.id_token
169
+ new_authorization.expires_in = self.expires_in
170
+ new_authorization.issued_at = self.issued_at if self.issued_at
171
+ new_authorization.expires_at = self.expires_at if self.expires_at
172
+ return new_authorization
173
+ end
174
+ end
175
+ end
176
+ end