simple_google_auth 0.3.2 → 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.
- checksums.yaml +4 -4
- data/lib/simple_google_auth/auth_data_presenter.rb +6 -3
- data/lib/simple_google_auth/controller.rb +9 -1
- data/lib/simple_google_auth/receiver.rb +7 -5
- data/lib/simple_google_auth/version.rb +1 -1
- metadata +17 -21
- data/spec/simple_google_auth/auth_data_presenter_spec.rb +0 -41
- data/spec/simple_google_auth/authorization_uri_builder_spec.rb +0 -13
- data/spec/simple_google_auth/config_spec.rb +0 -55
- data/spec/simple_google_auth/controller_spec.rb +0 -49
- data/spec/simple_google_auth/http_client_spec.rb +0 -82
- data/spec/simple_google_auth/oauth_spec.rb +0 -67
- data/spec/simple_google_auth/receiver_spec.rb +0 -102
- data/spec/simple_google_auth_spec.rb +0 -13
- data/spec/spec_helper.rb +0 -102
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 05f9712471de03b8e22a2f7f803a28ad58d9dbc8bbb95c2fa085cde8d078133e
|
|
4
|
+
data.tar.gz: 6ddebf2a44c6e3263a4bdb6c128f6587d3e664f2da6113ae02dd2f7be8a00165
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0dd0baf9ef54bcdcad751596b67b24c5f859895e8d9742b59d15ea432aaae7720dc22d357c5f0a8ca8086866eeef8f465dbc0d3cf44c97334a9d53c8cb969bed
|
|
7
|
+
data.tar.gz: 65213c8a689db1dce1e104e9ddd7ca7585d36757bd66d6db1de40fb682a3ddec513bb7216920a3fed4d2fad262c5c20d81487a0cb26b30083b04398b58014770
|
|
@@ -42,9 +42,12 @@ module SimpleGoogleAuth
|
|
|
42
42
|
# We don't worry about validating the signature because we got this JWT directly
|
|
43
43
|
# from Google over HTTPS (see
|
|
44
44
|
# https://developers.google.com/identity/protocols/OpenIDConnect#obtainuserinfo)
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
45
|
+
header, id_data_64 = id_token.split(".")
|
|
46
|
+
raise InvalidAuthDataError if id_data_64.nil?
|
|
47
|
+
|
|
48
|
+
JSON.parse(Base64.urlsafe_decode64(id_data_64))
|
|
49
|
+
rescue JSON::ParserError, ArgumentError
|
|
50
|
+
raise InvalidAuthDataError
|
|
48
51
|
end
|
|
49
52
|
end
|
|
50
53
|
end
|
|
@@ -22,9 +22,17 @@ module SimpleGoogleAuth
|
|
|
22
22
|
|
|
23
23
|
private
|
|
24
24
|
|
|
25
|
+
# If the refresh is refused (e.g. the refresh token has been revoked), clear
|
|
26
|
+
# the session data so the user is sent back through the login flow instead
|
|
27
|
+
# of the error propagating as a 500.
|
|
25
28
|
def refresh_google_auth_data
|
|
26
29
|
api = SimpleGoogleAuth::OAuth.new(SimpleGoogleAuth.config)
|
|
27
|
-
|
|
30
|
+
|
|
31
|
+
auth_data = begin
|
|
32
|
+
api.refresh_auth_token!(cached_google_auth_data["refresh_token"])
|
|
33
|
+
rescue ProviderError
|
|
34
|
+
nil
|
|
35
|
+
end
|
|
28
36
|
|
|
29
37
|
session[SimpleGoogleAuth.config.data_session_key_name] = auth_data
|
|
30
38
|
@_google_auth_data_presenter = nil
|
|
@@ -3,7 +3,11 @@ module SimpleGoogleAuth
|
|
|
3
3
|
def call(env)
|
|
4
4
|
request = Rack::Request.new(env)
|
|
5
5
|
config = SimpleGoogleAuth.config
|
|
6
|
-
|
|
6
|
+
|
|
7
|
+
# The state is single-use: remove it from the session up front so the
|
|
8
|
+
# callback URL can't be replayed, whether this attempt succeeds or fails.
|
|
9
|
+
state = request.session.delete(config.state_session_key_name)
|
|
10
|
+
ensure_params_are_correct(request, state)
|
|
7
11
|
|
|
8
12
|
api = SimpleGoogleAuth::OAuth.new(config)
|
|
9
13
|
auth_data = api.exchange_code_for_auth_token!(request.params["code"])
|
|
@@ -14,7 +18,7 @@ module SimpleGoogleAuth
|
|
|
14
18
|
renew_session(request)
|
|
15
19
|
request.session[config.data_session_key_name] = auth_data
|
|
16
20
|
|
|
17
|
-
path = config.authentication_uri_state_path_extractor.call(
|
|
21
|
+
path = config.authentication_uri_state_path_extractor.call(state)
|
|
18
22
|
path = "/" unless safe_redirect_path?(path)
|
|
19
23
|
[302, {"Location" => path}, [" "]]
|
|
20
24
|
|
|
@@ -26,9 +30,7 @@ module SimpleGoogleAuth
|
|
|
26
30
|
end
|
|
27
31
|
|
|
28
32
|
protected
|
|
29
|
-
def ensure_params_are_correct(request,
|
|
30
|
-
expected_state = request.session[config.state_session_key_name]
|
|
31
|
-
|
|
33
|
+
def ensure_params_are_correct(request, expected_state)
|
|
32
34
|
if expected_state.blank? || !states_match?(request.params["state"], expected_state)
|
|
33
35
|
raise Error, "Invalid state returned from Google"
|
|
34
36
|
elsif request.params["error"]
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: simple_google_auth
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 1.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Roger Nesbitt
|
|
@@ -38,6 +38,20 @@ dependencies:
|
|
|
38
38
|
- - "~>"
|
|
39
39
|
- !ruby/object:Gem::Version
|
|
40
40
|
version: '6.1'
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: simplecov
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - "~>"
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '0.22'
|
|
48
|
+
type: :development
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - "~>"
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '0.22'
|
|
41
55
|
description: An extremely easy way to protect your site by requiring Google logins
|
|
42
56
|
without having to set up a traditional authentication system
|
|
43
57
|
email:
|
|
@@ -60,15 +74,6 @@ files:
|
|
|
60
74
|
- lib/simple_google_auth/oauth.rb
|
|
61
75
|
- lib/simple_google_auth/receiver.rb
|
|
62
76
|
- lib/simple_google_auth/version.rb
|
|
63
|
-
- spec/simple_google_auth/auth_data_presenter_spec.rb
|
|
64
|
-
- spec/simple_google_auth/authorization_uri_builder_spec.rb
|
|
65
|
-
- spec/simple_google_auth/config_spec.rb
|
|
66
|
-
- spec/simple_google_auth/controller_spec.rb
|
|
67
|
-
- spec/simple_google_auth/http_client_spec.rb
|
|
68
|
-
- spec/simple_google_auth/oauth_spec.rb
|
|
69
|
-
- spec/simple_google_auth/receiver_spec.rb
|
|
70
|
-
- spec/simple_google_auth_spec.rb
|
|
71
|
-
- spec/spec_helper.rb
|
|
72
77
|
homepage: https://github.com/mogest/simple_google_auth
|
|
73
78
|
licenses:
|
|
74
79
|
- MIT
|
|
@@ -81,7 +86,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
81
86
|
requirements:
|
|
82
87
|
- - ">="
|
|
83
88
|
- !ruby/object:Gem::Version
|
|
84
|
-
version:
|
|
89
|
+
version: '3.0'
|
|
85
90
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
86
91
|
requirements:
|
|
87
92
|
- - ">="
|
|
@@ -92,13 +97,4 @@ rubygems_version: 3.5.22
|
|
|
92
97
|
signing_key:
|
|
93
98
|
specification_version: 4
|
|
94
99
|
summary: Super simple Google authentication for your Rails site
|
|
95
|
-
test_files:
|
|
96
|
-
- spec/simple_google_auth/auth_data_presenter_spec.rb
|
|
97
|
-
- spec/simple_google_auth/authorization_uri_builder_spec.rb
|
|
98
|
-
- spec/simple_google_auth/config_spec.rb
|
|
99
|
-
- spec/simple_google_auth/controller_spec.rb
|
|
100
|
-
- spec/simple_google_auth/http_client_spec.rb
|
|
101
|
-
- spec/simple_google_auth/oauth_spec.rb
|
|
102
|
-
- spec/simple_google_auth/receiver_spec.rb
|
|
103
|
-
- spec/simple_google_auth_spec.rb
|
|
104
|
-
- spec/spec_helper.rb
|
|
100
|
+
test_files: []
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
require 'spec_helper'
|
|
2
|
-
|
|
3
|
-
describe SimpleGoogleAuth::AuthDataPresenter do
|
|
4
|
-
let(:id_data) do
|
|
5
|
-
{
|
|
6
|
-
"iss" => "accounts.google.com",
|
|
7
|
-
"sub" => "10769150350006150715113082367",
|
|
8
|
-
"email" => "test@test.example",
|
|
9
|
-
"aud" => "1234987819200.apps.googleusercontent.com",
|
|
10
|
-
"iat" => 1353601026,
|
|
11
|
-
"exp" => 1353604926
|
|
12
|
-
}
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
let(:id_token) { "12345." + Base64.encode64(id_data.to_json).gsub('=', '') }
|
|
16
|
-
let(:auth_data) do
|
|
17
|
-
{
|
|
18
|
-
"id_token" => id_token,
|
|
19
|
-
"expires_in" => 1200,
|
|
20
|
-
"access_token" => "abcdef",
|
|
21
|
-
"token_type" => "Bearer"
|
|
22
|
-
}
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
subject { SimpleGoogleAuth::AuthDataPresenter.new(auth_data) }
|
|
26
|
-
|
|
27
|
-
it "provides indifferent hash access to data in the JWT" do
|
|
28
|
-
expect(subject['email']).to eq 'test@test.example'
|
|
29
|
-
expect(subject[:email]).to eq 'test@test.example'
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
it "provides method access to data in the JWT" do
|
|
33
|
-
expect(subject.email).to eq 'test@test.example'
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
it "raises if id_token not provided" do
|
|
37
|
-
expect {
|
|
38
|
-
SimpleGoogleAuth::AuthDataPresenter.new({})
|
|
39
|
-
}.to raise_error(SimpleGoogleAuth::AuthDataPresenter::InvalidAuthDataError)
|
|
40
|
-
end
|
|
41
|
-
end
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
require 'spec_helper'
|
|
2
|
-
|
|
3
|
-
describe SimpleGoogleAuth::AuthorizationUriBuilder do
|
|
4
|
-
subject do
|
|
5
|
-
SimpleGoogleAuth::AuthorizationUriBuilder.new("somestate")
|
|
6
|
-
end
|
|
7
|
-
|
|
8
|
-
describe "#uri" do
|
|
9
|
-
it "constructs an authorization URI" do
|
|
10
|
-
expect(subject.uri).to eq 'https://accounts.google.com/o/oauth2/auth?scope=openid+email&response_type=code&client_id=123&redirect_uri=%2Fabc&state=somestate'
|
|
11
|
-
end
|
|
12
|
-
end
|
|
13
|
-
end
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
require 'spec_helper'
|
|
2
|
-
|
|
3
|
-
describe SimpleGoogleAuth::Config do
|
|
4
|
-
subject { SimpleGoogleAuth::Config.new }
|
|
5
|
-
|
|
6
|
-
describe "#client_id" do
|
|
7
|
-
it "gets the value if it doesn't respond to call" do
|
|
8
|
-
subject.client_id = '12345'
|
|
9
|
-
expect(subject.client_id).to eq '12345'
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
it "calls to get the value if it responds to call" do
|
|
13
|
-
subject.client_id = lambda { '12345' }
|
|
14
|
-
expect(subject.client_id).to eq '12345'
|
|
15
|
-
end
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
describe "#authenticate=" do
|
|
19
|
-
it "saves the value if it is callable" do
|
|
20
|
-
fn = lambda {|data| true}
|
|
21
|
-
subject.authenticate = fn
|
|
22
|
-
expect(subject.authenticate).to eql fn
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
it "raises if the value isn't callable" do
|
|
26
|
-
expect {
|
|
27
|
-
subject.authenticate = "not a lambda"
|
|
28
|
-
}.to raise_error(SimpleGoogleAuth::Error, /responds to :call/)
|
|
29
|
-
end
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
describe "#ca_path=" do
|
|
33
|
-
it "logs a warning" do
|
|
34
|
-
Rails.logger ||= double
|
|
35
|
-
expect(Rails.logger).to receive(:warn)
|
|
36
|
-
subject.ca_path = "/etc/certs"
|
|
37
|
-
end
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
describe "#authentication_uri_state_builder=" do
|
|
41
|
-
it "raises if the value isn't callable" do
|
|
42
|
-
expect {
|
|
43
|
-
subject.authentication_uri_state_builder = "not a lambda"
|
|
44
|
-
}.to raise_error(SimpleGoogleAuth::Error, /responds to :call/)
|
|
45
|
-
end
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
describe "#authentication_uri_state_path_extractor=" do
|
|
49
|
-
it "raises if the value isn't callable" do
|
|
50
|
-
expect {
|
|
51
|
-
subject.authentication_uri_state_path_extractor = "not a lambda"
|
|
52
|
-
}.to raise_error(SimpleGoogleAuth::Error, /responds to :call/)
|
|
53
|
-
end
|
|
54
|
-
end
|
|
55
|
-
end
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
require 'spec_helper'
|
|
2
|
-
|
|
3
|
-
describe SimpleGoogleAuth::Controller do
|
|
4
|
-
class TestController
|
|
5
|
-
include SimpleGoogleAuth::Controller
|
|
6
|
-
|
|
7
|
-
attr_reader :request, :session
|
|
8
|
-
|
|
9
|
-
def redirect_to(x)
|
|
10
|
-
end
|
|
11
|
-
end
|
|
12
|
-
|
|
13
|
-
subject { TestController.new }
|
|
14
|
-
|
|
15
|
-
let(:id_data) { Base64.encode64({email:"hi@hi"}.to_json).gsub('=', '') }
|
|
16
|
-
let(:auth_data) { {"id_token" => "123." + id_data} }
|
|
17
|
-
let(:request) { double(path: "/somepath") }
|
|
18
|
-
let(:session) { {} }
|
|
19
|
-
|
|
20
|
-
before do
|
|
21
|
-
allow(subject).to receive(:request).and_return(request)
|
|
22
|
-
allow(subject).to receive(:session).and_return(session)
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
describe "#redirect_if_not_google_authenticated" do
|
|
26
|
-
it "redirects if not authenticated" do
|
|
27
|
-
SimpleGoogleAuth.config.authentication_uri_state_builder = ->(request) { 'prefix-/somepath' }
|
|
28
|
-
|
|
29
|
-
expect(subject).to receive(:redirect_to).with("https://accounts.google.com/o/oauth2/auth?scope=openid+email&response_type=code&client_id=123&redirect_uri=%2Fabc&state=prefix-%2Fsomepath")
|
|
30
|
-
subject.send(:redirect_if_not_google_authenticated)
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
it "does nothing if authenticated" do
|
|
34
|
-
session[SimpleGoogleAuth.config.data_session_key_name] = auth_data
|
|
35
|
-
expect(subject).to_not receive(:redirect_to)
|
|
36
|
-
subject.send(:redirect_if_not_google_authenticated)
|
|
37
|
-
end
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
describe "#google_auth_data" do
|
|
41
|
-
it "returns data from the session" do
|
|
42
|
-
session[SimpleGoogleAuth.config.data_session_key_name] = auth_data
|
|
43
|
-
data = subject.send(:google_auth_data)
|
|
44
|
-
expect(data.email).to eq 'hi@hi'
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
it "refreshes the token data if it's expired and refresh_stale_tokens is true"
|
|
48
|
-
end
|
|
49
|
-
end
|
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
require 'spec_helper'
|
|
2
|
-
|
|
3
|
-
describe SimpleGoogleAuth::HttpClient do
|
|
4
|
-
describe "#request" do
|
|
5
|
-
let(:http) { instance_double(Net::HTTP) }
|
|
6
|
-
let(:request) { instance_double(Net::HTTP::Post) }
|
|
7
|
-
|
|
8
|
-
before do
|
|
9
|
-
expect(Net::HTTP).to receive(:new).with("some.host", 443).and_return(http)
|
|
10
|
-
expect(http).to receive(:open_timeout=).with(12)
|
|
11
|
-
expect(http).to receive(:read_timeout=).with(13)
|
|
12
|
-
expect(http).to receive(:use_ssl=).with(true)
|
|
13
|
-
expect(http).to receive(:verify_mode=).with(OpenSSL::SSL::VERIFY_PEER)
|
|
14
|
-
expect(http).to receive(:request).with(request).and_return(response)
|
|
15
|
-
|
|
16
|
-
expect(Net::HTTP::Post).to receive(:new).with("/somepath").and_return(request)
|
|
17
|
-
expect(request).to receive(:set_form_data).with({'some' => 'data'})
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
subject { SimpleGoogleAuth::HttpClient.new("https://some.host/somepath", open_timeout: 12, read_timeout: 13) }
|
|
21
|
-
|
|
22
|
-
context "when the call is successful" do
|
|
23
|
-
let(:response) do
|
|
24
|
-
instance_double(
|
|
25
|
-
Net::HTTPSuccess,
|
|
26
|
-
code: '200',
|
|
27
|
-
body: {"data" => "very"}.to_json,
|
|
28
|
-
content_type: 'application/json'
|
|
29
|
-
)
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
it "returns the server's response" do
|
|
33
|
-
expect(subject.request('some' => 'data')).to eq("data" => "very")
|
|
34
|
-
end
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
context "when non-json data is returned" do
|
|
38
|
-
let(:response) do
|
|
39
|
-
instance_double(
|
|
40
|
-
Net::HTTPSuccess,
|
|
41
|
-
code: '200',
|
|
42
|
-
body: "some html",
|
|
43
|
-
content_type: 'text/html'
|
|
44
|
-
)
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
it "raises an error" do
|
|
48
|
-
expect { subject.request('some' => 'data') }.to raise_error(SimpleGoogleAuth::NonJsonResponseError, /non-JSON/)
|
|
49
|
-
end
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
context "when non-json-parseable data is returned" do
|
|
53
|
-
let(:response) do
|
|
54
|
-
instance_double(
|
|
55
|
-
Net::HTTPSuccess,
|
|
56
|
-
code: '200',
|
|
57
|
-
body: "some html",
|
|
58
|
-
content_type: 'application/json'
|
|
59
|
-
)
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
it "raises an error" do
|
|
63
|
-
expect { subject.request('some' => 'data') }.to raise_error(SimpleGoogleAuth::NonJsonResponseError, /parseable/)
|
|
64
|
-
end
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
context "when non-successful json data is returned" do
|
|
68
|
-
let(:response) do
|
|
69
|
-
instance_double(
|
|
70
|
-
Net::HTTPSuccess,
|
|
71
|
-
code: '400',
|
|
72
|
-
body: {"data" => "very"}.to_json,
|
|
73
|
-
content_type: 'application/json'
|
|
74
|
-
)
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
it "raises an error" do
|
|
78
|
-
expect { subject.request('some' => 'data') }.to raise_error(SimpleGoogleAuth::ProviderError, /400.+very/)
|
|
79
|
-
end
|
|
80
|
-
end
|
|
81
|
-
end
|
|
82
|
-
end
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
require 'spec_helper'
|
|
2
|
-
|
|
3
|
-
describe SimpleGoogleAuth::OAuth do
|
|
4
|
-
let(:config) do
|
|
5
|
-
instance_double(
|
|
6
|
-
SimpleGoogleAuth::Config,
|
|
7
|
-
google_token_url: "/token/url",
|
|
8
|
-
client_id: '12345',
|
|
9
|
-
client_secret: 'abcde',
|
|
10
|
-
redirect_uri: '/ok',
|
|
11
|
-
open_timeout: 12,
|
|
12
|
-
read_timeout: 13
|
|
13
|
-
)
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
let(:client) { instance_double(SimpleGoogleAuth::HttpClient) }
|
|
17
|
-
let(:response) { {"id_token" => "sometoken", "expires_in" => 1200, "other" => "data"} }
|
|
18
|
-
let(:expires_at) { Time.now + 1200 - 5 }
|
|
19
|
-
|
|
20
|
-
before do
|
|
21
|
-
now = Time.now
|
|
22
|
-
allow(Time).to receive(:now).and_return(now)
|
|
23
|
-
|
|
24
|
-
expect(SimpleGoogleAuth::HttpClient).to receive(:new).with(config.google_token_url, open_timeout: 12, read_timeout: 13).and_return(client)
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
subject { SimpleGoogleAuth::OAuth.new(config) }
|
|
28
|
-
|
|
29
|
-
describe "#exchange_code_for_auth_token!" do
|
|
30
|
-
before do
|
|
31
|
-
expect(client).to receive(:request).with(
|
|
32
|
-
code: "magic",
|
|
33
|
-
grant_type: "authorization_code",
|
|
34
|
-
client_id: "12345",
|
|
35
|
-
client_secret: "abcde",
|
|
36
|
-
redirect_uri: "/ok"
|
|
37
|
-
).and_return(response)
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
it "returns a hash of auth token data" do
|
|
41
|
-
expect(subject.exchange_code_for_auth_token!('magic')).to eq('expires_in' => 1200, 'other' => 'data', 'id_token' => 'sometoken', 'expires_at' => expires_at.to_s)
|
|
42
|
-
end
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
describe "#refresh_auth_token!" do
|
|
46
|
-
context "when a refresh token is provided" do
|
|
47
|
-
before do
|
|
48
|
-
expect(client).to receive(:request).with(
|
|
49
|
-
refresh_token: "magic",
|
|
50
|
-
grant_type: "refresh_token",
|
|
51
|
-
client_id: "12345",
|
|
52
|
-
client_secret: "abcde",
|
|
53
|
-
).and_return(response)
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
it "returns a hash of auth token data" do
|
|
57
|
-
expect(subject.refresh_auth_token!('magic')).to eq('expires_in' => 1200, 'other' => 'data', 'id_token' => 'sometoken', 'expires_at' => expires_at.to_s, 'refresh_token' => 'magic')
|
|
58
|
-
end
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
context "when no refresh token is provided" do
|
|
62
|
-
it "does nothing and returns nil" do
|
|
63
|
-
expect(subject.refresh_auth_token!(nil)).to be nil
|
|
64
|
-
end
|
|
65
|
-
end
|
|
66
|
-
end
|
|
67
|
-
end
|
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
require 'spec_helper'
|
|
2
|
-
|
|
3
|
-
describe SimpleGoogleAuth::Receiver do
|
|
4
|
-
let(:authenticator) { double(call: true) }
|
|
5
|
-
let(:authentication_result) { true }
|
|
6
|
-
let(:session) { double }
|
|
7
|
-
let(:state) { "abcd" * 8 + "/place" }
|
|
8
|
-
let(:code) { "sekrit" }
|
|
9
|
-
let(:params) { {"state" => state, "code" => code} }
|
|
10
|
-
let(:request) { instance_double(Rack::Request, session: session, params: params) }
|
|
11
|
-
let(:api) { instance_double(SimpleGoogleAuth::OAuth) }
|
|
12
|
-
let(:auth_data) { double }
|
|
13
|
-
let(:env) { double }
|
|
14
|
-
let(:auth_data_presenter) { instance_double(SimpleGoogleAuth::AuthDataPresenter) }
|
|
15
|
-
let(:authentication_uri_state_path_extractor) { double(:call => '') }
|
|
16
|
-
|
|
17
|
-
before do
|
|
18
|
-
expect(Rack::Request).to receive(:new).with(env).and_return(request)
|
|
19
|
-
expect(session).to receive(:[]).at_least(:once).with('simple-google-auth.state').and_return(state)
|
|
20
|
-
|
|
21
|
-
SimpleGoogleAuth.config.authenticate = authenticator
|
|
22
|
-
SimpleGoogleAuth.config.failed_login_path = '/error'
|
|
23
|
-
SimpleGoogleAuth.config.authentication_uri_state_path_extractor = authentication_uri_state_path_extractor
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
subject { SimpleGoogleAuth::Receiver.new.call(env) }
|
|
27
|
-
|
|
28
|
-
context "when a valid code is provided to the receiver" do
|
|
29
|
-
before do
|
|
30
|
-
expect(SimpleGoogleAuth::OAuth).to receive(:new).with(SimpleGoogleAuth.config).and_return(api)
|
|
31
|
-
expect(api).to receive(:exchange_code_for_auth_token!).with(code).and_return(auth_data)
|
|
32
|
-
|
|
33
|
-
expect(SimpleGoogleAuth::AuthDataPresenter).to receive(:new).with(auth_data).and_return(auth_data_presenter)
|
|
34
|
-
expect(authenticator).to receive(:call).with(auth_data_presenter).and_return(authentication_result)
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
context "and the authenticator accepts the login" do
|
|
38
|
-
before do
|
|
39
|
-
expect(session).to receive(:[]=).with('simple-google-auth.data', auth_data)
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
it "redirects to the URL specified in the session" do
|
|
43
|
-
expect(authentication_uri_state_path_extractor).to receive(:call).with(state).and_return('/place')
|
|
44
|
-
|
|
45
|
-
expect(subject).to eq [302, {"Location" => "/place"}, [" "]]
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
it "does not follow a protocol-relative redirect path (open redirect)" do
|
|
49
|
-
expect(authentication_uri_state_path_extractor).to receive(:call).with(state).and_return('//evil.com')
|
|
50
|
-
|
|
51
|
-
expect(subject).to eq [302, {"Location" => "/"}, [" "]]
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
it "does not follow a backslash-prefixed redirect path (open redirect)" do
|
|
55
|
-
expect(authentication_uri_state_path_extractor).to receive(:call).with(state).and_return('/\\evil.com')
|
|
56
|
-
|
|
57
|
-
expect(subject).to eq [302, {"Location" => "/"}, [" "]]
|
|
58
|
-
end
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
context "and the authenticator rejects the login" do
|
|
62
|
-
let(:authentication_result) { false }
|
|
63
|
-
|
|
64
|
-
it "redirects to the failed login path with a message" do
|
|
65
|
-
expect(subject).to eq [302, {"Location" => "/error?message=Authentication+failed"}, [" "]]
|
|
66
|
-
end
|
|
67
|
-
end
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
context "when the state doesn't match" do
|
|
71
|
-
let(:params) { {"state" => "doesnotmatch", "code" => code} }
|
|
72
|
-
|
|
73
|
-
it "redirects to the failed login path with a message" do
|
|
74
|
-
expect(subject).to eq [302, {"Location" => "/error?message=Invalid+state+returned+from+Google"}, [" "]]
|
|
75
|
-
end
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
context "when the session holds no state and the callback omits it (forged callback)" do
|
|
79
|
-
let(:state) { nil }
|
|
80
|
-
let(:params) { {"code" => code} }
|
|
81
|
-
|
|
82
|
-
it "rejects the login rather than treating two blank states as a match" do
|
|
83
|
-
expect(subject).to eq [302, {"Location" => "/error?message=Invalid+state+returned+from+Google"}, [" "]]
|
|
84
|
-
end
|
|
85
|
-
end
|
|
86
|
-
|
|
87
|
-
context "when the google authentication fails" do
|
|
88
|
-
let(:params) { {"state" => state, "error" => "bad stuff"} }
|
|
89
|
-
|
|
90
|
-
it "redirects to the failed login path with a message" do
|
|
91
|
-
expect(subject).to eq [302, {"Location" => "/error?message=Authentication+failed%3A+bad+stuff"}, [" "]]
|
|
92
|
-
end
|
|
93
|
-
end
|
|
94
|
-
|
|
95
|
-
context "when no code is returned (unexpected)" do
|
|
96
|
-
let(:params) { {"state" => state} }
|
|
97
|
-
|
|
98
|
-
it "redirects to the failed login path with a message" do
|
|
99
|
-
expect(subject).to eq [302, {"Location" => "/error?message=No+authentication+code+returned"}, [" "]]
|
|
100
|
-
end
|
|
101
|
-
end
|
|
102
|
-
end
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
require 'spec_helper'
|
|
2
|
-
|
|
3
|
-
describe SimpleGoogleAuth do
|
|
4
|
-
describe "::configure" do
|
|
5
|
-
it "yields the config object" do
|
|
6
|
-
SimpleGoogleAuth.configure do |config|
|
|
7
|
-
expect(config).to be_a(SimpleGoogleAuth::Config)
|
|
8
|
-
end
|
|
9
|
-
end
|
|
10
|
-
|
|
11
|
-
it "sets access_type to offline if refresh_stale_tokens set"
|
|
12
|
-
end
|
|
13
|
-
end
|
data/spec/spec_helper.rb
DELETED
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
ENV['RAILS_ENV'] ||= 'test'
|
|
2
|
-
|
|
3
|
-
require 'rails/all'
|
|
4
|
-
#require 'rspec/rails'
|
|
5
|
-
require 'simple_google_auth'
|
|
6
|
-
|
|
7
|
-
SimpleGoogleAuth.configure do |c|
|
|
8
|
-
c.client_id = '123'
|
|
9
|
-
c.redirect_uri = '/abc'
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
# This file was generated by the `rspec --init` command. Conventionally, all
|
|
13
|
-
# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
|
|
14
|
-
# The generated `.rspec` file contains `--require spec_helper` which will cause
|
|
15
|
-
# this file to always be loaded, without a need to explicitly require it in any
|
|
16
|
-
# files.
|
|
17
|
-
#
|
|
18
|
-
# Given that it is always loaded, you are encouraged to keep this file as
|
|
19
|
-
# light-weight as possible. Requiring heavyweight dependencies from this file
|
|
20
|
-
# will add to the boot time of your test suite on EVERY test run, even for an
|
|
21
|
-
# individual file that may not need all of that loaded. Instead, consider making
|
|
22
|
-
# a separate helper file that requires the additional dependencies and performs
|
|
23
|
-
# the additional setup, and require it from the spec files that actually need
|
|
24
|
-
# it.
|
|
25
|
-
#
|
|
26
|
-
# The `.rspec` file also contains a few flags that are not defaults but that
|
|
27
|
-
# users commonly want.
|
|
28
|
-
#
|
|
29
|
-
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
|
|
30
|
-
RSpec.configure do |config|
|
|
31
|
-
# rspec-expectations config goes here. You can use an alternate
|
|
32
|
-
# assertion/expectation library such as wrong or the stdlib/minitest
|
|
33
|
-
# assertions if you prefer.
|
|
34
|
-
config.expect_with :rspec do |expectations|
|
|
35
|
-
# This option will default to `true` in RSpec 4. It makes the `description`
|
|
36
|
-
# and `failure_message` of custom matchers include text for helper methods
|
|
37
|
-
# defined using `chain`, e.g.:
|
|
38
|
-
# be_bigger_than(2).and_smaller_than(4).description
|
|
39
|
-
# # => "be bigger than 2 and smaller than 4"
|
|
40
|
-
# ...rather than:
|
|
41
|
-
# # => "be bigger than 2"
|
|
42
|
-
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
# rspec-mocks config goes here. You can use an alternate test double
|
|
46
|
-
# library (such as bogus or mocha) by changing the `mock_with` option here.
|
|
47
|
-
config.mock_with :rspec do |mocks|
|
|
48
|
-
# Prevents you from mocking or stubbing a method that does not exist on
|
|
49
|
-
# a real object. This is generally recommended, and will default to
|
|
50
|
-
# `true` in RSpec 4.
|
|
51
|
-
mocks.verify_partial_doubles = true
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
# The settings below are suggested to provide a good initial experience
|
|
55
|
-
# with RSpec, but feel free to customize to your heart's content.
|
|
56
|
-
=begin
|
|
57
|
-
# These two settings work together to allow you to limit a spec run
|
|
58
|
-
# to individual examples or groups you care about by tagging them with
|
|
59
|
-
# `:focus` metadata. When nothing is tagged with `:focus`, all examples
|
|
60
|
-
# get run.
|
|
61
|
-
config.filter_run :focus
|
|
62
|
-
config.run_all_when_everything_filtered = true
|
|
63
|
-
|
|
64
|
-
# Limits the available syntax to the non-monkey patched syntax that is
|
|
65
|
-
# recommended. For more details, see:
|
|
66
|
-
# - http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax
|
|
67
|
-
# - http://teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
|
|
68
|
-
# - http://myronmars.to/n/dev-blog/2014/05/notable-changes-in-rspec-3#new__config_option_to_disable_rspeccore_monkey_patching
|
|
69
|
-
config.disable_monkey_patching!
|
|
70
|
-
|
|
71
|
-
# This setting enables warnings. It's recommended, but in some cases may
|
|
72
|
-
# be too noisy due to issues in dependencies.
|
|
73
|
-
config.warnings = true
|
|
74
|
-
|
|
75
|
-
# Many RSpec users commonly either run the entire suite or an individual
|
|
76
|
-
# file, and it's useful to allow more verbose output when running an
|
|
77
|
-
# individual spec file.
|
|
78
|
-
if config.files_to_run.one?
|
|
79
|
-
# Use the documentation formatter for detailed output,
|
|
80
|
-
# unless a formatter has already been configured
|
|
81
|
-
# (e.g. via a command-line flag).
|
|
82
|
-
config.default_formatter = 'doc'
|
|
83
|
-
end
|
|
84
|
-
|
|
85
|
-
# Print the 10 slowest examples and example groups at the
|
|
86
|
-
# end of the spec run, to help surface which specs are running
|
|
87
|
-
# particularly slow.
|
|
88
|
-
config.profile_examples = 10
|
|
89
|
-
|
|
90
|
-
# Run specs in random order to surface order dependencies. If you find an
|
|
91
|
-
# order dependency and want to debug it, you can fix the order by providing
|
|
92
|
-
# the seed, which is printed after each run.
|
|
93
|
-
# --seed 1234
|
|
94
|
-
config.order = :random
|
|
95
|
-
|
|
96
|
-
# Seed global randomization in this process using the `--seed` CLI option.
|
|
97
|
-
# Setting this allows you to use `--seed` to deterministically reproduce
|
|
98
|
-
# test failures related to randomization by passing the same `--seed` value
|
|
99
|
-
# as the one that triggered the failure.
|
|
100
|
-
Kernel.srand config.seed
|
|
101
|
-
=end
|
|
102
|
-
end
|