google-api-client 0.5.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
- # '123456-abcdef@developer.gserviceaccount.com',
31
- # 'https://www.googleapis.com/auth/prediction',
32
- # key)
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
- # RSA private key for signing assertions
58
- def initialize(issuer, scope, key)
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
- assertion = self.to_jwt(person)
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 JWTAuthorization.new(authorization, self, person)
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
- def fetch_access_token!(options={})
133
- new_authorization = @asserter.authorize(@person, options)
134
- __setobj__(new_authorization)
135
- self
136
- end
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
- begin
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