identity-toolkit-ruby-client 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (5) hide show
  1. checksums.yaml +7 -0
  2. data/README.rdoc +66 -0
  3. data/lib/gitkit_client.rb +289 -0
  4. data/lib/rpc_helper.rb +209 -0
  5. metadata +103 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: c99e7fd2191547c175870cb9656f018696d29a94
4
+ data.tar.gz: 6eb020bd9c8d326ca1790c175ce703528a67e1f1
5
+ SHA512:
6
+ metadata.gz: 5a2657585eef66bde0f66fb1e18af4ccef23ff5b7829d094a6e6a2fe60dcbc315ba7bb2930aaf546f5c2a9adde3f72eaba39696f44c3e72660c4ffbedfe93d14
7
+ data.tar.gz: 424ace8fdd5c630d0767ac4d31a2f360c468563532b8c686d087e0e79105b1d94d26240ad6795bb00eb1ce43282d7bf22064b75828cea2716534ee6a434f197a
data/README.rdoc ADDED
@@ -0,0 +1,66 @@
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 identity_toolkit_ruby_client
8
+
9
+ === Examples
10
+
11
+ require 'gitkit_client'
12
+
13
+ # Create a server config file or download it from Google Developer Console
14
+ # The config file contains Gitkit library config in json format
15
+ # {
16
+ # "clientId" : "oauth2-web-client-id.apps.googleusercontent.com",
17
+ # "serviceAccountEmail" : "service-account-email@developer.gserviceaccount.com",
18
+ # "serviceAccountPrivateKeyFile" : "path-to-service-account-private-key-file.p12",
19
+ # "widgetUrl" : "full-url-of-gitkit-widget-on-your-site",
20
+ # "cookieName" : "gtoken",
21
+ # "serverKey" : "devconsole-server-key"
22
+ # }
23
+
24
+ # Create a Gitkit client
25
+ gitkit_client = GitkitLib::GitkitClient.create_from_config_file 'gitkit-server-config.json'
26
+
27
+ # Verify the Gitkit token in the incoming http request
28
+ token = request.cookies["gtoken"]
29
+ user = gitkit_client.verify_gitkit_token token
30
+
31
+ # Upload passwords
32
+ def calc_sha1(key, plain_text, salt)
33
+ hmac = OpenSSL::HMAC.new key, 'sha1'
34
+ hmac << plain_text
35
+ hmac << salt
36
+ hmac.digest
37
+ end
38
+
39
+ hash_key = 'hash-key'
40
+
41
+ user1 = GitkitLib::GitkitUser.new
42
+ user1.email = '1234@example.com'
43
+ user1.user_id = '1234'
44
+ user1.salt = 'salt-1'
45
+ user1.password_hash = calc_sha1(hash_key, '1111', 'salt-1')
46
+
47
+ user2 = GitkitLib::GitkitUser.new
48
+ user2.email = '5678@example.com'
49
+ user2.user_id = '5678'
50
+ user2.salt = 'salt-2'
51
+ user2.password_hash = calc_sha1(hash_key, '5555', 'salt-2')
52
+ user2.name = '56 78'
53
+
54
+ gitkit_client.upload_users 'HMAC_SHA1', hash_key, [user1, user2]
55
+
56
+ # Get user by email
57
+ user = gitkit_client.get_user_by_email('1234@example.com')
58
+
59
+ # Get user by id
60
+ user = gitkit_client.get_user_by_id('5678')
61
+
62
+ # Delete a user
63
+ gitkit_client.delete_user '5678'
64
+
65
+ # Download all accounts
66
+ gitkit_client.get_all_users(2) { |account| pp account}
@@ -0,0 +1,289 @@
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
+ key_finder = lambda {|header|
67
+ key_id = header['kid']
68
+ unless @certificates.has_key? key_id
69
+ @certificates = Hash[get_certs.map {|key,cert|
70
+ [key, OpenSSL::X509::Certificate.new(cert)]}]
71
+ end
72
+ @certificates[key_id].public_key }
73
+ parsed_token = JWT.decode(token_string, nil, true, &key_finder).first
74
+ # check expiration time
75
+ if Time.new.to_i > parsed_token['exp']
76
+ return nil, 'token expired'
77
+ end
78
+ # check audience
79
+ if parsed_token['aud'] != @client_id
80
+ return nil, 'audience mismatch'
81
+ end
82
+ GitkitUser.parse_from_api_response parsed_token
83
+ rescue JWT::DecodeError => e
84
+ return nil, e.message
85
+ end
86
+ end
87
+
88
+ # Gets Gitkit public certs
89
+ #
90
+ # @api private
91
+ def get_certs
92
+ @rpc_helper.get_gitkit_certs()
93
+ end
94
+
95
+ # Gets user info by email
96
+ #
97
+ # @param [String] email user email
98
+ # @return [GitkitUser] for the email
99
+ def get_user_by_email(email)
100
+ response = @rpc_helper.get_user_by_email email
101
+ GitkitUser.parse_from_api_response response.fetch('users', [{}])[0]
102
+ end
103
+
104
+ # Gets user info by user id
105
+ #
106
+ # @param [String] id user id
107
+ # @return [GitkitUser] for the id
108
+ def get_user_by_id(id)
109
+ response = @rpc_helper.get_user_by_id id
110
+ GitkitUser.parse_from_api_response response.fetch('users', [{}])[0]
111
+ end
112
+
113
+ # Downloads all user accounts from Gitkit service
114
+ #
115
+ # @param [Fixnum] max_results pagination size of each request
116
+ # @yield [GitkitUser] individual user account
117
+ def get_all_users(max_results = 10)
118
+ next_page_token = nil
119
+ while true
120
+ next_page_token, accounts = @rpc_helper.download_account(
121
+ next_page_token, max_results)
122
+ accounts.each { |account|
123
+ yield GitkitUser.parse_from_api_response account }
124
+ if not next_page_token or accounts.length == 0
125
+ break
126
+ end
127
+ end
128
+ end
129
+
130
+ # Uploads multiple accounts to Gitkit service
131
+ #
132
+ # @param [String] hash_algorithm password hash algorithm
133
+ # @param [String] hash_key key of the hash algorithm
134
+ # @param [Array<GitkitUser>] accounts user accounts to be uploaded
135
+ def upload_users(hash_algorithm, hash_key, accounts)
136
+ account_request = accounts.collect { |account| account.to_request }
137
+ @rpc_helper.upload_account hash_algorithm, JWT.base64url_encode(hash_key),
138
+ account_request
139
+ end
140
+
141
+ # Deletes a user account from Gitkit service
142
+ #
143
+ # @param [String] local_id user id to be deleted
144
+ def delete_user(local_id)
145
+ @rpc_helper.delete_account local_id
146
+ end
147
+
148
+ # Get one-time out-of-band code for ResetPassword/ChangeEmail request
149
+ #
150
+ # @param [String] full_url the full URL of incoming request
151
+ # @param [Hash{String=>String}] param dict of HTTP POST params
152
+ # @param [String] user_ip end user's IP address
153
+ # @param [String] gitkit_token the gitkit token if user logged in
154
+ #
155
+ # @return [Hash] {
156
+ # email: user email who is asking reset password
157
+ # oobLink: the generated link to be send to user's email
158
+ # action: OobAction
159
+ # response_body: the http body to be returned
160
+ # }
161
+ def get_oob_result(full_url, param, user_ip, gitkit_token=nil)
162
+ if param.has_key? 'action'
163
+ begin
164
+ if param['action'] == 'resetPassword'
165
+ oob_link = build_oob_link(full_url,
166
+ password_reset_request(param, user_ip),
167
+ param['action'])
168
+ return password_reset_response(oob_link, param)
169
+ elsif param['action'] == 'changeEmail'
170
+ unless gitkit_token
171
+ return failure_msg('login is required')
172
+ end
173
+ oob_link = build_oob_link(full_url,
174
+ change_email_request(param, user_ip, gitkit_token),
175
+ param['action'])
176
+ return email_change_response(oob_link, param)
177
+ end
178
+ rescue GitkitClientError => error
179
+ return failure_msg(error.message)
180
+ end
181
+ end
182
+ failure_msg('unknown request type')
183
+ end
184
+
185
+ def password_reset_request(param, user_ip)
186
+ {
187
+ 'email' => param['email'],
188
+ 'userIp' => user_ip,
189
+ 'challenge' => param['challenge'],
190
+ 'captchaResp' => param['response'],
191
+ 'requestType' => 'PASSWORD_RESET'
192
+ }
193
+ end
194
+
195
+ def change_email_request(param, user_ip, gitkit_token)
196
+ {
197
+ 'email' => param['oldEmail'],
198
+ 'newEmail' => param['newEmail'],
199
+ 'userIp' => user_ip,
200
+ 'idToken' => gitkit_token,
201
+ 'requestType' => 'NEW_EMAIL_ACCEPT'
202
+ }
203
+ end
204
+
205
+ def build_oob_link(full_url, param, mode)
206
+ code = @rpc_helper.get_oob_code(param)
207
+ if code
208
+ oob_link = Addressable::URI.parse full_url
209
+ oob_link.path = @widget_url
210
+ oob_link.query_values = { 'mode' => mode, 'oobCode' => code}
211
+ return oob_link.to_s
212
+ end
213
+ nil
214
+ end
215
+
216
+ def failure_msg(msg)
217
+ {:response_body => {'error' => msg}}.to_json
218
+ end
219
+
220
+ def email_change_response(oob_link, param)
221
+ {
222
+ :oldEmail => param['email'],
223
+ :newEmail => param['newEmail'],
224
+ :oobLink => oob_link,
225
+ :action => :CHANGE_EMAIL,
226
+ :response_body => {'success' => true}.to_json
227
+ }
228
+ end
229
+
230
+ def password_reset_response(oob_link, param)
231
+ {
232
+ :email => param['email'],
233
+ :oobLink => oob_link,
234
+ :action => :RESET_PASSWORD,
235
+ :response_body => {'success' => true}.to_json
236
+ }
237
+ end
238
+ end
239
+
240
+ class GitkitUser
241
+ attr_accessor :email, :user_id, :name, :photo_url, :provider_id,
242
+ :email_verified, :password_hash, :salt, :password, :provider_info
243
+
244
+ def self.parse_from_api_response(api_response)
245
+ user = self.new
246
+ user.email = api_response.fetch('email', nil)
247
+ user.user_id = api_response.fetch('user_id',
248
+ api_response.fetch('localId', nil))
249
+ user.name = api_response.fetch('displayName', nil)
250
+ user.photo_url = api_response.fetch('photoUrl', nil)
251
+ user.provider_id = api_response.fetch('provider_id',
252
+ api_response.fetch('providerId', nil))
253
+ user.email_verified = api_response.fetch('emailVerified',
254
+ api_response.fetch('verified', nil))
255
+ user.password_hash = api_response.fetch('passwordHash', nil)
256
+ user.salt = api_response.fetch('salt', nil)
257
+ user.password = api_response.fetch('password', nil)
258
+ user.provider_info = api_response.fetch('providerUserInfo', {})
259
+ user
260
+ end
261
+
262
+ # Convert to gitkit api request (a dict)
263
+ def to_request
264
+ request = {}
265
+ request['email'] = @email if @email
266
+ request['localId'] = @user_id if @user_id
267
+ request['displayName'] = @name if @name
268
+ request['photoUrl'] = @photo_url if @photo_url
269
+ request['emailVerified'] = @email_verified if @email_verified != nil
270
+ request['passwordHash'] =
271
+ JWT.base64url_encode @password_hash if @password_hash
272
+ request['salt'] = JWT.base64url_encode @salt if @salt
273
+ request['providerUserInfo'] = @provider_info if @provider_info != nil
274
+ request
275
+ end
276
+ end
277
+
278
+ class GitkitClientError < StandardError
279
+ def initialize(message)
280
+ super
281
+ end
282
+ end
283
+
284
+ class GitkitServerError < StandardError
285
+ def initialize(message)
286
+ super
287
+ end
288
+ end
289
+ end
data/lib/rpc_helper.rb ADDED
@@ -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,103 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: identity-toolkit-ruby-client
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Jin Liu
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-09-23 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: addressable
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 2.3.2
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: 2.3.2
27
+ - !ruby/object:Gem::Dependency
28
+ name: faraday
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 0.9.0
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: 0.9.0
41
+ - !ruby/object:Gem::Dependency
42
+ name: multi_json
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: 1.0.0
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: 1.0.0
55
+ - !ruby/object:Gem::Dependency
56
+ name: jwt
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: 1.0.0
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: 1.0.0
69
+ description: Google Identity Toolkit Ruby client library
70
+ email:
71
+ executables: []
72
+ extensions: []
73
+ extra_rdoc_files:
74
+ - README.rdoc
75
+ files:
76
+ - README.rdoc
77
+ - lib/gitkit_client.rb
78
+ - lib/rpc_helper.rb
79
+ homepage: https://developers.google.com/identity-toolkit/v3
80
+ licenses:
81
+ - Apache 2.0
82
+ metadata: {}
83
+ post_install_message:
84
+ rdoc_options: []
85
+ require_paths:
86
+ - lib
87
+ required_ruby_version: !ruby/object:Gem::Requirement
88
+ requirements:
89
+ - - ">="
90
+ - !ruby/object:Gem::Version
91
+ version: '0'
92
+ required_rubygems_version: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ requirements: []
98
+ rubyforge_project:
99
+ rubygems_version: 2.2.2
100
+ signing_key:
101
+ specification_version: 4
102
+ summary: Google Identity Toolkit Ruby client
103
+ test_files: []