google-api-client 0.5.0 → 0.6.0
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.
- data/CHANGELOG.md +14 -0
- data/Gemfile +2 -2
- data/README.md +16 -13
- data/Rakefile +6 -2
- data/bin/google-api +24 -172
- data/lib/compat/multi_json.rb +4 -1
- data/lib/google/api_client.rb +30 -8
- data/lib/google/api_client/auth/installed_app.rb +115 -0
- data/lib/google/api_client/auth/jwt_asserter.rb +48 -50
- data/lib/google/api_client/auth/key_utils.rb +93 -0
- data/lib/google/api_client/auth/pkcs12.rb +4 -11
- data/lib/google/api_client/batch.rb +2 -3
- data/lib/google/api_client/client_secrets.rb +0 -1
- data/lib/google/api_client/discovery/api.rb +15 -1
- data/lib/google/api_client/logging.rb +32 -0
- data/lib/google/api_client/railtie.rb +16 -0
- data/lib/google/api_client/request.rb +36 -28
- data/lib/google/api_client/service_account.rb +1 -0
- data/lib/google/api_client/version.rb +1 -1
- data/spec/fixtures/files/privatekey.p12 +0 -0
- data/spec/fixtures/files/secret.pem +19 -0
- data/spec/google/api_client/batch_spec.rb +1 -1
- data/spec/google/api_client/discovery_spec.rb +20 -3
- data/spec/google/api_client/media_spec.rb +1 -1
- data/spec/google/api_client/result_spec.rb +1 -1
- data/spec/google/api_client/service_account_spec.rb +34 -1
- data/spec/google/api_client_spec.rb +1 -1
- data/tasks/gem.rake +2 -2
- metadata +35 -30
- data/Gemfile.lock +0 -80
@@ -0,0 +1,115 @@
|
|
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 'webrick'
|
16
|
+
require 'launchy'
|
17
|
+
|
18
|
+
module Google
|
19
|
+
class APIClient
|
20
|
+
|
21
|
+
# Small helper for the sample apps for performing OAuth 2.0 flows from the command
|
22
|
+
# line or in any other installed app environment.
|
23
|
+
#
|
24
|
+
# @example
|
25
|
+
#
|
26
|
+
# client = Google::APIClient.new
|
27
|
+
# flow = Google::APIClient::InstalledAppFlow.new(
|
28
|
+
# :client_id => '691380668085.apps.googleusercontent.com',
|
29
|
+
# :client_secret => '...,
|
30
|
+
# :scope => 'https://www.googleapis.com/auth/drive'
|
31
|
+
# )
|
32
|
+
# client.authorization = flow.authorize
|
33
|
+
#
|
34
|
+
class InstalledAppFlow
|
35
|
+
|
36
|
+
RESPONSE_BODY = <<-HTML
|
37
|
+
<html>
|
38
|
+
<head>
|
39
|
+
<script>
|
40
|
+
function closeWindow() {
|
41
|
+
window.open('', '_self', '');
|
42
|
+
window.close();
|
43
|
+
}
|
44
|
+
setTimeout(closeWindow, 10);
|
45
|
+
</script>
|
46
|
+
</head>
|
47
|
+
<body>You may close this window.</body>
|
48
|
+
</html>
|
49
|
+
HTML
|
50
|
+
|
51
|
+
##
|
52
|
+
# Configure the flow
|
53
|
+
#
|
54
|
+
# @param [Hash] options The configuration parameters for the client.
|
55
|
+
# @option options [Fixnum] :port
|
56
|
+
# Port to run the embedded server on. Defaults to 9292
|
57
|
+
# @option options [String] :client_id
|
58
|
+
# A unique identifier issued to the client to identify itself to the
|
59
|
+
# authorization server.
|
60
|
+
# @option options [String] :client_secret
|
61
|
+
# A shared symmetric secret issued by the authorization server,
|
62
|
+
# which is used to authenticate the client.
|
63
|
+
# @option options [String] :scope
|
64
|
+
# The scope of the access request, expressed either as an Array
|
65
|
+
# or as a space-delimited String.
|
66
|
+
#
|
67
|
+
# @see Signet::OAuth2::Client
|
68
|
+
def initialize(options)
|
69
|
+
@port = options[:port] || 9292
|
70
|
+
@authorization = Signet::OAuth2::Client.new({
|
71
|
+
:authorization_uri => 'https://accounts.google.com/o/oauth2/auth',
|
72
|
+
:token_credential_uri => 'https://accounts.google.com/o/oauth2/token',
|
73
|
+
:redirect_uri => "http://localhost:#{@port}/"}.update(options)
|
74
|
+
)
|
75
|
+
end
|
76
|
+
|
77
|
+
##
|
78
|
+
# Request authorization. Opens a browser and waits for response.
|
79
|
+
#
|
80
|
+
# @return [Signet::OAuth2::Client]
|
81
|
+
# Authorization instance, nil if user cancelled.
|
82
|
+
def authorize
|
83
|
+
auth = @authorization
|
84
|
+
|
85
|
+
server = WEBrick::HTTPServer.new(
|
86
|
+
:Port => @port,
|
87
|
+
:BindAddress =>"localhost",
|
88
|
+
:Logger => WEBrick::Log.new(STDOUT, 0),
|
89
|
+
:AccessLog => []
|
90
|
+
)
|
91
|
+
trap("INT") { server.shutdown }
|
92
|
+
|
93
|
+
server.mount_proc '/' do |req, res|
|
94
|
+
auth.code = req.query['code']
|
95
|
+
if auth.code
|
96
|
+
auth.fetch_access_token!
|
97
|
+
end
|
98
|
+
res.status = WEBrick::HTTPStatus::RC_ACCEPTED
|
99
|
+
res.body = RESPONSE_BODY
|
100
|
+
server.stop
|
101
|
+
end
|
102
|
+
|
103
|
+
Launchy.open(auth.authorization_uri.to_s)
|
104
|
+
server.start
|
105
|
+
if @authorization.access_token
|
106
|
+
return @authorization
|
107
|
+
else
|
108
|
+
return nil
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
@@ -22,17 +22,31 @@ module Google
|
|
22
22
|
# Generates access tokens using the JWT assertion profile. Requires a
|
23
23
|
# service account & access to the private key.
|
24
24
|
#
|
25
|
-
# @example
|
25
|
+
# @example Using Signet
|
26
|
+
#
|
27
|
+
# key = Google::APIClient::KeyUtils.load_from_pkcs12('client.p12', 'notasecret')
|
28
|
+
# client.authorization = Signet::OAuth2::Client.new(
|
29
|
+
# :token_credential_uri => 'https://accounts.google.com/o/oauth2/token',
|
30
|
+
# :audience => 'https://accounts.google.com/o/oauth2/token',
|
31
|
+
# :scope => 'https://www.googleapis.com/auth/prediction',
|
32
|
+
# :issuer => '123456-abcdef@developer.gserviceaccount.com',
|
33
|
+
# :signing_key => key)
|
34
|
+
# client.authorization.fetch_access_token!
|
35
|
+
# client.execute(...)
|
36
|
+
#
|
37
|
+
# @example Deprecated version
|
26
38
|
#
|
27
39
|
# client = Google::APIClient.new
|
28
40
|
# key = Google::APIClient::PKCS12.load_key('client.p12', 'notasecret')
|
29
|
-
# service_account = Google::APIClient::JWTAsserter(
|
30
|
-
#
|
31
|
-
#
|
32
|
-
#
|
41
|
+
# service_account = Google::APIClient::JWTAsserter.new(
|
42
|
+
# '123456-abcdef@developer.gserviceaccount.com',
|
43
|
+
# 'https://www.googleapis.com/auth/prediction',
|
44
|
+
# key)
|
33
45
|
# client.authorization = service_account.authorize
|
34
46
|
# client.execute(...)
|
35
47
|
#
|
48
|
+
# @deprecated
|
49
|
+
# Service accounts are now supported directly in Signet
|
36
50
|
# @see https://developers.google.com/accounts/docs/OAuth2ServiceAccount
|
37
51
|
class JWTAsserter
|
38
52
|
# @return [String] ID/email of the issuing party
|
@@ -43,9 +57,11 @@ module Google
|
|
43
57
|
attr_accessor :skew
|
44
58
|
# @return [String] Scopes to authorize
|
45
59
|
attr_reader :scope
|
46
|
-
# @return [OpenSSL::PKey] key for signing assertions
|
60
|
+
# @return [String,OpenSSL::PKey] key for signing assertions
|
47
61
|
attr_writer :key
|
48
|
-
|
62
|
+
# @return [String] Algorithm used for signing
|
63
|
+
attr_accessor :algorithm
|
64
|
+
|
49
65
|
##
|
50
66
|
# Initializes the asserter for a service account.
|
51
67
|
#
|
@@ -53,14 +69,18 @@ module Google
|
|
53
69
|
# Name/ID of the client issuing the assertion
|
54
70
|
# @param [String, Array] scope
|
55
71
|
# Scopes to authorize. May be a space delimited string or array of strings
|
56
|
-
# @param [OpenSSL::PKey] key
|
57
|
-
#
|
58
|
-
|
72
|
+
# @param [String,OpenSSL::PKey] key
|
73
|
+
# Key for signing assertions
|
74
|
+
# @param [String] algorithm
|
75
|
+
# Algorithm to use, either 'RS256' for RSA with SHA-256
|
76
|
+
# or 'HS256' for HMAC with SHA-256
|
77
|
+
def initialize(issuer, scope, key, algorithm = "RS256")
|
59
78
|
self.issuer = issuer
|
60
79
|
self.scope = scope
|
61
80
|
self.expiry = 60 # 1 min default
|
62
81
|
self.skew = 60
|
63
82
|
self.key = key
|
83
|
+
self.algorithm = algorithm
|
64
84
|
end
|
65
85
|
|
66
86
|
##
|
@@ -81,25 +101,6 @@ module Google
|
|
81
101
|
end
|
82
102
|
end
|
83
103
|
|
84
|
-
##
|
85
|
-
# Builds & signs the assertion.
|
86
|
-
#
|
87
|
-
# @param [String] person
|
88
|
-
# Email address of a user, if requesting a token to act on their behalf
|
89
|
-
# @return [String] Encoded JWT
|
90
|
-
def to_jwt(person=nil)
|
91
|
-
now = Time.new
|
92
|
-
assertion = {
|
93
|
-
"iss" => @issuer,
|
94
|
-
"scope" => self.scope,
|
95
|
-
"aud" => "https://accounts.google.com/o/oauth2/token",
|
96
|
-
"exp" => (now + expiry).to_i,
|
97
|
-
"iat" => (now - skew).to_i
|
98
|
-
}
|
99
|
-
assertion['prn'] = person unless person.nil?
|
100
|
-
return JWT.encode(assertion, @key, "RS256")
|
101
|
-
end
|
102
|
-
|
103
104
|
##
|
104
105
|
# Request a new access token.
|
105
106
|
#
|
@@ -109,31 +110,28 @@ module Google
|
|
109
110
|
# Pass through to Signet::OAuth2::Client.fetch_access_token
|
110
111
|
# @return [Signet::OAuth2::Client] Access token
|
111
112
|
#
|
112
|
-
# @see Signet::OAuth2::Client.fetch_access_token
|
113
|
+
# @see Signet::OAuth2::Client.fetch_access_token!
|
113
114
|
def authorize(person = nil, options={})
|
114
|
-
|
115
|
-
authorization = Signet::OAuth2::Client.new(
|
116
|
-
:token_credential_uri => 'https://accounts.google.com/o/oauth2/token'
|
117
|
-
)
|
118
|
-
authorization.grant_type = 'urn:ietf:params:oauth:grant-type:jwt-bearer'
|
119
|
-
authorization.extension_parameters = { :assertion => assertion }
|
115
|
+
authorization = self.to_authorization
|
120
116
|
authorization.fetch_access_token!(options)
|
121
|
-
return
|
122
|
-
end
|
123
|
-
end
|
124
|
-
|
125
|
-
class JWTAuthorization < DelegateClass(Signet::OAuth2::Client)
|
126
|
-
def initialize(authorization, asserter, person = nil)
|
127
|
-
@asserter = asserter
|
128
|
-
@person = person
|
129
|
-
super(authorization)
|
117
|
+
return authorization
|
130
118
|
end
|
131
119
|
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
120
|
+
##
|
121
|
+
# Builds a Signet OAuth2 client
|
122
|
+
#
|
123
|
+
# @return [Signet::OAuth2::Client] Access token
|
124
|
+
def to_authorization(person = nil)
|
125
|
+
return Signet::OAuth2::Client.new(
|
126
|
+
:token_credential_uri => 'https://accounts.google.com/o/oauth2/token',
|
127
|
+
:audience => 'https://accounts.google.com/o/oauth2/token',
|
128
|
+
:scope => self.scope,
|
129
|
+
:issuer => @issuer,
|
130
|
+
:signing_key => @key,
|
131
|
+
:signing_algorithm => @algorithm,
|
132
|
+
:person => person
|
133
|
+
)
|
134
|
+
end
|
137
135
|
end
|
138
136
|
end
|
139
137
|
end
|
@@ -0,0 +1,93 @@
|
|
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
|
+
module Google
|
16
|
+
class APIClient
|
17
|
+
##
|
18
|
+
# Helper for loading keys from the PKCS12 files downloaded when
|
19
|
+
# setting up service accounts at the APIs Console.
|
20
|
+
#
|
21
|
+
module KeyUtils
|
22
|
+
##
|
23
|
+
# Loads a key from PKCS12 file, assuming a single private key
|
24
|
+
# is present.
|
25
|
+
#
|
26
|
+
# @param [String] keyfile
|
27
|
+
# Path of the PKCS12 file to load. If not a path to an actual file,
|
28
|
+
# assumes the string is the content of the file itself.
|
29
|
+
# @param [String] passphrase
|
30
|
+
# Passphrase for unlocking the private key
|
31
|
+
#
|
32
|
+
# @return [OpenSSL::PKey] The private key for signing assertions.
|
33
|
+
def self.load_from_pkcs12(keyfile, passphrase)
|
34
|
+
load_key(keyfile, passphrase) do |content, passphrase|
|
35
|
+
OpenSSL::PKCS12.new(content, passphrase).key
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
|
40
|
+
##
|
41
|
+
# Loads a key from a PEM file.
|
42
|
+
#
|
43
|
+
# @param [String] keyfile
|
44
|
+
# Path of the PEM file to load. If not a path to an actual file,
|
45
|
+
# assumes the string is the content of the file itself.
|
46
|
+
# @param [String] passphrase
|
47
|
+
# Passphrase for unlocking the private key
|
48
|
+
#
|
49
|
+
# @return [OpenSSL::PKey] The private key for signing assertions.
|
50
|
+
#
|
51
|
+
def self.load_from_pem(keyfile, passphrase)
|
52
|
+
load_key(keyfile, passphrase) do | content, passphrase|
|
53
|
+
OpenSSL::PKey::RSA.new(content, passphrase)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
##
|
60
|
+
# Helper for loading keys from file or memory. Accepts a block
|
61
|
+
# to handle the specific file format.
|
62
|
+
#
|
63
|
+
# @param [String] keyfile
|
64
|
+
# Path of thefile to load. If not a path to an actual file,
|
65
|
+
# assumes the string is the content of the file itself.
|
66
|
+
# @param [String] passphrase
|
67
|
+
# Passphrase for unlocking the private key
|
68
|
+
#
|
69
|
+
# @yield [String, String]
|
70
|
+
# Key file & passphrase to extract key from
|
71
|
+
# @yieldparam [String] keyfile
|
72
|
+
# Contents of the file
|
73
|
+
# @yieldparam [String] passphrase
|
74
|
+
# Passphrase to unlock key
|
75
|
+
# @yieldreturn [OpenSSL::PKey]
|
76
|
+
# Private key
|
77
|
+
#
|
78
|
+
# @return [OpenSSL::PKey] The private key for signing assertions.
|
79
|
+
def self.load_key(keyfile, passphrase, &block)
|
80
|
+
begin
|
81
|
+
begin
|
82
|
+
content = File.open(keyfile, 'rb') { |io| io.read }
|
83
|
+
rescue
|
84
|
+
content = keyfile
|
85
|
+
end
|
86
|
+
block.call(content, passphrase)
|
87
|
+
rescue OpenSSL::OpenSSLError
|
88
|
+
raise ArgumentError.new("Invalid keyfile or passphrase")
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -12,6 +12,7 @@
|
|
12
12
|
# See the License for the specific language governing permissions and
|
13
13
|
# limitations under the License.
|
14
14
|
|
15
|
+
require 'google/api_client/auth/key_utils'
|
15
16
|
module Google
|
16
17
|
class APIClient
|
17
18
|
##
|
@@ -30,18 +31,10 @@ module Google
|
|
30
31
|
# Passphrase for unlocking the private key
|
31
32
|
#
|
32
33
|
# @return [OpenSSL::PKey] The private key for signing assertions.
|
34
|
+
# @deprecated
|
35
|
+
# Use {Google::APIClient::KeyUtils} instead
|
33
36
|
def self.load_key(keyfile, passphrase)
|
34
|
-
|
35
|
-
if File.exists?(keyfile)
|
36
|
-
content = File.read(keyfile)
|
37
|
-
else
|
38
|
-
content = keyfile
|
39
|
-
end
|
40
|
-
pkcs12 = OpenSSL::PKCS12.new(content, passphrase)
|
41
|
-
return pkcs12.key
|
42
|
-
rescue OpenSSL::PKCS12::PKCS12Error
|
43
|
-
raise ArgumentError.new("Invalid keyfile or passphrase")
|
44
|
-
end
|
37
|
+
KeyUtils.load_from_pkcs12(keyfile, passphrase)
|
45
38
|
end
|
46
39
|
end
|
47
40
|
end
|
@@ -59,12 +59,11 @@ module Google
|
|
59
59
|
# puts result.data
|
60
60
|
# end
|
61
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' })
|
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
64
|
#
|
65
65
|
# client.execute(batch)
|
66
66
|
#
|
67
|
-
|
68
67
|
class BatchRequest < Request
|
69
68
|
BATCH_BOUNDARY = "-----------RubyApiBatchRequest".freeze
|
70
69
|
|
@@ -63,7 +63,6 @@ module Google
|
|
63
63
|
end
|
64
64
|
while filename == nil
|
65
65
|
search_path ||= File.expand_path('.')
|
66
|
-
puts search_path
|
67
66
|
if File.exist?(File.join(search_path, 'client_secrets.json'))
|
68
67
|
filename = File.join(search_path, 'client_secrets.json')
|
69
68
|
elsif search_path == '/' || search_path =~ /[a-zA-Z]:[\/\\]/
|
@@ -14,7 +14,7 @@
|
|
14
14
|
|
15
15
|
|
16
16
|
require 'addressable/uri'
|
17
|
-
|
17
|
+
require 'multi_json'
|
18
18
|
require 'google/inflection'
|
19
19
|
require 'google/api_client/discovery/resource'
|
20
20
|
require 'google/api_client/discovery/method'
|
@@ -281,6 +281,20 @@ module Google
|
|
281
281
|
"#<%s:%#0x ID:%s>", self.class.to_s, self.object_id, self.id
|
282
282
|
)
|
283
283
|
end
|
284
|
+
|
285
|
+
##
|
286
|
+
# Marshalling support - serialize the API to a string (doc base + original
|
287
|
+
# discovery document).
|
288
|
+
def _dump(level)
|
289
|
+
MultiJson.dump([@document_base.to_s, @discovery_document])
|
290
|
+
end
|
291
|
+
|
292
|
+
##
|
293
|
+
# Marshalling support - Restore an API instance from serialized form
|
294
|
+
def self._load(obj)
|
295
|
+
new(*MultiJson.load(obj))
|
296
|
+
end
|
297
|
+
|
284
298
|
end
|
285
299
|
end
|
286
300
|
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'logger'
|
2
|
+
|
3
|
+
module Google
|
4
|
+
class APIClient
|
5
|
+
|
6
|
+
class << self
|
7
|
+
##
|
8
|
+
# Logger for the API client
|
9
|
+
#
|
10
|
+
# @return [Logger] logger instance.
|
11
|
+
attr_accessor :logger
|
12
|
+
end
|
13
|
+
|
14
|
+
self.logger = Logger.new(STDOUT)
|
15
|
+
self.logger.level = Logger::WARN
|
16
|
+
|
17
|
+
##
|
18
|
+
# Module to make accessing the logger simpler
|
19
|
+
module Logging
|
20
|
+
##
|
21
|
+
# Logger for the API client
|
22
|
+
#
|
23
|
+
# @return [Logger] logger instance.
|
24
|
+
def logger
|
25
|
+
Google::APIClient.logger
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
end
|