jomz-google-api-client 0.7.1

Sign up to get free protection for your applications and to get access to all the features.
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