gitkit-ruby 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +4 -0
- data/Gemfile.lock +38 -0
- data/Rakefile +32 -0
- data/VERSION +1 -0
- data/gitkit-ruby.gemspec +31 -0
- data/lib/google/identity_toolkit.rb +298 -0
- data/lib/google/identity_toolkit/api.rb +53 -0
- data/lib/google/identity_toolkit/errors.rb +11 -0
- data/lib/google/identity_toolkit/helpers.rb +64 -0
- data/lib/google/identity_toolkit/version.rb +5 -0
- data/tasks/test.rake +14 -0
- data/tasks/yard.rake +9 -0
- data/test/gitkit_test.rb +115 -0
- metadata +153 -0
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
gitkit-ruby (0.0.1)
|
5
|
+
addressable (~> 2.2)
|
6
|
+
httparty (~> 0.8)
|
7
|
+
json (~> 1.4.6)
|
8
|
+
rack (~> 1.3)
|
9
|
+
|
10
|
+
GEM
|
11
|
+
remote: http://rubygems.org/
|
12
|
+
specs:
|
13
|
+
addressable (2.2.6)
|
14
|
+
hoe (2.12.4)
|
15
|
+
rake (~> 0.8)
|
16
|
+
httparty (0.8.1)
|
17
|
+
multi_json
|
18
|
+
multi_xml
|
19
|
+
json (1.4.6)
|
20
|
+
multi_json (1.0.3)
|
21
|
+
multi_xml (0.4.1)
|
22
|
+
rack (1.3.4)
|
23
|
+
rack-test (0.6.1)
|
24
|
+
rack (>= 1.0)
|
25
|
+
rake (0.9.2)
|
26
|
+
rr (1.0.4)
|
27
|
+
test-unit (1.2.3)
|
28
|
+
hoe (>= 1.5.1)
|
29
|
+
|
30
|
+
PLATFORMS
|
31
|
+
ruby
|
32
|
+
|
33
|
+
DEPENDENCIES
|
34
|
+
gitkit-ruby!
|
35
|
+
rack-test (~> 0.6)
|
36
|
+
rake (~> 0.9)
|
37
|
+
rr (~> 1.0)
|
38
|
+
test-unit (~> 1.2)
|
data/Rakefile
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
require 'bundler'
|
4
|
+
|
5
|
+
Bundler::GemHelper.install_tasks
|
6
|
+
|
7
|
+
FileList['tasks/**/*.rake'].each { |task| import task }
|
8
|
+
|
9
|
+
"""
|
10
|
+
require 'bundle/gem_tasks'
|
11
|
+
begin
|
12
|
+
gem 'jeweler', '~> 1.6.4'
|
13
|
+
require 'jeweler'
|
14
|
+
|
15
|
+
Jeweler::Tasks.new do |gem|
|
16
|
+
gem.name = 'gitkit-ruby'
|
17
|
+
gem.summary = 'Rack middleware for Google Identity Toolkit'
|
18
|
+
gem.description = gem.summary
|
19
|
+
gem.email = 'sqrrrl@gmail.com'
|
20
|
+
gem.homepage = 'http://code.google.com/p/%s' % gem.name
|
21
|
+
gem.authors = [ 'Steve Bazyl' ]
|
22
|
+
|
23
|
+
#gem.rubyforge_project = 'gitkit-ruby'
|
24
|
+
end
|
25
|
+
|
26
|
+
Jeweler::GemcutterTasks.new
|
27
|
+
|
28
|
+
rescue LoadError
|
29
|
+
puts 'Jeweler (or a dependency) not available. Install it with: gem install jeweler -v 1.6.4'
|
30
|
+
end
|
31
|
+
"""
|
32
|
+
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.0
|
data/gitkit-ruby.gemspec
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "google/identity_toolkit/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "gitkit-ruby"
|
7
|
+
s.version = Google::IdentityToolkit::VERSION
|
8
|
+
s.authors = ["Steven Bazyl"]
|
9
|
+
s.email = ["sqrrrl@gmail.com"]
|
10
|
+
s.homepage = ""
|
11
|
+
s.summary = %q{Rack middleware for Google Identity Toolkit}
|
12
|
+
s.description = %q{Rack middleware for Google Identity Toolkit}
|
13
|
+
|
14
|
+
s.rubyforge_project = "gitkit-ruby"
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
|
21
|
+
s.add_dependency 'rack','~> 1.3'
|
22
|
+
s.add_dependency 'json', '~> 1.4.6'
|
23
|
+
s.add_dependency 'httparty', '~> 0.8'
|
24
|
+
s.add_dependency 'addressable', '~> 2.2'
|
25
|
+
|
26
|
+
s.add_development_dependency 'rake', '~> 0.9'
|
27
|
+
s.add_development_dependency 'rack-test', '~> 0.6'
|
28
|
+
s.add_development_dependency 'test-unit', '~> 1.2'
|
29
|
+
s.add_development_dependency 'rr', '~> 1.0'
|
30
|
+
|
31
|
+
end
|
@@ -0,0 +1,298 @@
|
|
1
|
+
require "rack"
|
2
|
+
require "json"
|
3
|
+
require "google/identity_toolkit/api"
|
4
|
+
require "google/identity_toolkit/helpers"
|
5
|
+
require "addressable/uri"
|
6
|
+
|
7
|
+
module Google
|
8
|
+
CONTENT_TYPE = 'Content-Type'
|
9
|
+
CONTENT_TYPE_HTML = 'text/html'
|
10
|
+
CONTENT_TYPE_JSON = 'application/json'
|
11
|
+
STATUS_SUCCESS = 'success'
|
12
|
+
STATUS_INVALID_EMAIL = 'invalidAssertionEmail'
|
13
|
+
STATUS_ACCOUNT_MISMATCH = 'accountMismatch'
|
14
|
+
|
15
|
+
##
|
16
|
+
# Rack middleware for using the Google Identity Kit for federated login
|
17
|
+
# @see http://code.google.com/apis/identitytoolkit/
|
18
|
+
class IdentityToolkit
|
19
|
+
attr_accessor :api_key
|
20
|
+
attr_accessor :path
|
21
|
+
attr_accessor :app
|
22
|
+
attr_accessor :api
|
23
|
+
attr_accessor :callback_url
|
24
|
+
attr_accessor :federated_signup_url
|
25
|
+
attr_accessor :signup_url
|
26
|
+
attr_accessor :home_url
|
27
|
+
|
28
|
+
##
|
29
|
+
# Creates the Identity toolkit middleware. Requires defining a block
|
30
|
+
# to configure the API and define callback methods for loading & authenticating
|
31
|
+
# users.
|
32
|
+
#
|
33
|
+
# @example
|
34
|
+
# use Google::IdentityToolkit, do |toolkit|
|
35
|
+
# toolkit.api_key = "..."
|
36
|
+
# def fetch_user(email, assertion = nil)
|
37
|
+
# # Load the user for the given email, optionally registering
|
38
|
+
# # the user if assertion is provided and '
|
39
|
+
# end
|
40
|
+
#
|
41
|
+
# def password_valid?(email, password)
|
42
|
+
# # Validates the user id/password
|
43
|
+
# end
|
44
|
+
# end
|
45
|
+
def initialize(app, &block)
|
46
|
+
@path = '/_gitkit'
|
47
|
+
@callback_url = nil
|
48
|
+
@app = app
|
49
|
+
instance_eval(&block) if block_given?
|
50
|
+
end
|
51
|
+
|
52
|
+
##
|
53
|
+
# Static method for fetching the API key from thread-local storage. Only valid for threads
|
54
|
+
# intercepted by the rack module. Use by helper methods rendering the login button.
|
55
|
+
#
|
56
|
+
# @return [String] API Key
|
57
|
+
def self.api_key
|
58
|
+
env[:gitkit_api_key]
|
59
|
+
end
|
60
|
+
|
61
|
+
##
|
62
|
+
# Static method for fetching the current user from thread-local storage. Only valid for threads
|
63
|
+
# intercepted by the rack module. Use by helper methods rendering the login button.
|
64
|
+
#
|
65
|
+
# @return [Hash] Hash representing the user
|
66
|
+
def self.current_user
|
67
|
+
env[:gitkit_user]
|
68
|
+
end
|
69
|
+
|
70
|
+
##
|
71
|
+
# Static method for fetching the callback URL from thread-local storage. Only valid for threads
|
72
|
+
# intercepted by the rack module. Use by helper methods rendering the login button.
|
73
|
+
#
|
74
|
+
# @return [Hash] Hash representing the user
|
75
|
+
def self.callback_url
|
76
|
+
env[:gitkit_callback_url]
|
77
|
+
end
|
78
|
+
|
79
|
+
##
|
80
|
+
# Get (and lazy init) the API client
|
81
|
+
#
|
82
|
+
# @return [Google::IdentityToolkit::Api]
|
83
|
+
# API client
|
84
|
+
def api
|
85
|
+
@api ||= Google::IdentityToolkit::Api.new(@api_key)
|
86
|
+
end
|
87
|
+
|
88
|
+
##
|
89
|
+
# Handles web requests for identity toolkit callbacks
|
90
|
+
#
|
91
|
+
# @param [Hash] env
|
92
|
+
# Rack request context
|
93
|
+
# @return [Array]
|
94
|
+
# HTTP response
|
95
|
+
def call(env)
|
96
|
+
request = Rack::Request.new(env)
|
97
|
+
env[:gitkit_api_key] = @api_key
|
98
|
+
env[:gitkit_callback_url] = callback_url(request)
|
99
|
+
env[:gitkit_user] = request.session[:git_user]
|
100
|
+
Thread.current[:gitkit_rack_env] = env
|
101
|
+
begin
|
102
|
+
return @app.call(env) unless request.path == @path
|
103
|
+
case request.params['rp_target']
|
104
|
+
when "callback"
|
105
|
+
body = callback(request)
|
106
|
+
[200, {CONTENT_TYPE => CONTENT_TYPE_HTML}, [body]]
|
107
|
+
when "login"
|
108
|
+
data = login(request)
|
109
|
+
[200, {CONTENT_TYPE => CONTENT_TYPE_JSON}, [data.to_json]]
|
110
|
+
when "userStatus"
|
111
|
+
data = status(request)
|
112
|
+
[200, {CONTENT_TYPE => CONTENT_TYPE_JSON}, [data.to_json]]
|
113
|
+
end
|
114
|
+
ensure
|
115
|
+
Thread.current[:gitkit_rack_env] = nil
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
##
|
120
|
+
# Handles IDP responses during login
|
121
|
+
#
|
122
|
+
# @param [Rack::Request] request
|
123
|
+
# current request
|
124
|
+
# @return [String]
|
125
|
+
# HTML boilerplate to close popup & notify parent window
|
126
|
+
def callback(request)
|
127
|
+
render_method = !!request.params['mobile'] ? :build_mobile_html : :build_notify_html
|
128
|
+
begin
|
129
|
+
assertion = api.verify_assertion(request.url, request.body)
|
130
|
+
input_email = request.params['rp_input_email']
|
131
|
+
email = assertion['verifiedEmail']
|
132
|
+
return send(render_method, STATUS_INVALID_EMAIL, {}) if email.nil?
|
133
|
+
return send(render_method, STATUS_ACCOUNT_MISMATCH, {
|
134
|
+
"inputEmail" => input_email,
|
135
|
+
"validatedEmail" => email
|
136
|
+
}) unless input_email.nil? or input_email == email
|
137
|
+
|
138
|
+
user = fetch_user(email, assertion)
|
139
|
+
if user.nil?
|
140
|
+
# Save the assertion for use in signup page
|
141
|
+
request.session[:gitkit_assertion] = assertion unless session.nil?
|
142
|
+
send(render_method, STATUS_SUCCESS, {
|
143
|
+
"email" => email,
|
144
|
+
"registered" => false,
|
145
|
+
"displayName" => assertion["displayName"],
|
146
|
+
"photoUrl" => assertion["photoUrl"]
|
147
|
+
})
|
148
|
+
else
|
149
|
+
# User exits, login
|
150
|
+
upgrade_user(email)
|
151
|
+
handle_login(user)
|
152
|
+
send(render_method, STATUS_SUCCESS, {
|
153
|
+
"email" => email,
|
154
|
+
"registered" => true,
|
155
|
+
"displayName" => assertion["displayName"],
|
156
|
+
"photoUrl" => assertion["photoUrl"]
|
157
|
+
})
|
158
|
+
end
|
159
|
+
rescue Exception => e
|
160
|
+
send(render_method, 'invalidAssertion', {})
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
##
|
165
|
+
# Handles authentication requests for non-federated users
|
166
|
+
#
|
167
|
+
# @param [Rack::Request] request
|
168
|
+
# current request
|
169
|
+
# @return [Hash]
|
170
|
+
# Result of login. Currently just sets the value of :status in the hash
|
171
|
+
def login(request)
|
172
|
+
user = fetch_user(request.params["email"])
|
173
|
+
if user.nil?
|
174
|
+
{:status => "emailNotExist"}
|
175
|
+
elsif user[:federated]
|
176
|
+
{:status => "federated"}
|
177
|
+
else
|
178
|
+
if password_valid?(request.params["email"], request.params["password"])
|
179
|
+
user = fetch_user(request.params["email"])
|
180
|
+
handle_login(user)
|
181
|
+
{:status => "OK"}
|
182
|
+
else
|
183
|
+
{:status => "passwordError"}
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
##
|
189
|
+
# Checks the status of a user
|
190
|
+
#
|
191
|
+
# @param [Rack::Request] request
|
192
|
+
# current request
|
193
|
+
# @return [Hash]
|
194
|
+
# Status of user
|
195
|
+
def status(request)
|
196
|
+
user = fetch_user(request.params["email"])
|
197
|
+
#referrer = request.params["referrer"]
|
198
|
+
{ "registered" => !!user,
|
199
|
+
"legacy" => !(user && user[:federated]) }
|
200
|
+
end
|
201
|
+
|
202
|
+
##
|
203
|
+
# Log the user after either a successful assertion or password validation
|
204
|
+
# @param [Hash]
|
205
|
+
# User (from find_user)
|
206
|
+
# @param [Rack::Request] request
|
207
|
+
# Current request
|
208
|
+
def handle_login(user)
|
209
|
+
unless session.nil?
|
210
|
+
request.session[:git_user] = user
|
211
|
+
on_login(user)
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
##
|
216
|
+
# Renders boilerplate HTML for closing popups after login
|
217
|
+
#
|
218
|
+
# @param [String] status
|
219
|
+
# Status of callback -- either success, or one of the gitkit supported error messages
|
220
|
+
# @param [Hash] data
|
221
|
+
# user data from assertion if successful login
|
222
|
+
# @return [String] HTML
|
223
|
+
def build_notify_html(status, data)
|
224
|
+
html = <<-EOF
|
225
|
+
<script type='text/javascript' src='https://ajax.googleapis.com/jsapi'></script>
|
226
|
+
<script type='text/javascript'>google.load("identitytoolkit", "1.0", {packages: ["notify"]});</script>
|
227
|
+
<script type='text/javascript'>
|
228
|
+
EOF
|
229
|
+
case status
|
230
|
+
when STATUS_SUCCESS
|
231
|
+
html << "window.google.identitytoolkit.notifyFederatedSuccess(#{data.to_json});"
|
232
|
+
else
|
233
|
+
html << "window.google.identitytoolkit.notifyFederatedError('#{status}', #{data.to_json});"
|
234
|
+
end
|
235
|
+
html << "</script>"
|
236
|
+
end
|
237
|
+
|
238
|
+
##
|
239
|
+
# Renders boilerplate HTML for non-poup mobile UI
|
240
|
+
#
|
241
|
+
# @param [String] status
|
242
|
+
# Status of callback -- either success, or one of the gitkit supported error messages
|
243
|
+
# @param [Hash] data
|
244
|
+
# user data from assertion if successful login
|
245
|
+
# @return [String] HTML
|
246
|
+
def build_mobile_html(status, data)
|
247
|
+
html = "<script type='text/javascript'>"
|
248
|
+
if status == STATUS_SUCCESS
|
249
|
+
if data["registered"]
|
250
|
+
html << "window.location = '#{home_url}';"
|
251
|
+
else
|
252
|
+
target = Addressable::URI.parse(signup_url)
|
253
|
+
target.query_values = target.query_values.merge("email" => data["email"])
|
254
|
+
html << "window.location = '#{target}';"
|
255
|
+
end
|
256
|
+
else
|
257
|
+
target = Addressable::URI.parse(login_url)
|
258
|
+
target.query_values = target.query_values.merge("error" => status)
|
259
|
+
html << "window.location = '#{login_url}';"
|
260
|
+
end
|
261
|
+
html << "</script>"
|
262
|
+
end
|
263
|
+
|
264
|
+
# Get the callback URL for use in the javascript helpers.
|
265
|
+
#
|
266
|
+
# @param [Rack::Request] request
|
267
|
+
# current request object
|
268
|
+
# @return [String]
|
269
|
+
# callback URL for verifying assertions
|
270
|
+
def callback_url(request = nil)
|
271
|
+
return @callback_url unless @callback_url.nil?
|
272
|
+
scheme = request.scheme
|
273
|
+
if (scheme == 'http' && request.port == 80 ||
|
274
|
+
scheme == 'https' && request.port == 443)
|
275
|
+
port = ""
|
276
|
+
else
|
277
|
+
port = ":#{request.port}"
|
278
|
+
end
|
279
|
+
"#{scheme}://#{request.host}#{port}#{request.script_name}#{@path}"
|
280
|
+
end
|
281
|
+
|
282
|
+
def self.env
|
283
|
+
Thread.current[:gitkit_rack_env]
|
284
|
+
end
|
285
|
+
|
286
|
+
def env
|
287
|
+
Thread.current[:gitkit_rack_env]
|
288
|
+
end
|
289
|
+
|
290
|
+
def request
|
291
|
+
Rack::Request.new(env)
|
292
|
+
end
|
293
|
+
|
294
|
+
def session
|
295
|
+
request.session
|
296
|
+
end
|
297
|
+
end
|
298
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'httparty'
|
2
|
+
require 'google/identity_toolkit/errors'
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
module Google
|
6
|
+
class IdentityToolkit
|
7
|
+
|
8
|
+
##
|
9
|
+
# Server API for creating login requests & verifying assertions
|
10
|
+
class Api
|
11
|
+
include HTTParty
|
12
|
+
format :json
|
13
|
+
headers "Content-Type" => "application/json"
|
14
|
+
|
15
|
+
##
|
16
|
+
# Initializes the API
|
17
|
+
#
|
18
|
+
# @param [String] api_key
|
19
|
+
# API key from developer console
|
20
|
+
def initialize(api_key)
|
21
|
+
@api_key = api_key
|
22
|
+
end
|
23
|
+
|
24
|
+
##
|
25
|
+
# Sends an IDP response for verification, returning an assertion
|
26
|
+
# if the response is valid
|
27
|
+
#
|
28
|
+
# @param [String] request_uri
|
29
|
+
# URL of the current request
|
30
|
+
# @param [StringIO] body
|
31
|
+
# Request body (if post)
|
32
|
+
#
|
33
|
+
# @return [Hash]
|
34
|
+
# Assertion of user identity
|
35
|
+
#
|
36
|
+
# @raise [ApiError]
|
37
|
+
# if assertion not valid
|
38
|
+
def verify_assertion(request_uri, body=nil)
|
39
|
+
request = {
|
40
|
+
"requestUri" => request_uri
|
41
|
+
}
|
42
|
+
request["postBody"] = body.gets unless body.nil?
|
43
|
+
response = Api.post('https://www.googleapis.com/identitytoolkit/v1/relyingparty/verifyAssertion',
|
44
|
+
:query => { "key" => @api_key }, :body => request.to_json )
|
45
|
+
raise ApiError.new("Error #{response.code} when verifying the assertion") unless response.code == 200
|
46
|
+
response.parsed_response
|
47
|
+
end
|
48
|
+
|
49
|
+
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'addressable/uri'
|
2
|
+
|
3
|
+
module Google
|
4
|
+
class IdentityToolkit
|
5
|
+
module Helpers
|
6
|
+
|
7
|
+
##
|
8
|
+
# Generate javascript for configuring the identity toolkit button
|
9
|
+
#
|
10
|
+
# @param [String] element_id
|
11
|
+
# Id of element that will contain the login button
|
12
|
+
# @param [Hash] options
|
13
|
+
# Configuration options
|
14
|
+
# @option options [String] :api_key
|
15
|
+
# APIKey from developer console
|
16
|
+
# @option options [String] :company_name
|
17
|
+
# Name of site to display on login pages
|
18
|
+
# @option options [String] :base_url
|
19
|
+
#
|
20
|
+
# @return [String] Block of javascript (must be wrapped in <script> tag)
|
21
|
+
|
22
|
+
def account_chooser(element_id, options = {})
|
23
|
+
callback_url = Addressable::URI.parse(options[:callback_url] || Google::IdentityToolkit.callback_url)
|
24
|
+
params = callback_url.query_values || {}
|
25
|
+
callback_url.query_values = params.merge("mobile" => "1") if options[:mobile]
|
26
|
+
js = <<-EOF
|
27
|
+
window.google.identitytoolkit.setConfig({
|
28
|
+
developerKey: "#{options[:api_key] || Google::IdentityToolkit.api_key}",
|
29
|
+
companyName: "#{options[:site_name]}",
|
30
|
+
callbackUrl: "#{callback_url}",
|
31
|
+
userStatusUrl: "#{callback_url}",
|
32
|
+
loginUrl: "#{callback_url}",
|
33
|
+
federatedSignupUrl: "#{options[:federated_signup_url] || options[:signup_url] || '/signup' }",
|
34
|
+
signupUrl: "#{options[:signup_url] || '/signup'}",
|
35
|
+
homeUrl: "#{options[:home_url] || '/' }",
|
36
|
+
logoutUrl: "#{options[:logout_url] || '/logout'}",
|
37
|
+
realm: "#{options[:realm]}",
|
38
|
+
language: "#{options[:language] || 'en'}",
|
39
|
+
idps: ["Gmail", "AOL", "Hotmail", "Yahoo"],
|
40
|
+
tryFederatedFirst: #{options[:try_federated_first] || false},
|
41
|
+
useCachedUserStatus: #{options[:use_cached_status] || false}
|
42
|
+
});
|
43
|
+
$('#{element_id}').accountChooser();
|
44
|
+
EOF
|
45
|
+
end
|
46
|
+
|
47
|
+
def show_current_user(options={})
|
48
|
+
js = ""
|
49
|
+
user = options[:user] || Google::IdentityToolkit.current_user
|
50
|
+
if user
|
51
|
+
js = <<-EOF
|
52
|
+
window.google.identitytoolkit.updateSavedAccount({
|
53
|
+
email: '#{user[:email]}',
|
54
|
+
displayName: '#{user[:display_name]}',
|
55
|
+
photoUrl: '#{user[:photo_url]}'
|
56
|
+
});
|
57
|
+
window.google.identitytoolkit.showSavedAccount('#{user[:email]}');
|
58
|
+
EOF
|
59
|
+
end
|
60
|
+
js
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
data/tasks/test.rake
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
begin
|
2
|
+
require 'rake/testtask'
|
3
|
+
|
4
|
+
desc "Run basic tests"
|
5
|
+
Rake::TestTask.new("test") { |t|
|
6
|
+
t.pattern = 'test/*_test.rb'
|
7
|
+
t.verbose = true
|
8
|
+
t.warning = true
|
9
|
+
}
|
10
|
+
rescue LoadError
|
11
|
+
task :yard do
|
12
|
+
abort 'testunit is not available. In order to run yard, you must: gem install testunit'
|
13
|
+
end
|
14
|
+
end
|
data/tasks/yard.rake
ADDED
data/test/gitkit_test.rb
ADDED
@@ -0,0 +1,115 @@
|
|
1
|
+
$: << (File.dirname(__FILE__) + '/../lib')
|
2
|
+
|
3
|
+
require "rr"
|
4
|
+
require "test/unit"
|
5
|
+
require 'rack/test'
|
6
|
+
require "google/identity_toolkit"
|
7
|
+
|
8
|
+
module MockCallbacks
|
9
|
+
def fetch_user(email, assertion = nil)
|
10
|
+
return {"verifiedEmail" => email} if email.start_with?("exist") || email.start_with?("unregistered")
|
11
|
+
end
|
12
|
+
|
13
|
+
def password_valid?(email, password)
|
14
|
+
"ok" == password
|
15
|
+
end
|
16
|
+
|
17
|
+
def upgrade_user(user)
|
18
|
+
end
|
19
|
+
|
20
|
+
def on_login(user)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class GITKit_Test < Test::Unit::TestCase
|
25
|
+
include Rack::Test::Methods
|
26
|
+
include RR::Adapters::TestUnit
|
27
|
+
|
28
|
+
attr_accessor :app
|
29
|
+
|
30
|
+
def setup
|
31
|
+
inner_app = lambda { |env| [200, {'Content-Type' => 'text/plain'}, ['hello']] }
|
32
|
+
@app = Google::IdentityToolkit.new(inner_app) do |git|
|
33
|
+
git.api_key = "AIzaSyBs1gLwB1_QZ-EXgFqBrrLY1MPXzB3G6ZU"
|
34
|
+
extend MockCallbacks
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def teardown
|
39
|
+
# Do nothing
|
40
|
+
end
|
41
|
+
|
42
|
+
def test_passthrough
|
43
|
+
get '/test'
|
44
|
+
assert_equal 'hello', last_response.body
|
45
|
+
end
|
46
|
+
|
47
|
+
def test_valid_login
|
48
|
+
post '/_gitkit', "rp_target" => "login", "email" => "exists@bar.com", "password" => "ok"
|
49
|
+
data = JSON.parse(last_response.body)
|
50
|
+
assert_equal("OK", data["status"])
|
51
|
+
end
|
52
|
+
|
53
|
+
def test_invalid_login
|
54
|
+
post '/_gitkit', "rp_target" => "login", "email" => "exists@bar.com", "password" => "badpassword"
|
55
|
+
data = JSON.parse(last_response.body)
|
56
|
+
assert_equal("passwordError", data["status"])
|
57
|
+
end
|
58
|
+
|
59
|
+
def test_user_exists
|
60
|
+
post '/_gitkit', "rp_target" => "userStatus", "email" => "exists@foo.com"
|
61
|
+
data = JSON.parse(last_response.body)
|
62
|
+
assert_equal(true, data["registered"])
|
63
|
+
end
|
64
|
+
|
65
|
+
def test_user_not_exists
|
66
|
+
post '/_gitkit', "rp_target" => "userStatus", "email" => "nobody@foo.com"
|
67
|
+
data = JSON.parse(last_response.body)
|
68
|
+
assert_equal(false, data["registered"])
|
69
|
+
end
|
70
|
+
|
71
|
+
def test_callback_registered_user
|
72
|
+
mock(@app.api).verify_assertion(is_a(String), anything) {
|
73
|
+
{ "kind" => "identitytoolkit#relyingparty",
|
74
|
+
"identifier" => "12345",
|
75
|
+
"authority" => "google.com",
|
76
|
+
"verifiedEmail" => "existing@foo.com",
|
77
|
+
"firstName" => "Test",
|
78
|
+
"lastName" => "User",
|
79
|
+
"fullName" => "Test User",
|
80
|
+
"nickName" => "test",
|
81
|
+
"language" => "en",
|
82
|
+
"timeZone" => "PST" }
|
83
|
+
}
|
84
|
+
post '/_gitkit?rp_target=callback', "some long assertion...."
|
85
|
+
assert_equal(200, last_response.status)
|
86
|
+
assert_match(/.*"registered":true.*/, last_response.body)
|
87
|
+
end
|
88
|
+
|
89
|
+
def test_callback_unregistered_user
|
90
|
+
mock(@app.api).verify_assertion(is_a(String), anything) {
|
91
|
+
{ "kind" => "identitytoolkit#relyingparty",
|
92
|
+
"identifier" => "12345",
|
93
|
+
"authority" => "google.com",
|
94
|
+
"verifiedEmail" => "new@foo.com",
|
95
|
+
"firstName" => "Test",
|
96
|
+
"lastName" => "User",
|
97
|
+
"fullName" => "Test User",
|
98
|
+
"nickName" => "test",
|
99
|
+
"language" => "en",
|
100
|
+
"timeZone" => "PST" }
|
101
|
+
}
|
102
|
+
post '/_gitkit?rp_target=callback', "some long assertion..."
|
103
|
+
assert_equal(200, last_response.status)
|
104
|
+
assert_match(/.*"registered":false.*/, last_response.body)
|
105
|
+
end
|
106
|
+
|
107
|
+
def test_callback_invalid_response
|
108
|
+
mock(@app.api).verify_assertion(is_a(String), anything) {
|
109
|
+
raise ApiError("Invalid assertion")
|
110
|
+
}
|
111
|
+
post '/_gitkit?rp_target=callback', "garbage"
|
112
|
+
assert_equal(200, last_response.status)
|
113
|
+
assert_match(/.*notifyFederatedError.*/, last_response.body)
|
114
|
+
end
|
115
|
+
end
|
metadata
ADDED
@@ -0,0 +1,153 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: gitkit-ruby
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Steven Bazyl
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2011-12-16 00:00:00.000000000Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rack
|
16
|
+
requirement: &70358498379620 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '1.3'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70358498379620
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: json
|
27
|
+
requirement: &70358498378900 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ~>
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 1.4.6
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *70358498378900
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: httparty
|
38
|
+
requirement: &70358498377960 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ~>
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0.8'
|
44
|
+
type: :runtime
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *70358498377960
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: addressable
|
49
|
+
requirement: &70358498376820 !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ~>
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '2.2'
|
55
|
+
type: :runtime
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: *70358498376820
|
58
|
+
- !ruby/object:Gem::Dependency
|
59
|
+
name: rake
|
60
|
+
requirement: &70358498375600 !ruby/object:Gem::Requirement
|
61
|
+
none: false
|
62
|
+
requirements:
|
63
|
+
- - ~>
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: '0.9'
|
66
|
+
type: :development
|
67
|
+
prerelease: false
|
68
|
+
version_requirements: *70358498375600
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rack-test
|
71
|
+
requirement: &70358498373840 !ruby/object:Gem::Requirement
|
72
|
+
none: false
|
73
|
+
requirements:
|
74
|
+
- - ~>
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '0.6'
|
77
|
+
type: :development
|
78
|
+
prerelease: false
|
79
|
+
version_requirements: *70358498373840
|
80
|
+
- !ruby/object:Gem::Dependency
|
81
|
+
name: test-unit
|
82
|
+
requirement: &70358498371760 !ruby/object:Gem::Requirement
|
83
|
+
none: false
|
84
|
+
requirements:
|
85
|
+
- - ~>
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
version: '1.2'
|
88
|
+
type: :development
|
89
|
+
prerelease: false
|
90
|
+
version_requirements: *70358498371760
|
91
|
+
- !ruby/object:Gem::Dependency
|
92
|
+
name: rr
|
93
|
+
requirement: &70358498370780 !ruby/object:Gem::Requirement
|
94
|
+
none: false
|
95
|
+
requirements:
|
96
|
+
- - ~>
|
97
|
+
- !ruby/object:Gem::Version
|
98
|
+
version: '1.0'
|
99
|
+
type: :development
|
100
|
+
prerelease: false
|
101
|
+
version_requirements: *70358498370780
|
102
|
+
description: Rack middleware for Google Identity Toolkit
|
103
|
+
email:
|
104
|
+
- sqrrrl@gmail.com
|
105
|
+
executables: []
|
106
|
+
extensions: []
|
107
|
+
extra_rdoc_files: []
|
108
|
+
files:
|
109
|
+
- Gemfile
|
110
|
+
- Gemfile.lock
|
111
|
+
- Rakefile
|
112
|
+
- VERSION
|
113
|
+
- gitkit-ruby.gemspec
|
114
|
+
- lib/google/identity_toolkit.rb
|
115
|
+
- lib/google/identity_toolkit/api.rb
|
116
|
+
- lib/google/identity_toolkit/errors.rb
|
117
|
+
- lib/google/identity_toolkit/helpers.rb
|
118
|
+
- lib/google/identity_toolkit/version.rb
|
119
|
+
- tasks/test.rake
|
120
|
+
- tasks/yard.rake
|
121
|
+
- test/gitkit_test.rb
|
122
|
+
homepage: ''
|
123
|
+
licenses: []
|
124
|
+
post_install_message:
|
125
|
+
rdoc_options: []
|
126
|
+
require_paths:
|
127
|
+
- lib
|
128
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
129
|
+
none: false
|
130
|
+
requirements:
|
131
|
+
- - ! '>='
|
132
|
+
- !ruby/object:Gem::Version
|
133
|
+
version: '0'
|
134
|
+
segments:
|
135
|
+
- 0
|
136
|
+
hash: 442708502916154574
|
137
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
138
|
+
none: false
|
139
|
+
requirements:
|
140
|
+
- - ! '>='
|
141
|
+
- !ruby/object:Gem::Version
|
142
|
+
version: '0'
|
143
|
+
segments:
|
144
|
+
- 0
|
145
|
+
hash: 442708502916154574
|
146
|
+
requirements: []
|
147
|
+
rubyforge_project: gitkit-ruby
|
148
|
+
rubygems_version: 1.8.10
|
149
|
+
signing_key:
|
150
|
+
specification_version: 3
|
151
|
+
summary: Rack middleware for Google Identity Toolkit
|
152
|
+
test_files:
|
153
|
+
- test/gitkit_test.rb
|