gitkit-ruby-client 1.0.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.
Files changed (4) hide show
  1. data/README.rdoc +64 -0
  2. data/lib/gitkit_client.rb +290 -0
  3. data/lib/rpc_helper.rb +209 -0
  4. metadata +130 -0
@@ -0,0 +1,64 @@
1
+ = Google Identity Toolkit Ruby Client
2
+
3
+ Google Identity Toolkit (Gitkit) ruby client library is for third party sites to easily integrate with Gitkit service.
4
+
5
+ === Installation
6
+
7
+ gem install gitkit-ruby-client
8
+
9
+ === Examples
10
+
11
+ # Create a server config file or download it from Google Developer Console
12
+ # The config file contains Gitkit library config in json format
13
+ # {
14
+ # "clientId" : "oauth2-web-client-id.apps.googleusercontent.com",
15
+ # "serviceAccountEmail" : "service-account-email@developer.gserviceaccount.com",
16
+ # "serviceAccountPrivateKeyFile" : "path-to-service-account-private-key-file.p12",
17
+ # "widgetUrl" : "full-url-of-gitkit-widget-on-your-site",
18
+ # "cookieName" : "gtoken",
19
+ # "serverKey" : "devconsole-server-key"
20
+ # }
21
+
22
+ # Create a Gitkit client
23
+ gitkit_client = GitkitLib::GitkitClient.create_from_config_file 'gitkit-server-config.json'
24
+
25
+ # Verify the Gitkit token in the incoming http request
26
+ token = request.cookies["gtoken"]
27
+ user = gitkit_client.verify_gitkit_token token
28
+
29
+ # Upload passwords
30
+ def calc_sha1(key, plain_text, salt)
31
+ hmac = OpenSSL::HMAC.new key, 'sha1'
32
+ hmac << plain_text
33
+ hmac << salt
34
+ hmac.digest
35
+ end
36
+
37
+ hash_key = 'hash-key'
38
+
39
+ user1 = GitkitLib::GitkitUser.new
40
+ user1.email = '1234@example.com'
41
+ user1.user_id = '1234'
42
+ user1.salt = 'salt-1'
43
+ user1.password_hash = calc_sha1(hash_key, '1111', 'salt-1')
44
+
45
+ user2 = GitkitLib::GitkitUser.new
46
+ user2.email = '5678@example.com'
47
+ user2.user_id = '5678'
48
+ user2.salt = 'salt-2'
49
+ user2.password_hash = calc_sha1(hash_key, '5555', 'salt-2')
50
+ user2.name = '56 78'
51
+
52
+ gitkit_client.upload_users 'HMAC_SHA1', hash_key, [user1, user2]
53
+
54
+ # Get user by email
55
+ user = gitkit_client.get_user_by_email('1234@example.com')
56
+
57
+ # Get user by id
58
+ user = gitkit_client.get_user_by_id('5678')
59
+
60
+ # Delete a user
61
+ gitkit_client.delete_user '5678'
62
+
63
+ # Download all accounts
64
+ gitkit_client.get_all_users(2) { |account| pp account}
@@ -0,0 +1,290 @@
1
+ # Copyright 2014 Google Inc. All Rights Reserved.
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 'jwt'
17
+ require 'json'
18
+ require 'rpc_helper'
19
+ require 'uri'
20
+
21
+ module GitkitLib
22
+
23
+ class GitkitClient
24
+
25
+ # Create a client from json config file
26
+ #
27
+ # @param [String] file file name of the json-format config
28
+ def self.create_from_config_file(file)
29
+ config = JSON.parse File.open(file, 'rb') { |io| io.read }
30
+ p12key = File.open(config['serviceAccountPrivateKeyFile'], 'rb') {
31
+ |io| io.read }
32
+ new(
33
+ config['clientId'],
34
+ config['serviceAccountEmail'],
35
+ p12key,
36
+ config['widgetUrl'],
37
+ config['serverApiKey'])
38
+ end
39
+
40
+ # Initializes a GitkitClient.
41
+ #
42
+ # @param [String] client_id Google oauth2 web client id of this site
43
+ # @param [String] service_account_email Google service account email
44
+ # @param [String] service_account_key Google service account private p12 key
45
+ # @param [String] widget_url url to host the Gitkit widget
46
+ # @param [String] server_api_key server-side Google API key
47
+ def initialize(client_id, service_account_email, service_account_key,
48
+ widget_url, server_api_key = nil)
49
+ @client_id = client_id
50
+ @widget_url = widget_url
51
+ @rpc_helper = RpcHelper.new(service_account_email, service_account_key,
52
+ server_api_key)
53
+ @certificates = {}
54
+ end
55
+
56
+ # Verifies a Gitkit token
57
+ #
58
+ # @param [String] token_string the token to be verified
59
+ # @return [GitkitUser, nil] for valid token, [nil, String] otherwise with
60
+ # error_message.
61
+ def verify_gitkit_token(token_string)
62
+ if token_string.nil?
63
+ return nil, 'no token'
64
+ end
65
+ begin
66
+ parsed_token, _ = JWT.decode(token_string, nil, true) {|header|
67
+ # key finder
68
+ key_id = header['kid']
69
+ unless @certificates.has_key? key_id
70
+ @certificates = Hash[get_certs.map {|key,cert|
71
+ [key, OpenSSL::X509::Certificate.new(cert)]}]
72
+ end
73
+ @certificates[key_id].public_key
74
+ }
75
+ # check expiration time
76
+ if Time.new.to_i > parsed_token['exp']
77
+ return nil, 'token expired'
78
+ end
79
+ # check audience
80
+ if parsed_token['aud'] != @client_id
81
+ return nil, 'audience mismatch'
82
+ end
83
+ GitkitUser.parse_from_api_response parsed_token
84
+ rescue JWT::DecodeError => e
85
+ return nil, e.message
86
+ end
87
+ end
88
+
89
+ # Gets Gitkit public certs
90
+ #
91
+ # @api private
92
+ def get_certs
93
+ @rpc_helper.get_gitkit_certs()
94
+ end
95
+
96
+ # Gets user info by email
97
+ #
98
+ # @param [String] email user email
99
+ # @return [GitkitUser] for the email
100
+ def get_user_by_email(email)
101
+ response = @rpc_helper.get_user_by_email email
102
+ GitkitUser.parse_from_api_response response.fetch('users', [{}])[0]
103
+ end
104
+
105
+ # Gets user info by user id
106
+ #
107
+ # @param [String] id user id
108
+ # @return [GitkitUser] for the id
109
+ def get_user_by_id(id)
110
+ response = @rpc_helper.get_user_by_id id
111
+ GitkitUser.parse_from_api_response response.fetch('users', [{}])[0]
112
+ end
113
+
114
+ # Downloads all user accounts from Gitkit service
115
+ #
116
+ # @param [Fixnum] max_results pagination size of each request
117
+ # @yield [GitkitUser] individual user account
118
+ def get_all_users(max_results = 10)
119
+ next_page_token = nil
120
+ while true
121
+ next_page_token, accounts = @rpc_helper.download_account(
122
+ next_page_token, max_results)
123
+ accounts.each { |account|
124
+ yield GitkitUser.parse_from_api_response account }
125
+ if not next_page_token or accounts.length == 0
126
+ break
127
+ end
128
+ end
129
+ end
130
+
131
+ # Uploads multiple accounts to Gitkit service
132
+ #
133
+ # @param [String] hash_algorithm password hash algorithm
134
+ # @param [String] hash_key key of the hash algorithm
135
+ # @param [Array<GitkitUser>] accounts user accounts to be uploaded
136
+ def upload_users(hash_algorithm, hash_key, accounts)
137
+ account_request = accounts.collect { |account| account.to_request }
138
+ @rpc_helper.upload_account hash_algorithm, JWT.base64url_encode(hash_key),
139
+ account_request
140
+ end
141
+
142
+ # Deletes a user account from Gitkit service
143
+ #
144
+ # @param [String] local_id user id to be deleted
145
+ def delete_user(local_id)
146
+ @rpc_helper.delete_account local_id
147
+ end
148
+
149
+ # Get one-time out-of-band code for ResetPassword/ChangeEmail request
150
+ #
151
+ # @param [String] full_url the full URL of incoming request
152
+ # @param [Hash{String=>String}] param dict of HTTP POST params
153
+ # @param [String] user_ip end user's IP address
154
+ # @param [String] gitkit_token the gitkit token if user logged in
155
+ #
156
+ # @return [Hash] {
157
+ # email: user email who is asking reset password
158
+ # oobLink: the generated link to be send to user's email
159
+ # action: OobAction
160
+ # response_body: the http body to be returned
161
+ # }
162
+ def get_oob_result(full_url, param, user_ip, gitkit_token=nil)
163
+ if param.has_key? 'action'
164
+ begin
165
+ if param['action'] == 'resetPassword'
166
+ oob_link = build_oob_link(full_url,
167
+ password_reset_request(param, user_ip),
168
+ param['action'])
169
+ return password_reset_response(oob_link, param)
170
+ elsif param['action'] == 'changeEmail'
171
+ unless gitkit_token
172
+ return failure_msg('login is required')
173
+ end
174
+ oob_link = build_oob_link(full_url,
175
+ change_email_request(param, user_ip, gitkit_token),
176
+ param['action'])
177
+ return email_change_response(oob_link, param)
178
+ end
179
+ rescue GitkitClientError => error
180
+ return failure_msg(error.message)
181
+ end
182
+ end
183
+ failure_msg('unknown request type')
184
+ end
185
+
186
+ def password_reset_request(param, user_ip)
187
+ {
188
+ 'email' => param['email'],
189
+ 'userIp' => user_ip,
190
+ 'challenge' => param['challenge'],
191
+ 'captchaResp' => param['response'],
192
+ 'requestType' => 'PASSWORD_RESET'
193
+ }
194
+ end
195
+
196
+ def change_email_request(param, user_ip, gitkit_token)
197
+ {
198
+ 'email' => param['oldEmail'],
199
+ 'newEmail' => param['newEmail'],
200
+ 'userIp' => user_ip,
201
+ 'idToken' => gitkit_token,
202
+ 'requestType' => 'NEW_EMAIL_ACCEPT'
203
+ }
204
+ end
205
+
206
+ def build_oob_link(full_url, param, mode)
207
+ code = @rpc_helper.get_oob_code(param)
208
+ if code
209
+ oob_link = Addressable::URI.parse full_url
210
+ oob_link.path = @widget_url
211
+ oob_link.query_values = { 'mode' => mode, 'oobCode' => code}
212
+ return oob_link.to_s
213
+ end
214
+ nil
215
+ end
216
+
217
+ def failure_msg(msg)
218
+ {:response_body => {'error' => msg}}.to_json
219
+ end
220
+
221
+ def email_change_response(oob_link, param)
222
+ {
223
+ :oldEmail => param['email'],
224
+ :newEmail => param['newEmail'],
225
+ :oobLink => oob_link,
226
+ :action => :CHANGE_EMAIL,
227
+ :response_body => {'success' => true}.to_json
228
+ }
229
+ end
230
+
231
+ def password_reset_response(oob_link, param)
232
+ {
233
+ :email => param['email'],
234
+ :oobLink => oob_link,
235
+ :action => :RESET_PASSWORD,
236
+ :response_body => {'success' => true}.to_json
237
+ }
238
+ end
239
+ end
240
+
241
+ class GitkitUser
242
+ attr_accessor :email, :user_id, :name, :photo_url, :provider_id,
243
+ :email_verified, :password_hash, :salt, :password, :provider_info
244
+
245
+ def self.parse_from_api_response(api_response)
246
+ user = self.new
247
+ user.email = api_response.fetch('email', nil)
248
+ user.user_id = api_response.fetch('user_id',
249
+ api_response.fetch('localId', nil))
250
+ user.name = api_response.fetch('displayName', nil)
251
+ user.photo_url = api_response.fetch('photoUrl', nil)
252
+ user.provider_id = api_response.fetch('provider_id',
253
+ api_response.fetch('providerId', nil))
254
+ user.email_verified = api_response.fetch('emailVerified',
255
+ api_response.fetch('verified', nil))
256
+ user.password_hash = api_response.fetch('passwordHash', nil)
257
+ user.salt = api_response.fetch('salt', nil)
258
+ user.password = api_response.fetch('password', nil)
259
+ user.provider_info = api_response.fetch('providerUserInfo', {})
260
+ user
261
+ end
262
+
263
+ # Convert to gitkit api request (a dict)
264
+ def to_request
265
+ request = {}
266
+ request['email'] = @email if @email
267
+ request['localId'] = @user_id if @user_id
268
+ request['displayName'] = @name if @name
269
+ request['photoUrl'] = @photo_url if @photo_url
270
+ request['emailVerified'] = @email_verified if @email_verified != nil
271
+ request['passwordHash'] =
272
+ JWT.base64url_encode @password_hash if @password_hash
273
+ request['salt'] = JWT.base64url_encode @salt if @salt
274
+ request['providerUserInfo'] = @provider_info if @provider_info != nil
275
+ request
276
+ end
277
+ end
278
+
279
+ class GitkitClientError < StandardError
280
+ def initialize(message)
281
+ super
282
+ end
283
+ end
284
+
285
+ class GitkitServerError < StandardError
286
+ def initialize(message)
287
+ super
288
+ end
289
+ end
290
+ end
@@ -0,0 +1,209 @@
1
+ # Copyright 2014 Google Inc. All Rights Reserved.
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 'jwt'
16
+ require 'json'
17
+ require 'faraday'
18
+ require 'gitkit_client'
19
+ require 'openssl'
20
+
21
+ module GitkitLib
22
+ class RpcHelper
23
+ attr_accessor :access_token, :token_issued_at, :token_duration
24
+
25
+ TOKEN_ENDPOINT = 'https://accounts.google.com/o/oauth2/token'
26
+ GITKIT_SCOPE = 'https://www.googleapis.com/auth/identitytoolkit'
27
+ GITKIT_API_URL =
28
+ 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/'
29
+
30
+ def initialize(service_account_email, service_account_key, server_api_key,
31
+ google_token_endpoint = TOKEN_ENDPOINT)
32
+ @service_account_email = service_account_email
33
+ @google_api_url = google_token_endpoint
34
+ @connection = Faraday::Connection.new
35
+ @service_account_key =
36
+ OpenSSL::PKCS12.new(service_account_key, 'notasecret').key
37
+ @server_api_key = server_api_key
38
+ @token_duration = 3600
39
+ @token_issued_at = 0
40
+ @access_token = nil
41
+ end
42
+
43
+ # GetAccountInfo by email
44
+ #
45
+ # @api private
46
+ # @param [String] email account email to be queried
47
+ # @return [JSON] account info
48
+ def get_user_by_email(email)
49
+ invoke_gitkit_api('getAccountInfo', {'email' => [email]})
50
+ end
51
+
52
+ # GetAccountInfo by id
53
+ #
54
+ # @api private
55
+ # @param [String] id account id to be queried
56
+ # @return [JSON] account info
57
+ def get_user_by_id(id)
58
+ invoke_gitkit_api('getAccountInfo', {'localId' => [id]})
59
+ end
60
+
61
+ # Get out-of-band code for ResetPassword/ChangeEmail etc. operation
62
+ #
63
+ # @api private
64
+ # @param [Hash<String, String>] request the oob request
65
+ # @return <String> the oob code
66
+ def get_oob_code(request)
67
+ response = invoke_gitkit_api('getOobConfirmationCode', request)
68
+ response.fetch('oobCode', nil)
69
+ end
70
+
71
+ # Download all accounts
72
+ #
73
+ # @api private
74
+ # @param [String] next_page_token pagination token for next page
75
+ # @param [Fixnum] max_results pagination size
76
+ # @return [Array<JSON>] user account info
77
+ def download_account(next_page_token, max_results)
78
+ param = {}
79
+ if next_page_token
80
+ param['nextPageToken'] = next_page_token
81
+ end
82
+ if max_results
83
+ param['maxResults'] = max_results
84
+ end
85
+ response = invoke_gitkit_api('downloadAccount', param)
86
+ return response.fetch('nextPageToken', nil), response.fetch('users', {})
87
+ end
88
+
89
+ # Delete an account
90
+ #
91
+ # @api private
92
+ # @param <String> local_id user id to be deleted
93
+ def delete_account(local_id)
94
+ invoke_gitkit_api('deleteAccount', {'localId' => local_id})
95
+ end
96
+
97
+ # Upload batch accounts
98
+ #
99
+ # @api private
100
+ # @param <String> hash_algorithm hash algorithm
101
+ # @param <String> hash_key hash key
102
+ # @param <Array<GitkitUser>> accounts account to be uploaded
103
+ def upload_account(hash_algorithm, hash_key, accounts)
104
+ param = {
105
+ 'hashAlgorithm' => hash_algorithm,
106
+ 'signerKey' => hash_key,
107
+ 'users' => accounts
108
+ }
109
+ invoke_gitkit_api('uploadAccount', param)
110
+ end
111
+
112
+ # Creates a signed jwt assertion
113
+ #
114
+ # @api private
115
+ # @return [String] jwt assertion
116
+ def sign_assertion
117
+ now = Time.new
118
+ assertion = {
119
+ 'iss' => @service_account_email,
120
+ 'scope' => GITKIT_SCOPE,
121
+ 'aud' => @google_api_url,
122
+ 'exp' => (now + @token_duration).to_i,
123
+ 'iat' => now.to_i
124
+ }
125
+ JWT.encode(assertion, @service_account_key, 'RS256')
126
+ end
127
+
128
+ # Get an access token, from Google server if cached one is expired
129
+ #
130
+ # @api private
131
+ def fetch_access_token
132
+ if is_token_expired
133
+ assertion = sign_assertion
134
+ post_body = {
135
+ 'assertion' => assertion,
136
+ 'grant_type' => 'urn:ietf:params:oauth:grant-type:jwt-bearer'}
137
+ headers = {'Content-type' => 'application/x-www-form-urlencoded'}
138
+ response = @connection.post(RpcHelper::TOKEN_ENDPOINT, post_body,
139
+ headers)
140
+ @access_token = JSON.parse(response.env[:body])['access_token']
141
+ @token_issued_at = Time.new.to_i
142
+ end
143
+ @access_token
144
+ end
145
+
146
+ # Check whether the cached access token is expired
147
+ #
148
+ # @api private
149
+ # @return <Boolean> whether the access token is expired
150
+ def is_token_expired
151
+ @access_token == nil ||
152
+ Time.new.to_i > @token_issued_at + @token_duration - 30
153
+ end
154
+
155
+ # Invoke Gitkit API, with optional access token for service account
156
+ # operations
157
+ #
158
+ # @api private
159
+ # @param [String] method Gitkit API method name
160
+ # @param [Hash<String, String>] params api request params
161
+ # @param [bool] need_service_account whether the request needs to be
162
+ # authenticated
163
+ # @return <JSON> the Gitkit api response
164
+ def invoke_gitkit_api(method, params, need_service_account=true)
165
+ post_body = JSON.generate(params)
166
+ headers = {'Content-type' => 'application/json'}
167
+ if need_service_account
168
+ @connection.authorization :Bearer, fetch_access_token
169
+ end
170
+ response = @connection.post(GITKIT_API_URL + method, post_body, headers)
171
+ check_gitkit_error JSON.parse(response.env[:body])
172
+ end
173
+
174
+ # Download the Gitkit public certs
175
+ #
176
+ # @api private
177
+ # @return <JSON> the public certs
178
+ def get_gitkit_certs
179
+ if @server_api_key.nil?
180
+ @connection.authorization :Bearer, fetch_access_token
181
+ response = @connection.get(GITKIT_API_URL + 'publicKeys')
182
+ else
183
+ response = @connection.get [GITKIT_API_URL, 'publicKeys?key=',
184
+ @server_api_key].join
185
+ end
186
+ MultiJson.load response.body
187
+ end
188
+
189
+ # Checks the Gitkit response
190
+ #
191
+ # @api private
192
+ # @param [JSON] response the response received
193
+ # @return [JSON] the response if no error
194
+ def check_gitkit_error(response)
195
+ if response.has_key? 'error'
196
+ error = response['error']
197
+ if error.has_key? 'code'
198
+ code = error['code']
199
+ raise GitkitClientError, error['message'] if code.to_s.match(/^4/)
200
+ raise GitkitServerError, error['message']
201
+ else
202
+ raise GitkitServerError, 'null error code from Gitkit server'
203
+ end
204
+ else
205
+ response
206
+ end
207
+ end
208
+ end
209
+ end
metadata ADDED
@@ -0,0 +1,130 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: gitkit-ruby-client
3
+ version: !ruby/object:Gem::Version
4
+ hash: 23
5
+ prerelease:
6
+ segments:
7
+ - 1
8
+ - 0
9
+ - 0
10
+ version: 1.0.0
11
+ platform: ruby
12
+ authors:
13
+ - Jin Liu
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2014-05-15 00:00:00 Z
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: addressable
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ hash: 7
29
+ segments:
30
+ - 2
31
+ - 3
32
+ - 2
33
+ version: 2.3.2
34
+ type: :runtime
35
+ version_requirements: *id001
36
+ - !ruby/object:Gem::Dependency
37
+ name: faraday
38
+ prerelease: false
39
+ requirement: &id002 !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ">="
43
+ - !ruby/object:Gem::Version
44
+ hash: 59
45
+ segments:
46
+ - 0
47
+ - 9
48
+ - 0
49
+ version: 0.9.0
50
+ type: :runtime
51
+ version_requirements: *id002
52
+ - !ruby/object:Gem::Dependency
53
+ name: multi_json
54
+ prerelease: false
55
+ requirement: &id003 !ruby/object:Gem::Requirement
56
+ none: false
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ hash: 23
61
+ segments:
62
+ - 1
63
+ - 0
64
+ - 0
65
+ version: 1.0.0
66
+ type: :runtime
67
+ version_requirements: *id003
68
+ - !ruby/object:Gem::Dependency
69
+ name: jwt
70
+ prerelease: false
71
+ requirement: &id004 !ruby/object:Gem::Requirement
72
+ none: false
73
+ requirements:
74
+ - - "="
75
+ - !ruby/object:Gem::Version
76
+ hash: 13
77
+ segments:
78
+ - 0
79
+ - 1
80
+ - 11
81
+ version: 0.1.11
82
+ type: :runtime
83
+ version_requirements: *id004
84
+ description: Google Identity Toolkit Ruby client library
85
+ email:
86
+ executables: []
87
+
88
+ extensions: []
89
+
90
+ extra_rdoc_files:
91
+ - README.rdoc
92
+ files:
93
+ - lib/gitkit_client.rb
94
+ - lib/rpc_helper.rb
95
+ - README.rdoc
96
+ homepage: https://developers.google.com/identity-toolkit/v3
97
+ licenses: []
98
+
99
+ post_install_message:
100
+ rdoc_options: []
101
+
102
+ require_paths:
103
+ - lib
104
+ required_ruby_version: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ">="
108
+ - !ruby/object:Gem::Version
109
+ hash: 3
110
+ segments:
111
+ - 0
112
+ version: "0"
113
+ required_rubygems_version: !ruby/object:Gem::Requirement
114
+ none: false
115
+ requirements:
116
+ - - ">="
117
+ - !ruby/object:Gem::Version
118
+ hash: 3
119
+ segments:
120
+ - 0
121
+ version: "0"
122
+ requirements: []
123
+
124
+ rubyforge_project:
125
+ rubygems_version: 1.8.15
126
+ signing_key:
127
+ specification_version: 4
128
+ summary: Google Identity Toolkit Ruby client
129
+ test_files: []
130
+